在react中, 如果不使用双向绑定, 那么获取一个受控制的input的数据需要写一个onChange回调函数来获取输入值, 再通过setState来将value传入input做展示. 少量的输入框的情况下, 还可以使用这种方法, 但是如果在比较大和复杂的表单中, 这种写法就会觉得太麻烦了.
旧版本的valueLink 在以前的版本中, 可以通过mixin:[LinkedStateMixin]这种方法来实现简易的双向绑定, 它的底层方法是react为input提供的valueLink属性, 可以通过传递一个含有value和requestChange属性的对象, 来实现value和onChange的同时绑定. 但是在react 15中, valueLink已经被标记为deprecated, 如果你继续使用, 会出现提示:
Note: LinkedStateMixin is deprecated as of React v15. The recommendation is to explicitly set the value and change handler, instead of using LinkedStateMixin.
而在react 16这个版本中, valueLink已经正式消失了, 对此, react官方的说法是, No easy/messy 2-way data binding with modern React!, 不过这只是说说而已, 来看看新版本的双向绑定怎么实现比较好.
新版本使用 当不使用双向绑定的时候, react的做法是为input提供value和onChange属性, 那么如果想简写时, ES6的扩展运算符...则提供了很大的方便性, 我们只需要返回一个含有value和onChange的对象即可.
1 2 3 4 5 6 7 function twoway ( ) { return { value : 'value' , onChange : function , } } <input {...twoway()}/>
这便是最基础的用法了, 那么基于此来扩展一下. 首先, value所在key肯定是需要作为参数传入的, 而读取和设置state的方法, 也是组件特有的. 那么我们需要直接把组件实例传入即可.
1 2 3 4 5 6 7 8 9 10 11 function twoway (component, key ) { return { value : component.state[key], onChange : (e ) => { component.setState({ [key]: event.target.value, }); }, }; } <input {...twoway(this , 'testValue' )} />;
加入path 好了, 初步的双向绑定方法就完成了, 下一步来优化它. 首先, 不应该所有的value都是直接保存在state上, 可能是保存在state中某个对象上, 那么所在对象的深层path也应该作为参数传入, 而key是作为state的key传入.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 import _ from 'lodash' ;function twoway (component, key, path ) { let hasPath = _.isArray(path) && path.length > 0 ; if (!hasPath) { return { value : component.state[key], onChange : (event ) => { component.setState({ [key]: event.target.value, }); }, }; } else { return { value : _.get(component.state[key], path), onChange : (event ) => { component.setState({ [key]: { ..._.set(component.state[key] || {}, path, event.target.value) }, }); }, }; } } class Test extends React .Component { constructor (props ) { super (props); this .state = {}; } render ( ) { return ( <div > <input {...twoway (this , 'testValue ')} /> <input {...twoway (this , 'testValueObject ', ['Lv1 ', 'Lv2 '])} /> <div > testValue : {this.state.testValue}</div > <div > testValueObject : {JSON.stringify(this.state.testValueObject)}</div > </div > ); } }
加入immutable支持 而我们推荐整个项目里面的对象尽量使用immutable结构, 达到优化性能和避免state对象深度拷贝的目的, 那么twoway方法也需要加入对immutable结构的支持. 顺便也加入对checkbox的支持.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 import _ from 'lodash' ;import Immutable from 'immutable' ;function twoway (component, key, path ) { if (!key) { throw 'dataValueBindHandler to bind state need one key' ; } const bindFun = (event ) => { const ele = event.target; const value = ele.type === 'checkbox' ? ele.checked : ele.value; const stateValue = component.state[key] || {}; if (Immutable.isCollection(stateValue)) { const setFuncName = _.isArray(path) ? 'setIn' : 'set' ; component.setState({ [key]: path ? component.state[key][setFuncName](path, value) : value, }); } else { component.setState({ [key]: path ? { ..._.set(stateValue, path, value) } : value, }); } }; let value = '' ; if (Immutable.isCollection(component.state[key])) { value = path ? component.state[key][_.isArray(path) ? 'getIn' : 'get' ](path) : component.state[key]; } else { value = path ? _.get(component.state[key], path) : component.state[key]; } return { value : value, onChange : bindFun, }; } class Test extends React .Component { constructor (props ) { super (props); this .state = { testValue : '' , testValueObject : { Lv1 : { Lv2 : 'val' , }, }, testValueImmutableObject : Immutable.Map().setIn(['Lv1' , 'Lv2' ], 'valImmutable' ), }; } render ( ) { return ( <div > <input {...twoway (this , 'testValue ')} /> <input {...twoway (this , 'testValueObject ', ['Lv1 ', 'Lv2 '])} /> <input {...twoway (this , 'testValueImmutableObject ', ['Lv1 ', 'Lv2 '])} /> <div > testValue : {this.state.testValue}</div > <div > testValueObject : {JSON.stringify(this.state.testValueObject)}</div > <div > testValueImmutableObject : {JSON.stringify(this.state.testValueImmutableObject)}</div > </div > ); } }
加入onChange方法缓存 对于这个函数, 还可以再优化一下, 因为每次执行twoway方法, 都会生成一个新的bindFun函数, 如果你的input使用的是自定义组件, 则会因为改变props中onChange变化而不是value变化而执行render函数, 那么需要将onChange变成一个固定的函数, 不需要每次重新生成, 则不会因为变化onChange而不是value时触发子组件的render函数了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 import React from 'react' ;import _ from 'lodash' ;import Immutable from 'immutable' ;const dataValueBindHandler = (component, key, path ) => { if (!key) { throw 'dataValueBindHandler to bind state need one key' ; } let dataHandleBindMap = component.__dataHandleBindMap || Immutable.Map(); let mapKey = `key:${key} ` ; if (path) { mapKey += `::path:${_.isArray(path) ? path.join('__' ) : path} ` ; } let onChange = null ; if (dataHandleBindMap.has(mapKey)) { onChange = dataHandleBindMap.get(mapKey); } else { const bindFun = (event ) => { const ele = event.target; const value = ele.type === 'checkbox' ? ele.checked : ele.value; const stateValue = component.state[key] || {}; if (Immutable.isCollection(stateValue)) { const setFuncName = _.isArray(path) ? 'setIn' : 'set' ; component.setState({ [key]: path ? component.state[key][setFuncName](path, value) : value, }); } else { component.setState({ [key]: path ? { ..._.set(stateValue, path, value) } : value, }); } }; component.__dataHandleBindMap = dataHandleBindMap.set(mapKey, bindFun); onChange = bindFun; } let value = '' ; if (Immutable.isCollection(component.state[key])) { value = path ? component.state[key][_.isArray(path) ? 'getIn' : 'get' ](path) : component.state[key]; } else { value = path ? _.get(component.state[key], path) : component.state[key]; } return { value : value, onChange : onChange, }; }; export default dataValueBindHandler;class Test extends React .Component { constructor (props ) { super (props); this .state = { testValue : '' , testValueObject : { Lv1 : { Lv2 : 'val' , }, }, testValueImmutableObject : Immutable.Map().setIn(['Lv1' , 'Lv2' ], 'valImmutable' ), }; } render ( ) { return ( <div > <input {...dataBindHandler (this , 'testValue ')} /> <input {...dataBindHandler (this , 'testValueObject ', ['Lv1 ', 'Lv2 '])} /> <input {...dataBindHandler (this , 'testValueImmutableObject ', ['Lv1 ', 'Lv2 '])} /> <div > testValue : {this.state.testValue}</div > <div > testValueObject : {JSON.stringify(this.state.testValueObject)}</div > <div > testValueImmutableObject : {JSON.stringify(this.state.testValueImmutableObject)}</div > </div > ); } }