1. React Native+React Navigation+Redux开发实用教程

              Posted by 本的秘密博客 on March 30, 2019

              期待已久的新教程上线啦!解锁React Native开发新姿势,一网打尽React Native最新与最热技术,点我Get!!!

              React Native+Redux开发实用教程

              为了帮助大家快速上手在React Native与Redux开发,在这本文中将向大家介绍如何在React Native中使用Redux?,以及一些必备基础以及高级知识

              本参考了《新版React Native+Redux打造高质量上线App》课程的部分讲解,更多关于React Native与Redux的实战技巧可在《新版React Native+Redux打造高质量上线App》中查看。

              那么如何在React Native中使用Redux和react-navigation组合?呢?

              在使用 React Navigation 的项目中,想要集成 redux 就必须要引入 react-navigation-redux-helpers 这个库。

              第一步:安装react-navigation-redux-helpers

              npm install --save react-navigation-redux-helpers
              

              第二步:配置Navigation

              import React from 'react';
              import {createStackNavigator, createSwitchNavigator} from 'react-navigation';
              import {connect} from 'react-redux';
              import {createReactNavigationReduxMiddleware, reduxifyNavigator} from 'react-navigation-redux-helpers';
              
              export const rootCom = 'Init';//设置根路由
              
              export const RootNavigator = createSwitchNavigator({
                 ...
              });
              
              /**
               * 1.初始化react-navigation与redux的中间件,
               * 该方法的一个很大的作用就是为reduxifyNavigator的key设置actionSubscribers(行为订阅者)
               * 设置订阅者@https://github.com/react-navigation/react-navigation-redux-helpers/blob/master/src/middleware.js#L29
               * 检测订阅者是否存在@https://github.com/react-navigation/react-navigation-redux-helpers/blob/master/src/middleware.js#L97
               * @type {Middleware}
               */
              export const middleware = createReactNavigationReduxMiddleware(
                  'root',
                  state => state.nav
              );
               
              /**
               * 2.将根导航器组件传递给 reduxifyNavigator 函数,
               * 并返回一个将navigation state 和 dispatch 函数作为 props的新组件;
               * 注意:要在createReactNavigationReduxMiddleware之后执行
               */
              const AppWithNavigationState = reduxifyNavigator(RootNavigator, 'root');
              
              /**
               * State到Props的映射关系
               * @param state
               */
              const mapStateToProps = state => ({
                  state: state.nav,//v2
              });
              /**
               * 3.连接 React 组件与 Redux store
               */
              export default connect(mapStateToProps)(AppWithNavigationState);
              

              提示:createReactNavigationReduxMiddleware方法要放到reduxifyNavigator之前执行否则会报错,以上代码片段的完整部分可以在课程源码中查找。

              第二步:配置Reducer

              import {combineReducers} from 'redux'
              import theme from './theme'
              import {rootCom, RootNavigator} from '../navigator/AppNavigators';
              
              //1.指定默认state
              const navState = RootNavigator.router.getStateForAction(RootNavigator.router.getActionForPathAndParams(rootCom));
              
              /**
               * 2.创建自己的 navigation reducer,
               */
              const navReducer = (state = navState, action) => {
                  const nextState = RootNavigator.router.getStateForAction(action, state);
                  // 如果`nextState`为null或未定义,只需返回原始`state`
                  return nextState || state;
              };
              
              /**
               * 3.合并reducer
               * @type {Reducer<any> | Reducer<any, AnyAction>}
               */
              const index = combineReducers({
                  nav: navReducer,
                  theme: theme,
              });
              
              export default index;
              

              以上代码片段的完整部分可以在课程源码中查找。

              第三步:配置store

              import {applyMiddleware, createStore} from 'redux'
              import thunk from 'redux-thunk'
              import reducers from '../reducer'
              import {middleware} from '../navigator/AppNavigators'
              
              const middlewares = [
                  middleware,
              ];
              /**
               * 创建store
               */
              export default createStore(reducers, applyMiddleware(...middlewares));
              

              以上代码片段的完整部分可以在课程源码中查找。

              第四步:在组件中应用

              import React, {Component} from 'react';
              import {Provider} from 'react-redux';
              import AppNavigator from './navigator/AppNavigators';
              import store from './store'
              
              type Props = {};
              export default class App extends Component<Props> {
                  render() {
                      /**
                       * 将store传递给App框架
                       */
                      return <Provider store={store}>
                          <AppNavigator/>
                      </Provider>
                  }
              }
              

              以上代码片段的完整部分可以在课程源码中查找。

              经过上述4步呢,我们已经完成了react-navigaton+redux的集成,那么如何使用它呢?

              使用react-navigaton+redux

              1.订阅state

              import React from 'react';
              import {connect} from 'react-redux';
              
              class TabBarComponent extends React.Component {
                  render() {
                      return (
                          <BottomTabBar
                              {...this.props}
                              activeTintColor={this.props.theme}
                          />
                      );
                  }
              }
              
              const mapStateToProps = state => ({
                  theme: state.theme.theme,
              });
              
              export default connect(mapStateToProps)(TabBarComponent);
              

              以上代码片段的完整部分可以在课程源码中查找。

              在上述代码中我们订阅了store中的theme state,然后该组件就可以通过this.props.theme获取到所订阅的theme state了。

              2.触发action改变state

              import React, {Component} from 'react';
              import {connect} from 'react-redux'
              import {onThemeChange} from '../action/theme'
              import {StyleSheet, Text, View, Button} from 'react-native';
              
              type Props = {};
              class FavoritePage extends Component<Props> {
                  render() {
                      return (
                          <View style={styles.container}>
                              <Text style={styles.welcome}>FavoritePage</Text>
                              <Button
                                  title="改变主题色"
                                  onPress={() => {
                                      // let {dispatch} = this.props.navigation;
                                      // dispatch(onThemeChange('red'))
                                      this.props.onThemeChange('#096');
                                  }} 
                              />
                          </View>
                      );
                  }
              }
              
              const mapStateToProps = state => ({});
              
              const mapDispatchToProps = dispatch => ({
                  onThemeChange: (theme) => dispatch(onThemeChange(theme)),
              });
              export default connect(mapStateToProps, mapDispatchToProps)(FavoritePage);
              ...
              

              以上代码片段的完整部分可以在课程源码中查找。

              触发action有两种方式:

              • 一种是通过mapDispatchToProps将dispatch创建函数和props绑定,这样就可以通过this.props.onThemeChange('#096')调用这个dispatch创建函数来触发onThemeChange action了;
              • 另外一种方式是通过this.props中的navigation来获取dispatch,然后通过这个dispatch手动触发一个action:

                  let {dispatch} = this.props.navigation;
                  dispatch(onThemeChange('red'))
                

                以上代码片段的完整部分可以在课程源码中查找。

              在Redux+react-navigation场景中处理 Android 中的物理返回键

              在Redux+react-navigation场景中处理Android的物理返回键需要注意当前路由的所以位置,然后根据指定路由的索引位置来进行操作,这里需要用到BackHandler

              import React, {Component} from 'react';
              import {BackHandler} from "react-native";
              import {NavigationActions} from "react-navigation";
              import {connect} from 'react-redux';
              import DynamicTabNavigator from '../navigator/DynamicTabNavigator';
              import NavigatorUtil from '../navigator/NavigatorUtil';
              
              type Props = {};
              
              class HomePage extends Component<Props> {
                  componentDidMount() {
                      BackHandler.addEventListener("hardwareBackPress", this.onBackPress);
                  }
              
                  componentWillUnmount() {
                      BackHandler.removeEventListener("hardwareBackPress", this.onBackPress);
                  }
              
                  /**
                   * 处理 Android 中的物理返回键
                   * https://reactnavigation.org/docs/en/redux-integration.html#handling-the-hardware-back-button-in-android
                   * @returns {boolean}
                   */
                  onBackPress = () => {
                      const {dispatch, nav} = this.props;
                      //if (nav.index === 0) {
                      if (nav.routes[1].index === 0) {//如果RootNavigator中的MainNavigator的index为0,则不处理返回事件
                          return false;
                      }
                      dispatch(NavigationActions.back());
                      return true;
                  };
              
                  render() {
                      return <DynamicTabNavigator/>;
                  }
              }
              
              const mapStateToProps = state => ({
                  nav: state.nav,
              });
              
              export default connect(mapStateToProps)(HomePage);
              

              以上代码片段的完整部分可以在课程源码中查找。

              2end

              API

              combineReducers(reducers)

              随着应用变得越来越复杂,可以考虑将 reducer 函数 拆分成多个单独的函数,拆分后的每个函数负责独立管理 state 的一部分。

              函数原型:combineReducers(reducers)

              • 参数:reducers (Object): 一个对象,它的值(value)对应不同的 reducer 函数,这些 reducer 函数后面会被合并成一个。下面会介绍传入 reducer 函数需要满足的规则。

              • 每个传入 combineReducers 的 reducer 都需满足以下规则:

                • 所有未匹配到的 action,必须把它接收到的第一个参数也就是那个 state 原封不动返回。
                • 永远不能返回 undefined。当过早 return 时非常容易犯这个错误,为了避免错误扩散,遇到这种情况时 combineReducers 会抛异常。
                • 如果传入的 state 就是 undefined,一定要返回对应 reducer 的初始 state。根据上一条规则,初始 state 禁止使用 undefined。使用 ES6 的默认参数值语法来设置初始 state 很容易,但你也可以手动检查第一个参数是否为 undefined。

              返回值

              (Function):一个调用 reducers 对象里所有 reducer 的 reducer,并且构造一个与 reducers 对象结构相同的 state 对象。

              combineReducers 辅助函数的作用是,把一个由多个不同 reducer 函数作为 value 的 object,合并成一个最终的 reducer 函数,然后就可以对这个 reducer 调用 createStore 方法。

              合并后的 reducer 可以调用各个子 reducer,并把它们返回的结果合并成一个 state 对象。 由 combineReducers() 返回的 state 对象,会将传入的每个 reducer 返回的 state 按其传递给 combineReducers() 时对应的 key 进行命名。

              提示:在 reducer 层级的任何一级都可以调用 combineReducers。并不是一定要在最外层。实际上,你可以把一些复杂的子 reducer 拆分成单独的孙子级 reducer,甚至更多层。

              createStore

              函数原型:createStore(reducer, [preloadedState], enhancer)

              参数

              • reducer (Function)::项目的根reducer。
              • [preloadedState] (any):这个参数是可选的, 用于设置 state 初始状态。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。
              • enhancer (Function): Store enhancer 是一个组合 store creator 的高阶函数,返回一个新的强化过的 store creator。这与 middleware 相似,它也允许你通过复合函数改变 store 接口。

              返回值

              • (Store): 保存了应用所有 state 的对象。改变 state 的惟一方法是 dispatch action。你也可以 subscribe 监听 state 的变化,然后更新 UI。

              示例

              import { createStore } from 'redux'
              
              function todos(state = [], action) {
                switch (action.type) {
                  case 'ADD_TODO':
                    return state.concat([action.text])
                  default:
                    return state
                }
              }
              
              let store = createStore(todos, ['Use Redux'])
              
              store.dispatch({
                type: 'ADD_TODO',
                text: 'Read the docs'
              })
              
              console.log(store.getState())
              // [ 'Use Redux', 'Read the docs' ]
              

              以上代码片段的完整部分可以在课程源码中查找。

              注意事项

              • 应用中不要创建多个 store!相反,使用 combineReducers 来把多个 reducer 创建成一个根 reducer。
              • 你可以决定 state 的格式。你可以使用普通对象或者 Immutable 这类的实现。如果你不知道如何做,刚开始可以使用普通对象。
              • 如果 state 是普通对象,永远不要修改它!比如,reducer 里不要使用 Object.assign(state, newData),应该使用 Object.assign({}, state, newData)。这样才不会覆盖旧的 state。如果可以的话,也可以使用 对象拓展操作符(object spread spread operator 特性中的 return { …state, …newData }。
              • 对于服务端运行的同构应用,为每一个请求创建一个 store 实例,以此让 store 相隔离。dispatch 一系列请求数据的 action 到 store 实例上,等待请求完成后再在服务端渲染应用。
              • 当 store 创建后,Redux 会 dispatch 一个 action 到 reducer 上,来用初始的 state 来填充 store。你不需要处理这个 action。但要记住,如果第一个参数也就是传入的 state 是 undefined 的话,reducer 应该返回初始的 state 值。
              • 要使用多个 store 增强器的时候,你可能需要使用 compose

              applyMiddleware

              函数原型:applyMiddleware(...middleware)

              使用包含自定义功能的 middleware 来扩展 Redux。

              技巧

              • react-navigation+redux;
              • 如何防止重复创建实例:
                • 方式一:单例+Map+工厂;
                • 方式二:页面保存实例变量,传递给,Action使用;
                • 方式三:在action中创建实例;
              • 如何动态的设置store,和动态获取store(难点:storekey不固定);
              • 如何实现可取消的redux action:可参考SearchPage的设计;

              上述的实战技巧可在新版React Native+Redux打造高质量上线App中获取;

              问答

              • Redux是如何实现JS的可预测状态的管理?
                • 单一数据源;
                • 所有数据都是只读的,要想修改数据,必须 dispatch 一个 action 来描述什么发生了改变;
                • 当处理 action 时,必须生成一个新的 state,不得直接修改原始对象;
              • Redux的核心思想是什么?
                • 单向数据流的是Redux架构的设计核心;

              如何做到从不直接修改 state ?

              从不直接修改 state 是 Redux 的核心理念之一:为实现这一理念,可以通过一下两种方式:

              1.通过[Object.assign()](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)创建对象拷贝, 而拷贝中会包含新创建或更新过的属性值

              在下面的 todoApp 示例中, Object.assign() 将会返回一个新的 state 对象, 而其中的 visibilityFilter 属性被更新了:

              function todoApp(state = initialState, action) {
                switch (action.type) {
                  case SET_VISIBILITY_FILTER:
                    return Object.assign({}, state, {
                      visibilityFilter: action.filter
                    })
                  default:
                    return state
                }
              }
              

              以上代码片段的完整部分可以在课程源码中查找。

              2. 通过通过ES7的新特性[对象展开运算符(Object Spread Operator)](http://cn.redux.js.org/docs/recipes/UsingObjectSpreadOperator.html)

              function todoApp(state = initialState, action) {
                switch (action.type) {
                  case SET_VISIBILITY_FILTER:
                    return { ...state, visibilityFilter: action.filter }
                  default:
                    return state
                }
              }
              

              以上代码片段的完整部分可以在课程源码中查找。

              这样你就能轻松的跳回到这个对象之前的某个状态(想象一个撤销功能)。

              总结

              • Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store;
              • redux一个特点是:状态共享,所有的状态都放在一个store中,任何component都可以订阅store中的数据;
              • 并不是所有的state都适合放在store中,这样会让store变得非常庞大,如某个状态只被一个组件使用,不存在状态共享,可以不放在store中;

              未完待续

              参考



              16kou fushipifaw 1sysb 155217 293182 618632 229187 266793 782276 177152 598613 536683 658753 218325 896853 596113 675327 021sh6 278857 0551tt 0752cy 022ct ahcnjt 52lpw