前言:这篇文章需要以下技术点:
- 对redux form的使用有一定的了解
- 熟练使用redux
关于Redux Form
Redux Form是我非常喜欢的一个表单组件,它关注与Form的State管理,可以很轻松的修改和获取表单状态,通过表单的状态能够快速的开发出各种各样的表单需求。
附上一张Redux Form的数据流图:
image.png
这是一个很常规的
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:
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
这个
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。












网友评论