【问题标题】:react-native component lifecycle methods not firing on navigationreact-native 组件生命周期方法未在导航时触发
【发布时间】:2015-08-26 10:17:22
【问题描述】:

我遇到了一个问题,我在多个组件中基于 AsyncStorage 中的相同键设置状态。由于 state 是在 componentDidMount 中设置的,并且这些组件不一定会在导航上卸载和挂载,因此 state 值和 AsyncStorage 值可能会不同步。

这是我能做的最简单的例子。

组件 A

A只是设置了导航和app。

var React = require('react-native');
var B = require('./B');

var {
    AppRegistry,
    Navigator
} = React;

var A = React.createClass({
    render() {
        return (
            <Navigator
                initialRoute={{
                    component: B
                }}
                renderScene={(route, navigator) => {
                    return <route.component navigator={navigator} />;
                }} />
        );
    }
});

AppRegistry.registerComponent('A', () => A);

组件 B

B 在挂载时从 AsyncStorage 读取,然后设置为状态。

var React = require('react-native');
var C = require('./C');

var {
    AsyncStorage,
    View,
    Text,
    TouchableHighlight
} = React;

var B = React.createClass({
    componentDidMount() {
        AsyncStorage.getItem('some-identifier').then(value => {
            this.setState({
                isPresent: value !== null
            });
        });
    },

    getInitialState() {
        return {
            isPresent: false
        };
    },

    goToC() {
        this.props.navigator.push({
            component: C
        });
    },

    render() {
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>
                    {this.state.isPresent
                        ? 'Value is present'
                        : 'Value is not present'}
                </Text>

                <TouchableHighlight onPress={this.goToC}>
                    <Text>Click to go to C</Text>
                </TouchableHighlight>
            </View>
        );
    }
});

module.exports = B;

组件 C

C 从 AsyncStorage 中读取与 B 相同的值,但允许您更改该值。更改会切换状态和 AsyncStorage 中的值。

var React = require('react-native');
var {
    AsyncStorage,
    View,
    Text,
    TouchableHighlight
} = React;

var C = React.createClass({
    componentDidMount() {
        AsyncStorage.getItem('some-identifier').then(value => {
            this.setState({
                isPresent: value !== null
            });
        });
    },

    getInitialState() {
        return {
            isPresent: false
        };
    },

    toggle() {
        if (this.state.isPresent) {
            AsyncStorage.removeItem('some-identifier').then(() => {
                this.setState({
                    isPresent: false
                });
            })
        } else {
            AsyncStorage.setItem('some-identifier', 'some-value').then(() => {
                this.setState({
                    isPresent: true
                });
            });
        }
    },

    goToB() {
        this.props.navigator.pop();
    },

    render() {
        return (
            <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
                <Text>
                    {this.state.isPresent
                        ? 'Value is present'
                        : 'Value is not present'}
                </Text>

                <TouchableHighlight onPress={this.toggle}>
                    <Text>Click to toggle</Text>
                </TouchableHighlight>

                <TouchableHighlight onPress={this.goToB}>
                    <Text>Click to go back</Text>
                </TouchableHighlight>
            </View>
        );
    }
});

module.exports = C;

如果您在 C 中切换然后返回到 B,则 B 中的状态和 AsyncStorage 中的值现在不同步。据我所知, navigator.pop() 不会触发我可以用来告诉 B 刷新值的任何组件生命周期函数。

我知道但并不理想的一个解决方案是让 B 的状态成为 C 的道具,并给 C 一个回调道具来切换它。如果 B 和 C 始终是直接的父子关系,这将很有效,但在真正的应用程序中,导航层次结构可能会更深。

是否有在导航事件之后触发组件上的功能,或者我缺少的其他东西?

