从Redux Form学习reusing-reducer-log
前言:这篇文章需要以下技术点:
- 对redux form的使用有一定的了解
- 熟练使用redux
关于Redux Form
Redux Form
是我非常喜欢的一个表单组件,它关注与Form
的State
管理,可以很轻松的修改和获取表单状态,通过表单的状态能够快速的开发出各种各样的表单需求。
附上一张Redux Form的数据流图:
这是一个很常规的
Redux
数据流图,从图中最底部可以看出,使用reduxForm
修饰的Component
可以获取State
中指定的字段,也可以通过startSubmit
、change
等一些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 Form
在combineReducers
里只注册了一个formReducer
,并且使用reduxForm
装饰器中配置时,name
字段都是自定义的,不可能Redux Form
预先知道我们有哪些表单,所以Redux Form
的reducer
内部一定对name
做了逻辑划分,而且这种逻辑划分还是动态的。
Redux Form如何Reusing Reducer Logic
action流程:
在使用Redux Form的过程中,我们经常会触发以下actions
:
红框框中是一个表单常见的生命周期,从
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:
这个
action
的meta
字段中不仅有form
字段,还有要改变的field
字段,在payload
里有修改后的值,formreducer
处理这个action
后的state diff
为:image.png
在values里面新增了一个
newMessageCode
字段,值为5
。当
DESTROY
时action
的结构为: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 Form
的action
,则不处理这个action
。
2、如果是DESTROY
的action
,则在state
中删除这个formState
。
3、其余的action
的处理步骤为:
(1)从state
中获取form
的formState
(getIn
)。
(2)使用reducer
处理formState
和action
(reducer
)。
(3)使用reducer
对formState
的处理结果result
来更新state
(setIn
)。
因此可以清楚的知道,byForm(reducer)
返回的reducer
是用来管理应用中所有的表单状态(state
),state
是一个key-value
形式,key
为form
唯一标识符,value
为formState
(单个表单的状态),然后将 formState
和action
传给const reducer = (state: any = empty, action: Action)
做处理。
const reducer = (state: any = empty, action: Action)
函数很简单,它就是通过action.type
在behaviors
中查找是否定义了该action
的处理方法,如果定义了则用behavior(formState, action)
去处理获取新的formState
,如果未定义则返回之前的formState
。
behavior
中也是一个key-value
形式, key
为Action Type
,value
为reducer
,这个reducer
的逻辑只是用来管理单个表单的。
然后再通过在外部定义一些Help Function
,例如管理所有表单状态的getIn
,setin
,empty
,deleteIn
等函数和数据定义在一个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
中只要简单的调用getIn
和setIn
就可以获取和修改state
数据。
总结
通过以上对Reusing Reducer Logic
的两种方法的学习,两种复用的方法各有优缺点。
前一种复用写法通过对action
加点盐来区别不同业务的相同的reducer
组件,并且要在combineReducers
中为一个组件注册多次,来区分不同的业务数据。
第二中复用写法也对action
加了盐,但是使用这种写法的组件不需要在combineReducers
中多次注册,只要注册一次即可,它内部会对用来区分业务的唯一标识符,例如Redux Form
的action.meta.form
进行隔离处理。
所以在组件的使用上,第二种会舒适些,但是在组件的开发上第一种会简单些,因为第二种复用要自己管理组件的所有state
,并且隔离不同业务之间的state
。