美文网首页React Native开发React.js
从Redux Form学习reusing-reducer-log

从Redux Form学习reusing-reducer-log

作者: 羽纱 | 来源:发表于2018-09-19 11:20 被阅读12次

    前言:这篇文章需要以下技术点:

    关于Redux Form

    Redux Form是我非常喜欢的一个表单组件,它关注与FormState管理,可以很轻松的修改和获取表单状态,通过表单的状态能够快速的开发出各种各样的表单需求。
    附上一张Redux Form的数据流图:

    image.png
    这是一个很常规的Redux数据流图,从图中最底部可以看出,使用reduxForm修饰的Component可以获取State中指定的字段,也可以通过startSubmitchange等一些Action Creators来创建actions并派发出去,这些由Redux Form派发的Action通过formRducer处理后获的新的state,然后表现在reduxForm修饰的组件中。
    关于Redux Form的用法,刚兴趣的朋友可以查阅官方文档:https://redux-form.com/6.8.0/docs/gettingstarted.md/

    从Redux Form一窥Redux state container

    由上对Redux Form的分析,我们可以猜出Redux Form的两个重要的基本配置,一个是在reducers中配置Redux Form提供的formReducer,一个是用reduxForm修饰我们写的表单。
    因为项目中存在多个表单,因此我们会多次使用reduxForm修饰我们写的表单,可想而知,每个表单的状态都是相互独立,互不影响的,它们会派发出处理自己表单状态的action,当自己表单状态改变时,它们的组件也要更新。但是项目中虽然存在多个表单,多个不同的表单状态,但却只有一个formReducer在处理所有的表单的action,因此formReducer是一个被复用的表单逻辑(reusing-reducer-logic)。
    reusing-reducer-logic是使用redux写组件常用的技术。
    reusing-reducer-logic的官方文档:https://redux.js.org/recipes/structuring-reducers/reusing-reducer-logic

    复用表单逻辑(Reusing Reducer Logic)

    Or, you may want to have multiple "instances" of a certain type of data being handled in the store.
    官方写的Reusing Reducer Login类似于这样:

    function createFilteredReducer(reducerFunction, reducerPredicate) {
        return (state, action) => {
            const isInitializationCall = state === undefined;
            const shouldRunWrappedReducer = reducerPredicate(action) || isInitializationCall;
            return shouldRunWrappedReducer ? reducerFunction(state, action) : state;
        }
    }
     
    const rootReducer = combineReducers({
        // check for suffixed strings
        counterA : createFilteredReducer(counter, action => action.type.endsWith('_A')),
        // check for extra data in the action
        counterB : createFilteredReducer(counter, action => action.name === 'B'),
        // respond to all 'INCREMENT' actions, but never 'DECREMENT'
        counterC : createFilteredReducer(counter, action => action.type === 'INCREMENT')
    };
    

    使用higher-order reducer,来重用reducer,并且通过reducerPredicate来控制只有action满足一定条件时才触发reduer方法。
    这样虽然满足了重用reducer,但是每一次重用,都得在combineReducers里面注册使用了createFiltereReducer工厂方法创建的新的reducer。而Redux FormcombineReducers里只注册了一个formReducer,并且使用reduxForm装饰器中配置时,name字段都是自定义的,不可能Redux Form预先知道我们有哪些表单,所以Redux Formreducer内部一定对name做了逻辑划分,而且这种逻辑划分还是动态的。

    Redux Form如何Reusing Reducer Logic

    action流程:
    在使用Redux Form的过程中,我们经常会触发以下actions

    image.png
    红框框中是一个表单常见的生命周期,从INITIALIZE -> REGISTER_FIELD -> CHANGE -> ... -> DESTROY,可以从右边的框框中可以看到action的构成,第一个字段是常规字段type,可以看出@@redux-form/是所有Redux Form的ActionType的前缀,可以看做命名空间,用来避免与其他Action产生冲突。
    上图右边,INITIALIZE了一个新的form,标识符为:replaceMobile,它引发的state diff如下图所示:
    image.png
    可以看出在form的state下新增了一个字段replaceMobile,结构是:
    {
      values: {flag: true},
      initial: {flag: true},
    }
    

    当我们修改一个字段值时引发了CHANGE的Action:

    image.png
    这个actionmeta字段中不仅有form字段,还有要改变的field字段,在payload里有修改后的值,formreducer处理这个action后的state diff为:
    image.png
    在values里面新增了一个newMessageCode字段,值为5
    DESTROYaction的结构为:
    image.png
    state diff为:
    image.png
    如何使用redux构建组件
    从上面的流程可以看出,formReducer有动态增删form,以及修改form状态,修改form中指定的field的状态的能力。那它是如何做到的?
    createReducer:https://github.com/erikras/redux-form/blob/master/src/createReducer.js
    相关代码:
    function createReducer<M, L>(structure: Structure<M, L>) {
    ...
      const behaviors : | { [string]:  {  (state: any,  action: Action):  M  }  }  =  { 
        [ARRAY_INSERT]( 
          state, 
          { 
            meta: {  field,  index  },
            payload
          }
          ){ 
              return arraySplice(state,  field,  index, 0,  payload) |
          })
        ...
      }
      const reducer =  (state: any =  empty, action: Action) => { 
        const behavior  =  behaviors[action.type] 
        return behavior ? behavior(state,  action)  :  state 
      }
      const byForm = reducer => (
        state: any = empty,
        action: Action = { type: 'NONE' }
      ) => {
        const form = action && action.meta && action.meta.form
        if (!form || !isReduxFormAction(action)) {
          return state
        }
        if (action.type === DESTROY && action.meta && action.meta.form) {
          return action.meta.form.reduce(
            (result, form) => deleteInWithCleanUp(result, form),
            state
          )
        }
        const formState = getIn(state, form)
        const result = reducer(formState, action)
        return result === formState ? state : setIn(state, form, result)
      }
    ...
      return decorate(byForm(reducer))
    }
    

    我们从下往上看,先看byForm(reducer)byForm(reducer)在传入reducer后返回了一个新的reducer结构,我们先称呼它为newReducer
    newReducer的执行流程如下:
    1、返回的newReducer会去action中获取放在meta中的form标识符,如果form不存在或者action不是Redux Formaction,则不处理这个action
    2、如果是DESTROYaction,则在state中删除这个formState
    3、其余的action的处理步骤为:
    (1)从state中获取formformStategetIn)。
    (2)使用reducer处理formStateactionreducer)。
    (3)使用reducerformState的处理结果result来更新statesetIn)。

    因此可以清楚的知道,byForm(reducer)返回的reducer是用来管理应用中所有的表单状态(state),state是一个key-value形式,keyform唯一标识符,valueformState(单个表单的状态),然后将 formStateaction传给const reducer = (state: any = empty, action: Action)做处理。

    const reducer = (state: any = empty, action: Action)函数很简单,它就是通过action.typebehaviors中查找是否定义了该action的处理方法,如果定义了则用behavior(formState, action)去处理获取新的formState,如果未定义则返回之前的formState

    behavior中也是一个key-value形式, keyAction Typevaluereducer,这个reducer的逻辑只是用来管理单个表单的。

    然后再通过在外部定义一些Help Function,例如管理所有表单状态的getInsetinemptydeleteIn等函数和数据定义在一个structure内:
    https://github.com/erikras/redux-form/blob/master/src/structure/plain/index.js

    import splice from './splice'
    import getIn from './getIn'
    import setIn from './setIn'
    import deepEqual from './deepEqual'
    import deleteIn from './deleteIn'
    import keys from './keys'
    import type { Structure } from '../../types'
    
    const structure: Structure<Object, Array<*>> = {
      allowsArrayErrors: true,
      empty: {},
      emptyList: [],
      getIn,
      setIn,
      deepEqual,
      deleteIn,
      forEach: (items, callback) => items.forEach(callback),
      fromJS: value => value,
      keys,
      size: array => (array ? array.length : 0),
      some: (items, callback) => items.some(callback),
      splice,
      toJS: value => value
    }
    
    export default structure
    

    可以让上部分的newReducer结构更加的清晰简单,因为把对state的数据操作都封装起来了,在newReducer中只要简单的调用getInsetIn就可以获取和修改state数据。

    总结

    通过以上对Reusing Reducer Logic的两种方法的学习,两种复用的方法各有优缺点。
    前一种复用写法通过对action加点来区别不同业务的相同的reducer组件,并且要在combineReducers中为一个组件注册多次,来区分不同的业务数据。
    第二中复用写法也对action加了,但是使用这种写法的组件不需要在combineReducers中多次注册,只要注册一次即可,它内部会对用来区分业务的唯一标识符,例如Redux Formaction.meta.form进行隔离处理。
    所以在组件的使用上,第二种会舒适些,但是在组件的开发上第一种会简单些,因为第二种复用要自己管理组件的所有state,并且隔离不同业务之间的state

    相关文章

      网友评论

        本文标题:从Redux Form学习reusing-reducer-log

        本文链接:https://www.haomeiwen.com/subject/glpckftx.html