本系列文章学习和研究redux-form的主要参考资源,特别是代码部分,主要在于其官方网站提供的一切资源及工程源码剖析。
基础
在使用 redux-form 之前,需要用户具备以下基础:
- HTML5+CSS3
- ES6+高阶组件函数式编程思想+React
-
Redux及React-redux
redux-form核心模块
redux-form基于React-Redux状态管理理念,而form本身作为一种「特殊」的容器组件,要实现这种组件与数据中心(即Redux的store)的交互,关键在于把握 redux-form 的三个主要模块:
- formReducer reducer : 表单的各种操作以 Redux action 的方式,通过此 reducer 来促成 Redux store 数据的变化。
- reduxForm() API :此高阶组件用以整合 Redux action 绑定的用户交互与您的组件,并返回一个新的组件供以使用。
- <Field/> API: 用此代替您原生态的HTML5 <input/> 组件,可以与redux-form的逻辑相连接。
补充解释如下。
(一)关于redux-form的reducer
有关代码如下(store.js):
import { reducer as reduxFormReducer } from 'redux-form';
const rootReducer = combineReducers({
//other custom reducers
form: reduxFormReducer, // mounted under "form"
});
combineReducers工具函数组件各个子reducer,最后形成一个大型reducer,称为rootReducer。然后,以此rootReducer为参数传递给createStore创建Redux的store对象。
Reducer的作用是:负责根据子组件发出的Action和原有State生成新的State,即:
Reducer的作用 |
oldState+Action=>newState |
为了全面理解上面的代码,让我们再回顾一下Redux编程中Reducer拆分思想。Redux编程思想中建议把较大型的超过20行以上代码的reducer函数拆成了若干个小型的recducer函数,每一个负责生成对应的一部分state。而且,这种拆分与 React 应用的结构相吻合:一个 React 根组件由很多子组件构成。这就是说,子组件与子 Reducer 完全可以一一对应。
Redux 提供的combineReducers方法,正是用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,即可将它们合并成一个大的 Reducer。
典型情况下,上述combineReducers方法用场是:
生成rootReducer的模块中。因为往往随后使用rootReducer作为参数并通过调用createStore方法生成整个系统唯一的store,所以,combineReducers方法主要store.js模块中。
关于combineReducers方法还有一个重要细节,请注意如下代码:
const rootReducer = combineReducers({
chatLogReducer,
statusMessageReducer,
userNameReducer,
form: reduxFormReducer
});
这种写法有一个前提: State 的属性名必须与各个子 Reducer 同名;否则,就要采用下面的写法。
const rootReducer = combineReducers({
chatLog:chatLogReducer,
statusMessage:statusMessageReducer,
userName:userNameReducer,
form: reduxFormReducer
});
因此,该函数根据 State 的 不同key 去执行相应的子 Reducer,并将返回结果合并成一个大的 State 对象。注意:最后一个form键名是不能更改的,这是redux-form系统规定的。
将来使用上面代码的结果的方法是:
state.chatLog、state.statusMessage......
当然,由于一个典型的reducer是要返回一个新的状态——一般使用对象方式表示;所以,最终你会见到代码中出现类似于下面的引用形式:
state.chatLog.obj1.p1
这一点在redux-form官方网站提供的实例InitializeFromStateForm中文件InitializeFromStateForm.js中即有如下使用方式:
InitializeFromStateForm = connect(
state => ({
initialValues: state.accountReducer.data,
}),
{ load: loadAccount },
)(InitializeFromStateForm);
上面代码行出现在示例表单定义模块的代码中,这里通过connect方法调用进一步包装表单组件,实现把store中state有关数据(state.accountReducer.data)映射到表单组件的属性上。
啰嗦上面这一些,就是为了强调一个简短的combineReducers调用意义重大,正是这一调用最终把store数据(代码中典型称为state)与组件props关联到一起。
(二)关于reduxForm()方法
API部分的描述是:reduxForm(config:Object)
这里,参数config是一个对象类型。此对象规定了系统内置的许多key,分别实现不同的功能。此高阶组件方法用以整合 Redux action绑定的用户交互与您的组件,并返回一个新的组件供以使用。
上面示例中的代码用法如下:
InitializeFromStateForm = reduxForm({
form: 'initializeFromState', // a unique identifier for this form
})(InitializeFromStateForm);
InitializeFromStateForm = connect(
state => ({
initialValues: state.accountReducer.data, // pull initial values from account reducer
}),
{ load: loadAccount }, // bind account loading action creator
)(InitializeFromStateForm);
上面reduxForm方法调用至少有两层含义:
(1)此方法进一步封装上面定义的表单组件,从而实现组成表单的UI组件的属性(props)(包括表单具体定义中的各个内置属性与接下来通过connect创建的少数定制属性,例如load)与store中的数据(即state,这个state中的对应形式可能是对象也可能是函数)关联到一起。
(2)此方法返回一个新的表单组件,提供给index.js中ReactDOM.render方法最终渲染网页中的表单使用,有关代码如下:
ReactDOM.render(
<Provider store={store}>
<div style={{ padding: 15 }}>
<h3>Initialize From State</h3>
<InitializeFromStateForm onSubmit={showResults} />
<Values form="initializeFromState" />
</div>
</Provider>,
rootEl
);
(三)关于<Field/>组件
所有需要与 store 数据连接的表单组件,都可以用 <Field/>。在正确使用它之前,需要清楚三条基本概念:
- 必须包含 name 属性。可以是简单的字符串,如 userName、password,也可以是复杂的结构,如 contact.billing.address[2].phones[1].areaCode。
* 必须包含 component 属性。可以是一个组件、无状态组件或者DOM所支持的默认的标签(input、textarea、select)。
- 其他所有属性会通过prop传递到元素生成器中,如 className。
使用方法列举如下:
1.组件
可以是任何自定义的 class 组件(如下面的MyCustomInput组件),或者其他第三方库。
/```
/ MyCustomInput.js
import React, { Component } from 'react'
class MyCustomInput extends Component {
render() {
const { input: { value, onChange } } = this.props
return (
<div>
<span>The current value is {value}.</span>
<button type="button" onClick={() => onChange(value + 1)}>Inc</button>
<button type="button" onClick={() => onChange(value - 1)}>Dec</button>
</div>
)
}
}
然后这样使用:
import MyCustomInput from './MyCustomInput'
//...
<Field name="myField" component={MyCustomInput}/>
### 2.无状态组件
这是一个非常灵活的使用 <Field/> 的方法。你必须在你的 render() 方法外定义它,否则它每次渲染都会被重建,并且由于组件的 prop 会变,就会强制 <Field/> 进行渲染。如果你在 render() 内部定义无状态组件,不但会拖慢你的程序的运行,而且组件的input每次都会在组件重新渲染的时候失去焦点。
//在方法 render() 外定义
const renderField = (field) => (
<div className="input-row">
<input {...field.input} type="text"/>
{field.meta.touched && field.meta.error &&
<span className="error">{field.meta.error}</span>}
</div>
)
//在render()方法内定义
<Field name="myField" component={renderField}/>
### 3.最简单且最常用的形式: input, select, or textarea
比如创建一个文字输入框组件
<Field component="input" type="text"/>
# 参考
(1)https://github.com/tedyuen/react-redux-form-v6-example#field-value-lifecycle
(2)http://www.ruanyifeng.com/blog/2016/09/redux_tutorial_part_one_basic_usages.html
(3)https://redux-form.com/7.4.2/docs/gettingstarted.md/