【问题讨论】:

    标签: reactjs react-native reactjs-native


    【解决方案1】:

    1) 这里的主要问题在于您的架构 - 您需要围绕 AsyncStorage 创建一些包装器,该包装器还会在某些值发生更改时生成事件,类接口的示例是:

    class Storage extends EventEmitter {
        get(key) { ... }
        set(key, value) { ... }
    }
    

    在组件WillMount中:

    storage.on('someValueChanged', this.onChanged);
    

    在组件WillUnmount:

    storage.removeListener('someValueChanged', this.onChanged);
    

    2) 架构问题也可以通过使用例如redux + react-redux 来解决,它的全局应用状态和更改时自动重新渲染。

    3) 其他方式(不是基于事件的,因此并不完美)是添加自定义生命周期方法,如 componentDidAppear 和 componentDidDissapear。这是BaseComponent 类的示例:

    import React, { Component } from 'react';
    
    export default class BaseComponent extends Component {
        constructor(props) {
            super(props);
            this.appeared = false;
        }
    
        componentWillMount() {
            this.route = this.props.navigator.navigationContext.currentRoute;
            console.log('componentWillMount', this.route.name);
    
            this.didFocusSubscription = this.props.navigator.navigationContext.addListener('didfocus', event => {
                if (this.route === event.data.route) {
                    this.appeared = true;
                    this.componentDidAppear();
                } else if (this.appeared) {
                    this.appeared = false;
                    this.componentDidDisappear();
                }
            });
        }
    
        componentDidMount() {
            console.log('componentDidMount', this.route.name);
        }
    
        componentWillUnmount() {
            console.log('componentWillUnmount', this.route.name);
            this.didFocusSubscription.remove();
            this.componentDidDisappear();
        }
    
        componentDidAppear() {
            console.log('componentDidAppear', this.route.name);
        }
    
        componentDidDisappear() {
            console.log('componentDidDisappear', this.route.name);
        }
    }
    

    因此,只需从该组件扩展并覆盖 componentDidAppear 方法(不要忘记 OOP 并在内部调用超级实现:super.componentdDidAppear())。

    【讨论】:

      【解决方案2】:

      我的解决方案是尝试为组件添加我的自定义生命周期。

      this._navigator.addListener('didfocus', (event) => {
        let component = event.target.currentRoute.scene;
        if (component.getWrappedInstance !== undefined) {
          // If component is wrapped by react-redux
          component = component.getWrappedInstance();
        }
        if (component.componentDidFocusByNavigator !== undefined &&
            typeof(component.componentDidFocusByNavigator) === 'function') {
          component.componentDidFocusByNavigator();
        }
      });
      

      然后你可以在你的组件中添加componentDidFocusByNavigator()来做一些事情。

      【讨论】:

        【解决方案3】:

        遇到了和你一样的问题。我希望这会改变,或者我们在组件上获得一个额外的事件来监听 (componentDidRenderFromRoute) 或类似的东西。无论如何,我解决它的方法是将我的父组件保持在范围内,因此子导航栏可以调用组件上的方法。我正在使用:https://github.com/Kureev/react-native-navbar,但它只是 Navigator 的一个包装器:

        class ProjectList extends Component {
          _onChange(project) {
            this.props.navigator.push({
              component: Project,
              props: { project: project },
              navigationBar: (<NavigationBar
                title={project.title}
                titleColor="#000000"
                style={appStyles.navigator}
                customPrev={<CustomPrev onPress={() => {
                  this.props.navigator.pop();
                  this._sub();
                }} />}
              />)
            });
          }
        }
        

        我正在推送一个带有道具数据的项目组件,并附加了我的导航栏组件。 customPrev 是 react-native-navbar 将替换为其默认值的内容。因此,在其按下时,我调用 pop,并在我的 ProjectList 实例上调用 _sub 方法。

        【讨论】:

          【解决方案4】:

          我认为解决方案应该是围绕 AsyncStorage 进行包装,并可能使用“flux”架构。 https://github.com/facebook/flux 在 Dispatcher 和 Event Emitters 的帮助下 - 非常类似于 Flux 聊天示例:https://github.com/facebook/flux/tree/master/examples/flux-chat

          首先也是最重要的。如 AsyncStorage 文档中所述:https://facebook.github.io/react-native/docs/asyncstorage.html

          建议您在 AsyncStorage 之上使用抽象 而不是 AsyncStorage 直接用于轻量级的使用 因为它在全球范围内运作。

          所以我认为您应该构建自己的特定“域”存储,它将包装通用 AsyncStorage 并执行以下操作(请参阅聊天示例中的存储):

          1. 公开特定方法(可能是属性?)以更改值(任何更改都应在异步更改完成后触发“更改”事件)
          2. 公开用于读取值的特定方法(可能是属性?)(只要域存储在异步更改完成后缓存值,这些可能成为同步读取的属性)
          3. 公开一个“注册更改”方法(以便需要响应更改的组件可以注册)
          4. 在此类“更改”事件处理程序中,组件的状态应设置为从存储中读取(读取属性)
          5. 最后但并非最不重要 - 我认为最好遵循 react 的模式,通过 Dispatcher(通量的一部分)对存储进行更改。因此,组件不会直接将“更改”方法调用到“域存储”,而是生成“动作”,然后“域”存储应该通过更新其存储的值(并因此触发更改事件)来处理这些动作

          这首先看起来有点矫枉过正,但它解决了许多问题(包括级联更新等 - 当应用程序变得更大时会很明显 - 它引入了一些似乎有意义的合理抽象。你可以可能只是第 1-4 点而不引入调度程序 - 应该仍然可以工作,但以后可能会导致 Facebook 描述的问题(阅读通量文档了解更多详细信息)。

          【讨论】:

            【解决方案5】:

            如果您使用 NavigatorIOS,那么它可以将底层导航器传递给每个子组件。这有几个事件可以在这些组件中使用:

            onDidFocus 函数

            将与每个场景的新路线一起调用 过渡完成后或初始安装后

            onItemRef 函数

            将被 (ref, indexInStack, route) 调用 当场景参考改变时

            onWillFocus 函数

            将在安装时发出目标路线,并且 在每次导航转换之前

            https://facebook.github.io/react-native/docs/navigator.html#content

            【讨论】:

            • 感谢您的回答。我认为你不需要使用 NavigatorIOS 来让导航器传递给孩子。请注意,我已经在 B 和 C 中使用了 this.props.navigator,而只是使用了 Navigator 组件。我可能会遗漏一些东西,但我不确定这些功能是否对我有帮助。将它们连接起来后,它们会触发,但它们只是提供对路由和组件的引用,您只能从中调用静态函数 - 而不是任何可以触摸状态或道具的东西。
            • 当然,在导航器的事件处理程序中,范围可以设置为组件,因此您可以通过这种方式获取它的状态和道具。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2018-04-07
            • 2017-02-06
            • 1970-01-01
            • 2018-05-21
            • 1970-01-01
            • 2020-05-10
            • 1970-01-01
            相关资源
            最近更新 更多