6

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

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

组分 A

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 在挂载时从 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 始终是直接的父子关系,那将很有效,但在真正的应用程序中,导航层次结构可能会更深。

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

4

5 回答 5

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 方法。

于 2015-06-24T03:48:02.027 回答
1

我认为解决方案应该是围绕 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 描述的问题(阅读通量文档了解更多详细信息)。

于 2015-06-11T21:38:57.007 回答
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()

于 2016-12-21T20:50:58.040 回答
0

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

onDidFocus 函数

过渡完成后或初始挂载后,将使用每个场景的新路由调用

onItemRef 函数

当场景 ref 发生变化时,将使用 (ref, indexInStack, route) 调用

onWillFocus 函数

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

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

于 2015-06-11T13:01:30.750 回答
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()你的组件来做一些事情。

于 2015-11-27T06:19:09.807 回答