React Native开发React.js

从Redux Form学习reusing-reducer-log

2018-09-19  本文已影响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

上一篇下一篇

猜你喜欢

热点阅读