react 中的数据双向绑定

react中, 如果不使用双向绑定, 那么获取一个受控制的input的数据需要写一个onChange回调函数来获取输入值, 再通过setState来将value传入input做展示. 少量的输入框的情况下, 还可以使用这种方法, 但是如果在比较大和复杂的表单中, 这种写法就会觉得太麻烦了.

在以前的版本中, 可以通过mixin:[LinkedStateMixin]这种方法来实现简易的双向绑定, 它的底层方法是reactinput提供的valueLink属性, 可以通过传递一个含有valuerequestChange属性的对象, 来实现valueonChange的同时绑定. 但是在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提供valueonChange属性, 那么如果想简写时, ES6的扩展运算符...则提供了很大的方便性, 我们只需要返回一个含有valueonChange的对象即可.

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是作为statekey传入.

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使用的是自定义组件, 则会因为改变propsonChange变化而不是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>
);
}
}
作者

Mosby

发布于

2017-09-17

许可协议

评论