这两天在浏览 React 官方文档关于非受控组件部分时,官方推荐了一篇文章: Controlled and uncontrolled form inputs in React don’t have to be complicated。写的简洁清晰,所以想着做一次翻译练习,同时希望能给还在为控还是不控而纠结的朋友们提供一点参考。
你也许读过很多文章中说: "我们应该避免使用 setState ",但是几乎又有同样数量的文章却说: “使用 ref 是很糟糕的”。。。这可是两种矛盾的用法,相当于一方说要用非受控组件,一方却又说当然是受控组件好。对于初学者来说,遇到这种情况很难作出选择,也很难了解清楚选择的准则应该是什么。
终极问题就是:我应该如何开发一个 Fom 组件 ?
可以说,Form 是大多数 web 应用的核心组件。因此对 Form 表单的处理也成为了 React 的核心功能之一。
但是无需担心,接下来我会讲解 Form 表单中的受控 (Controlled) 以及非受控 (Uncontrolled)组件,以及对应的使用场景。
非受控组件 (The Uncontrolled)
非受控组件的使用方式就像我们操纵原生 html 组件。通过 DOM 来获取控件的 value。
我们以实现一个简单的 Form 组件为例:
class Form extends Component {
render() {
return (
<div>
<input type="text" />
</div>
);
}
}
你可以通过 ref 来获取控件的 value。我们在上面的组件中再增加一个 button 控件,点击 button 获取 input 控件的 value 值。
class Form extends Component {
handleSubmitClick = () => {
const name = this._name.value;
// do something with `name`
}
render() {
return (
<div>
<input type="text" ref={input => this._name = input} />
<button onClick={this.handleSubmitClick}>Sign up</button>
</div>
);
}
}
由上可见,我们从 input 控件获取值的方式类似于拉取(pull)。这个操作通常发生在我们提交表单的时候。
上面的例子是在 React 中实现 Form 表单最简单的一种方式,这也正好说明了非受控组件的适用场景:当业务逻辑非常简单的时候,还有一种场景估计就是你还是个 React 新手的时候。。
受控组件 (The Controlled)
一个受控组件 (controlled input) 以 props 的方式接收当前值,对于值发生改变时的回调函数也遵循同样的方式。这种使用方式很 “React” (但这并不意味着我们在开发种必须使用这种方式)
<input value={someValue} onChange={handleChange} />
在 React 中,作为 props 传递的控件的值通常存放在其所在 Component 的 state 中。
class Form extends Component {
constructor() {
super();
this.state = {
name: '',
};
}
handleNameChange = (event) => {
this.setState({ name: event.target.value });
};
render() {
return (
<div>
<input
type="text"
value={this.state.name}
onChange={this.handleNameChange}
/>
</div>
);
}
}
当然,存放在其所在 Component 中的 state 不是绝对的,props 的传值可以来在于其他 Component 甚至是单独的 state store 比如 Redux
每当用户输入一个新的字符,handleNameChange 会被调用,它会将当前 input 的值更新到 Component 的 state 中。
- input 初始状态值为空字符串 ‘’
- 用户键入一个 ‘a’ , handleNameChange回调函数取得该值同时 setState 触发一次渲染将用户键入的值展示在屏幕上
- 用户继续输入 ‘b’, handleNameChange回调函数取得值 ‘ab’ 同时 setState 触发一次渲染将用户键入的值展示在屏幕上即 value = ‘ab’
上述过程像是将控件由于 onChange 而产生的新值推送(push)给 Form Component,因此 Form 总是可以获得控件的最新值。
数据 (state) 和 UI (inputs) 总是保持同步状态。input 中的值从 state 获取,input 通过回调向 Form 发起请求更新 state。
我们可以看出,Form Component 对控件 onChange 的反应是实时的,可适用于如下场景:
- 即时反馈:表单输入验证 (validation)
- 在所有字段验证通过前,将提交 button 置灰 disabled
- 控件对输入格式有要求,比如信用卡号,身份证号等。
如果你的业务逻辑简单到根本没有上述场景,那么使用非受控组件可能是个更好的选择。
组件受控的本质
除了上述举例的 input 控件,Form 表单还有其他控件比如 checkbox, radio, select 和 textarea.
Form 表单中控件的 value 值,是通过 props 绑定获取,那么我们就称控件是受控的。就这么简单!
不同的 Form 表单控件,有不同的 value 形式,如下表所示:
总结
受控组件与非受控组件各有优点。我们应该根据自身业务逻辑的特点与场景综合评估来做出选择,适合自己的就是好的。
如果我们业务中用到的表单逻辑交互非常简单(very simple),那么使用非受控组件 (ref方式) 就足够用了。你不必在意很多文章中所说的使用 ref 是多么的 "不好"。
当然,以上建议并不是一锤子买卖,任何非受控组件都可以写成受控组件的形式:Going from uncontrolled to controlled inputs is not hard