wd and cc

-- Good good study, day day up!

Howto Make react-redux Work With react-navigation

#React-Native #Redux #React-Redux #React-Navigation

这周花了一些时间研究 react-redux 和怎么让它和 react-navigation 配合一起工作,总结一下,把代码和注释直接贴这里了,也可以看这个 gist

  1/**
  2 * 一个简单的 RN 应用,有 2 个页面,使用了 react-navigation 的 StackNavigator 来做界面管理
  3 * 为了说明如何使用 redux,以及如何让 redux 和 StackNavigator 配合
  4 * 为了容易理解,把所有内容都放到了一个页面里面,实际开发的时候不要这么做
  5 * 参考:
  6 *  https://github.com/jackielii/simplest-redux-example
  7 *  http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_three_react-redux.html
  8 */
  9
 10import React, { Component } from 'react';
 11import {
 12    StyleSheet,
 13    Text,
 14    View,
 15    Button
 16} from 'react-native';
 17
 18import { Provider, connect } from 'react-redux';
 19import { createStore, combineReducers } from 'redux';
 20import { StackNavigator, addNavigationHelpers } from 'react-navigation';
 21
 22// Home 页面,UI 组件
 23class MyHome extends Component {
 24    constructor(props) {
 25        super(props);
 26        console.log('init home, props', props);
 27    }
 28
 29    _nextPage() {
 30        // navigation 依然在 this.props 里面获取,和不用 redux 的时候用法一样
 31        let {navigation} = this.props;
 32        navigation.navigate("App");
 33    }
 34
 35    render() {
 36        // 所有的传递过来的状态,都需要从 this.props.screenProps 里面读取 (4)
 37        // 我这里给不同页面的 action 取了各自的命名空间,避免冲突,也可以直接所有 action 都在一个命名空间,这块我还在摸索如何处理比较好 (5)
 38        let {onIncButtonClicked} = this.props.screenProps.MyAppActions;
 39
 40        // 界面有两个按钮,一个用来增加另外一个页面的计数器,一个用来访问下一个页面
 41        return (
 42            <View style={styles.container}>
 43                <Button title="Inc counter" onPress={onIncButtonClicked}></Button>
 44                <Button title="Next page" onPress={()=>this._nextPage()}></Button>
 45            </View>
 46        )
 47    }
 48}
 49
 50// 这个组件只是用来测试就算一个 props 传递给子组件,在 props 被修改的时候也会被自动刷新
 51class ShowText extends Component {
 52    render() {
 53        let {counter} = this.props;
 54
 55        return (
 56            <Text>{counter}</Text>
 57        )
 58    }
 59}
 60
 61// App 页面,UI 组件
 62class MyApp extends Component {
 63    constructor(props) {
 64        super(props);
 65        console.log('init App, props', props);
 66    }
 67
 68    componentWillReceiveProps(newProps) {
 69        console.log('myapp recive props', newProps);
 70    }
 71
 72    render() {
 73        // 组件的 state/props 获取,有自己的命名空间 (1)
 74        let {counter} = this.props.screenProps.MyApp;
 75        // 组件的 action props (5)
 76        let {onIncButtonClicked, onDecButtonClicked} = this.props.screenProps.MyAppActions;
 77
 78        // 界面有一个计数器的结果,两个按钮
 79        return (
 80            <View style={styles.container}>
 81                <ShowText counter={counter} />
 82                <Button title="Inc counter" onPress={onIncButtonClicked}></Button>
 83                <Button title="Dec counter" onPress={onDecButtonClicked}></Button>
 84            </View>
 85        )
 86    }
 87}
 88
 89// 初始化 StackNavigator,定义页面路由
 90let AppNavigator = StackNavigator({
 91    Home: {
 92        screen: MyHome
 93    },
 94    App: {
 95        screen: MyApp
 96    }
 97});
 98
 99// 包装一下 StackNavigator,因为有些参数需要定制一下
