Реагировать на встроенную навигацию - дочерний элемент не обновляется при родительском обновлении - PullRequest
1 голос
/ 03 июня 2019

Я немного отредактировал пример из официального руководства реагировать на навигацию , чтобы смоделировать изменение состояния через 1 секунду в HomeScreen.

Я не могу понятьпочему DetailScreen не визуализируется при изменении состояния родительского экрана.Есть ли способ получить такое поведение?

import React from 'react';
import { Button, View, Text } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';

class HomeScreen extends React.Component {
  state = { value: 10 }
  render() {
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Home Screen</Text>
        <Button
          title="Click"
          onPress={() => {
            const { value } = this.state;
            this.props.navigation.navigate('Detail', { value });
            setTimeout(() => {
              this.setState({ value: value + 1 })
            }, 1000);
          }}
        />
      </View>
    );
  }
}

class DetailScreen extends React.Component {
  render() {
    const { navigation } = this.props;
    const value = navigation.getParam('value');
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Detail Screen</Text>
        <Text>{value}</Text>
      </View>
    );
  }
}

const RootStack = createStackNavigator(
  {
    Home: HomeScreen,
    Detail: DetailScreen,
  },
  {
    initialRouteName: 'Home',
  }
);

const AppContainer = createAppContainer(RootStack);

export default class App extends React.Component {
  render() {
    return <AppContainer />;
  }
}

Закуска здесь

1 Ответ

2 голосов
/ 03 июня 2019

Почему ваш код не работает:

Вы передаете value в качестве навигационной опоры от Home до Details. В отличие от обычных реквизитов, изменение значения навигационной стойки в Home не приводит к изменению значения самой навигационной стойки. Поэтому, если Home был родительским компонентом, который имел Details в качестве дочернего компонента, например:

class HomeScreen extends React.Component {
  ...
  <DetailsScreen
    value: this.state.value
  />
  ...
}

затем, когда this.state.value изменяется в Home, this.props.value автоматически изменяется в Details. Однако, поскольку у Home и Details есть родственные отношения в стековом навигаторе, вы не можете передать value как обычный реквизит; единственный способ передать реквизит от Home до Details, как вы уже сделали, в качестве параметра навигации. Проблема в том, что когда вы передаете value, как вы сделали:

const { value } = this.state;
this.props.navigation.navigate('Detail', { value });

обновление this.state.value в Home делает не причиной автоматического обновления this.props.navigation.getParam('value'). Итак, в вашем коде:

const value = navigation.getParam('value');
<Text>{value}</Text>

value остается тем, что было, когда оно было первоначально передано.

РЕШЕНИЕ

Существует несколько возможных обходных путей, например, принудительное повторное рендеринг вручную после setTimeout или реструктуризация иерархии компонентов, чтобы сделать Home родителем Details. Тем не менее, я думаю, что лучший способ решить эту проблему при сохранении структуры вашего приложения заключается в следующем:

Вместо this.state.value в Home, удерживайте его в App. Это следует общему принципу, что дочернему элементу проще обновлять переменные состояния родителя (или наоборот), чем компоненту обновлять переменные состояния своего родного брата.

Обновление до App компонента

Поскольку App является AppContainer, вам нужно передать this.state.value в Details через screenprops. При создании любого вида навигатора screenprops - это способ передачи переменных всем компонентам в навигаторе. Итак, ваш App компонент теперь будет выглядеть так:

export default class App extends React.Component {

  state = {value: 10} // state in App now instead of Home

  updateValue = (value) => { // method to update App's state, passed to children
    this.setState({value})
  }

  render() {
    return <AppContainer screenProps={{
      parentState: this.state,
      updateValue: this.updateValue
    }}/>;
  }

}

Обновление до Home компонента

Единственное, что вы измените в компоненте Home, это функция onPress. Во-первых, вы больше не будете передавать value как реквизит навигации, поскольку вы будете получать доступ к значению в виде screenProps, передаваемого из App на все экраны, а не как реквизит навигации, передаваемый из Home в Details , Во-вторых, вместо обновления this.state из Home вы будете вызывать this.props.screenProps.updateValue(), чтобы обновить состояние в App. Таким образом, onPress в вашем Home компоненте теперь будет выглядеть так:

onPress={() => {
  this.props.navigation.navigate('Detail'); // no navigation prop
  setTimeout(() => {
    screenProps.updateValue(screenProps.appState.value + 1) // updating state of App rather than state of Home
  }, 1000);
}}

Обновление до Details компонента

Единственное изменение в Details состоит в том, что вместо отображения this.props.navigation.getParam('value') вы будете отображать this.props.screenProps.appState.value, поскольку теперь мы получаем value из screenProps из App вместо того, чтобы использовать его как навигационную опору Home. Итак, ваш Details компонент теперь будет выглядеть так:

class DetailScreen extends React.Component {
  render() {
    const { screenProps } = this.props;
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Detail Screen</Text>
        <Text>{screenProps.appState.value}</Text> 
      </View>
    );
  }
}

ВСЕ НОВЫЙ КОД

import React from 'react';
import { Button, View, Text } from 'react-native';
import { createStackNavigator, createAppContainer } from 'react-navigation';

class HomeScreen extends React.Component {
  state = { value: 10 }
  render() {
    const { screenProps } = this.props;
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Home Screen</Text>
        <Button
          title="Click"
          onPress={() => {
            this.props.navigation.navigate('Detail'); // no navigation prop
            setTimeout(() => {
              screenProps.updateValue(screenProps.appState.value + 1) // updating state of App rather than state of Home
            }, 1000);
          }}
        />
      </View>
    );
  }
}

class DetailScreen extends React.Component {
  render() {
    const { screenProps } = this.props;
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>Detail Screen</Text>
        <Text>{screenProps.appState.value}</Text>
      </View>
    );
  }
}

const RootStack = createStackNavigator(
  {
    Home: HomeScreen,
    Detail: DetailScreen,
  },
  {
    initialRouteName: 'Home',
  }
);

const AppContainer = createAppContainer(RootStack);

export default class App extends React.Component {

  state = {value: 10} // state in App now instead of Home

  updateValue = (value) => { // method to update App's state, passed to children
    this.setState({value})
  }

  render() {
    return <AppContainer screenProps={{
      appState: this.state,
      updateValue: this.updateValue
    }}/>;
  }

}
...