在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 >      );   } }