100class MyStackNavigator extends Component {
101    constructor(props) {
102        super(props);
103        console.log("inside MyStackNavigator", props);
104    }
105
106    render() {
107        // screenProps: 使用这个往所有的页面传递 props,这个是和直接使用 redux 不同的地方 (4)
108        // navigation: 因为使用 redux 之后,就不会直接操作 this.state 了,所以得告诉 StackNavigator dispatch 方法和 state 从哪里读取
109        return (
110            <AppNavigator
111                screenProps={this.props}
112                navigation={addNavigationHelpers({
113                    dispatch: this.props.dispatch, // 通过 action props 定义 (2)
114                    state: this.props.nav, // 通过 state props 定义 (3)
115                })} />
116        )
117    }
118}
119
120// 定义 state 和 props 的关系,所有 redux 应用都需要 (6)
121let mapStateToProps = (state, ownProps) => {
122    console.log("inside mapstate to props", state, ownProps);
123    return {
124        // 这两个是不同的命名空间,和上面你使用的时候的路径对应 (1)
125        "MyApp": state.MyApp,
126        "MyHome": state.MyHome,
127        // 定义 StackNavigator 的 state (3)
128        "nav": state.nav
129    }
130};
131
132// 定义 action 和 props 的关系,所有 redux 应用都需要
133let mapDispatchToProps = (dispatch, ownProps) => {
134    console.log("inside map dispath to props");
135    return {
136        // 这两个也是不同的命名空间,和上面使用的时候路径对应 (5)
137        'MyAppActions': {
138            onIncButtonClicked: () => {
139                let action = {
140                    type: "INC_COUNTER",
141                    payload: 1
142                };
143
144                dispatch(action);
145            },
146            onDecButtonClicked: () => {
147                let action = {
148                    type: "DEC_COUNTER",
149                    payload: -1
150                };
151
152                dispatch(action);
153            }
154        },
155        'MyHomeActions': {
156            onNextButtonClicked: () => {
157                let action = {
158                    type: "NEXT_PAGE"
159                };
160
161                dispatch(action);
162            }
163        },
164        // 定义 StackNavigator 的 action props (2)
165        'dispatch': dispatch
166    }
167}
168
169// 定义 home 页面的 reducer,不过因为那个页面唯一的一个 action 是触发别的页面的动作的,所以这个 reducer 其实也可以没有
170// 所以从这里也能看出来,reducer 并不一定按照页面去分
171let homeReducer = (state, action) => {
172    console.log("inside home reducer", state, action);
173    return state || {};
174};
175
176// 定义一个初始化的 state
177let myAppInitState = { 'counter': 10};
178// 定义 app 页面的 reducer
179let myAppReducer = (state = myAppInitState, action) => {
180    // 收到的 state 实际上只是自己命名空间下的 (6)
181    console.log("inside myAppReducer", state, action);
182    let myState = state;
183    // 需要处理的 action 的逻辑
184    // 要注意,一个 action 被触发的时候,所有的 reducer 都会被调用,所以其实更像是订阅自己想要处理的 action
185    switch (action.type) {
186        case "DEC_COUNTER":
187        case "INC_COUNTER":
188            // 如果修改了 state,必须要返回一个新的对象,不能直接在原对象上修改,否则 state 变化不会触发组件的刷新
189            return Object.assign({}, myState, {
190                'counter': myState.counter + action.payload
191            });
192        default:
193            return state;
194    }
195};
196
197// 定义一个 StackNavigator 用到的初始化状态,这个很重要
198const initialState = AppNavigator.router.getStateForAction(AppNavigator.router.getActionForPathAndParams('Home'));
199// 定义 StackNavigator 的 reducer,代码直接复制来的
200const navReducer = (state = initialState, action) => {
201    console.log("inside nav reducer", state, action);
202    const nextState = AppNavigator.router.getStateForAction(action, state);
203
204    // Simply return the original `state` if `nextState` is null or undefined.
205    return nextState || state;
206};
207
208// 创建 store
209let store = createStore(combineReducers({
210    // 这里的 MyApp 等和前面定义 mapStateToProps 的地方对应 (6)
211    // 这里也是导致 reducer 收到的 state 只有自己命名空间下数据的一个原因 (6)
212    MyApp: myAppReducer,
213    MyHome: homeReducer,
214    nav: navReducer
215}));
216
217// 让 redux 加持一下,保佑
218let App = connect(mapStateToProps, mapDispatchToProps)(MyStackNavigator);
219
220// 其他的就是比较常见的 redux 的逻辑了,另外需要说明的是实际使用的时候,肯定会做页面拆分,如何拆分可能都会有不同的看法,我也还在摸索
221export default class Root extends Component<{}> {
222    constructor(props) {
223        super(props);
224    }
225
226    render() {
227        return (
228            <Provider store={store}>
229                <App prop1="prop1" />
230            </Provider>
231        );
232    }
233}
234
235
236const styles = StyleSheet.create({
237    container: {
238        flex: 1,
239        justifyContent: 'center',
240        alignItems: 'center',
241        backgroundColor: '#F5FCFF',
242    },
243    welcome: {
244        fontSize: 20,
245        textAlign: 'center',
246        margin: 10,
247    },
248    instructions: {
249        textAlign: 'center',
250        color: '#333333',
251        marginBottom: 5,
252    },
253});
comments powered by Disqus