受控和非受控组件真的那么难理解吗|博客系列
# 正文
# 受控组件基本概念
通过名称,我们可以猜测一下这两个词是什么意思:
- 受控组件:受我们控制的组件
- 非控组件:不受我们控制的组件
(这提莫的不是废话吗…)
咳咳,好吧,这里的受控和非控是什么意思呢?其实也就是我们对某个组件状态的掌控,它的值是否只能由用户设置,而不能通过代码控制。
我们知道,在React中定义了一个input输入框的话,它并没有类似于Vue里v-model的这种双向绑定功能。也就是说,我们并没有一个指令能够将数据和输入框结合起来,用户在输入框中输入内容,然后数据同步更新。
就像下面这个案例:
class TestComponent extends React.Component {
render () {
return <input name="username" />
}
}
用户在界面上的输入框输入内容时,它是自己维护了一个"state",这样的话就能根据用户的输入自己进行UI上的更新。(这个state并不是我们平常看见的this.state,而是每个表单元素上抽象的state)
想想此时如果我们想要控制输入框的内容可以怎样做呢?唔…输入框的内容取决的是input中的value属性,那么我们可以在this.state中定义一个名为username的属性,并将input上的value指定为这个属性:
class TestComponent extends React.Component {
constructor (props) {
super(props);
this.state = { username: 'lindaidai' };
}
render () {
return <input name="username" value={this.state.username} />
}
}
但是这时候你会发现input的内容是只读的,因为value会被我们的this.state.username所控制,当用户输入新的内容时,this.state.username并不会自动更新,这样的话input内的内容也就不会变了。
哈哈,你可能已经想到了,我们可以用一个onChange事件来监听输入内容的改变并使用setState更新this.state.username:
class TestComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
username: "lindaidai"
}
}
onChange (e) {
console.log(e.target.value);
this.setState({
username: e.target.value
})
}
render () {
return <input name="username" value={this.state.username} onChange={(e) => this.onChange(e)} />
}
}
现在不论用户输入什么内容state与UI都会跟着更新了,并且我们可以在组件中的其它地方使用this.state.username来获取到input里的内容,也可以通过this.setState()来修改input里的内容。
OK👌,现在让我们来看看受控组件的定义:
在HTML的表单元素中,它们通常自己维护一套state,并随着用户的输入自己进行UI上的更新,这种行为是不被我们程序所管控的。而如果将React里的state属性和表单元素的值建立依赖关系,再通过onChange事件与setState()结合更新state属性,就能达到控制用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件。
# select受控组件
在上面用input向大家演示了一个最基本的受控组件,那么其实对于其它的表单元素使用起来也差不多,可能就是属性名和事件不同而已。
例如input类型为text的表单元素中使用的是:
valueonChange
对于textarea标签也和它一样是使用value和onChange:
<textarea value={this.state.value} onChange={this.handleChange} />
# 单选select
对于select表单元素来说,React中将其转化为受控组件可能和原生HTML中有一些区别。
在原生中,我们默认一个select选项选中使用的是selected,比如下面这样:
<select>
<option value="sunshine">阳光</option>
<option value="handsome">帅气</option>
<option selected value="cute">可爱</option>
<option value="reserved">高冷</option>
</select>
给"可爱"的选项设置了selected,默认选中的就是它了。
但是如果是使用React受控组件来写的话就不用那么麻烦了,因为它允许在根select标签上使用value属性,去控制选中了哪个。这样的话,对于我们也更加便捷,在用户每次重选之后我们只需要在根标签中更新它,就像是这个案例🌰:
class SelectComponent extends React.Component {
constructor(props) {
super(props);
this.state = { value: 'cute' };
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('你今日相亲对象的类型是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={(e) => this.handleSubmit(e)}>
<label>
你今日相亲对象的类型是:
<select value={this.state.value} onChange={(e) => this.handleChange(e)}>
<option value="sunshine">阳光</option>
<option value="handsome">帅气</option>
<option value="cute">可爱</option>
<option value="reserved">高冷</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
);
}
}
export default SelectComponent;
可以看到不论是input类型为text的控件还是textarea、select在实现为受控组件上都差不多。
# 多选select
多选select的话,对比单选来说,只有这两处改动:
- 给
select标签设置multiple属性为true select标签value绑定的值为一个数组
这里也来小小的写一个案例吧:
class SelectComponent extends React.Component {
constructor(props) {
super(props);
// this.state = { value: 'cute' };
this.state = { value: ['cute'] };
}
handleChange(event) {
console.log(event.target.value)
const val = event.target.value;
const oldValue = this.state.value;
const i = this.state.value.indexOf(val);
const newValue = i > -1 ? [...oldValue].splice(i, 1) : [...oldValue, val];
this.setState({value: newValue});
}
handleSubmit(event) {
alert('你今日相亲对象的类型是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={(e) => this.handleSubmit(e)}>
<label>
你今日相亲对象的类型是:
<select multiple={true} value={this.state.value} onChange={(e) => this.handleChange(e)}>
<option value="sunshine">阳光</option>
<option value="handsome">帅气</option>
<option value="cute">可爱</option>
<option value="reserved">高冷</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
);
}
}
export default SelectComponent;
# 动态表单受控组件案例
上面👆咱们实现了一些简单的受控组件案例,接着来玩个稍微难点的。
先看一下我们的需求:
实现一个组件,传入以下数组,自动渲染出表单:
(CInput代表一个输入框,CSelect代表一个选择框)
// 决定表单的结构
const formConfig = [
{
Component: CInput,
label: '姓名',
field: 'name',
},
{
Component: CSelect,
label: '性别',
field: 'sex',
options: [{ label: '男', value: 'man' }, { label: '女', value: 'woman' }]
}
]
// 决定表单的内容
this.state = {
name: 'xx',
sex: 'man'
}
也就是来实现一个简单的动态表单,看看受控组件在其中的应用。
-
formConfig决定了表单的结构,也就是定义表单中会有哪些项 -
this.state中定义了表单中各项的值是什么,它与formConfig是靠formConfig中各项的field字段来建立链接的。
知道了上面👆这些东西,我们就能很快写出这个动态表单组件的大概样子了:
(我们就把这个组件命名为formComponent吧,重点看render部分)
