react-native组件生命周期方法未在导航上触发

时间:2015-06-10 22:44:53

标签: reactjs react-native reactjs-native

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

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

组件A

只需设置导航和应用。

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在mount上从AsyncStorage读取,然后设置为state。

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总是直接是父和子,那么这将很有效,但在真实应用中,导航层次结构可能更深。

无论如何在导航事件之后触发组件上的功能,或者我缺少的其他东西?

5 个答案:

答案 0 :(得分:2)

和你一样陷入同样的​​问题。我希望这会改变,或者我们在组件上获得额外的事件来监听(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方法。

答案 1 :(得分:1)

我认为解决方案应该是AsyncStorage的包装,并可能使用“flux”架构。 https://github.com/facebook/flux在Dispatcher和Event Emitters的帮助下 - 与flux chat示例非常相似: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. 在这样的“change”事件处理程序中,组件的状态应设置为从存储中读取(读取属性)
  5. 最后但并非最不重要 - 我认为如果遵循react的模式,最好通过Dispatcher(通量的一部分)对存储进行更改。因此,而不是将“更改”方法直接调用到“域存储”的组件,它们会生成“操作”,然后“域”存储应该通过更新存储的值来处理操作(并因此触发更改事件)
  6. 这可能看起来像是一种矫枉过正,但它解决了许多问题(包括级联更新等等 - 当应用程序变大时会很明显 - 并且它引入了一些似乎有意义的合理抽象。你可以可能只是在没有引入调度员的情况下只有1-4点 - 应该仍然可以工作但稍后可以导致Facebook描述的问题(阅读flux文档了解更多细节)。

答案 2 :(得分:1)

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

return 0

在componentWillMount中:

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

在componentWillUnmount中:

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

2)架构问题也可以通过使用storage.removeListener('someValueChanged', this.onChanged); + redux,使用它的全局应用状态以及更改时自动重新呈现来修复。

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

BaseComponent

所以只需从该组件扩展并覆盖componentDidAppear方法(不要忘记OOP并调用内部的超级实现: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); } } )。

答案 3 :(得分:0)

如果您使用NavigatorIOS,则它可以将基础导航器传递给每个子组件。这有几个可以在这些组件中使用的事件:

  

onDidFocus功能

     

将使用每个场景的新路线进行调用   转换完成后或初始安装后

     

onItemRef功能

     

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

     

onWillFocus功能

     

安装时会发出目标路线   每次导航过渡之前

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

答案 4 :(得分:0)

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

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()来执行某些操作。