React Native导航 - 子组件在父组件更新时未更新

3
我已经稍微编辑了React Navigation官方指南中的示例,以便在HomeScreen中模拟1秒后的状态更改。
我不明白为什么当父屏幕状态更改时,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个回答

3

为什么你的代码不能工作:

你从Home传递value作为导航属性到Details。与常规属性不同,更改 Home 中导航属性的值并不会导致导航属性本身的值发生变化。因此,如果 Home 是一个有 Details 作为子组件的父组件,就像这样:

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

当在Home中更改this.state.value时,Details中的this.props.value会自动更改。然而,由于HomeDetails在堆栈导航器中是兄弟关系,你不能将value作为普通属性传递;从Home传递valueDetails的唯一方法就像你已经做的那样,作为导航参数。问题在于,当像你所做的一样传递value时:

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

Home中更新this.state.value不会自动更新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,所以您需要通过screenprops将此.state.value传递给Details。当创建任何类型的导航器时,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作为导航属性传递,因为您将通过从App传递给所有屏幕的screenProps访问该值,而不是从Home传递给Details的导航属性。其次,您将调用this.props.screenProps.updateValue()来更新App中的状态,而不是更新Home中的this.state。因此,Home组件中的onPress现在看起来像这样:

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。因为现在我们从App获取value,而不是从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
    }}/>;
  }

}

请纠正我,如果我错了,使用这种模式会失去使用PureComponents的可能性,因为浅比较不检查screenProps值。 - c.bear
我不确定。我没有使用过PureComponents :) - gkeenley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接