本系列文章学习和研究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);
(1)此方法进一步封装上面定义的表单组件,从而实现组成表单的UI组件的属性(props)(包括表单具体定义中的各个内置属性与接下来通过connect创建的少数定制属性,例如load)与store中的数据(即state,这个state中的对应形式可能是对象也可能是函数)关联到一起。
(2)此方法返回一个新的表单组件,提供给index.js中ReactDOM.render方法最终渲染网页中的表单使用,有关代码如下:
ReactDOM.render( <Provider store={store}> <div style={{ padding: 15 }}> <h2>Initialize From State</h2> <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/