React main concepts(九)
建议配合官网文档Forms进行使用。
Forms
HTML 表单的工作方式和 React 表单的工作方式略有不同,因此 HTML 的表单元素有一些固有的性质,如下面这个纯 HTML 表单:
<form >
<label>
Name:
<input type="text" name="name">
</label>
<input type="submit" value="submit" />
</form>
该表单有一个默认的 HTML 行为,即在用户提交表单后浏览到新页面,如果你希望在 React 中也实现这样的行为,那它确实能正常工作,但是在大多数情况下,使用 JavaScript 函数来控制表单的提交和获取用户填写的数据是更优的做法。比较标准的做法是使用“受控组件controlled components
的方法。
Controlled Components
在 HTML 中,表单元素例如<input>
, <textarea>
, and <select>
通常维护自己的状态,并根据用户的输入更新。对于 React 而言,可变状态一般保存在组件的 state 中,并使用 setState() 函数进行 更新。
我们可以将 HTML 保存的状态和 React 组件保存的状态绑定在一起,使之同源,也就是说, React 即渲染 UI 页面,也控制表单数据。
我们将上面的纯 HTML 表单修改为下面的组件:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) { this.setState({value: event.target.value}); }
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}> <label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} /> </label>
<input type="submit" value="Submit" />
</form>
);
}
}
上面的input
表单元素的 value
始终为组件的 this.state.value
, 这使得 React 组件状态和 HTML 维护的状态保持一致,handleChange()
函数会在每次用户输入时调用,因此 React 每次都会同步更新 UI。
The textarea Tag
在 HTML 中, <textarea>
元素的内容由其孩子确定。
<textarea>
Hello there, this is some text in a text area
</textarea>
在 React 中,textarea
使用value
属性定义其内容,和input
的使用方法保持一致:
class EssayForm extends React.Component {
constructor(props){
super(props);
this.state = {value: 'Please write an essay about your favorite DOM element'};
}
handleChange = () => {
this.setState({value: event.target.value});
}
handleSubmit = () => {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Essay:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="submit" />
</form>
)
}
}
因为在constructor
中初始化了this.state.value
,因此textarea
中一开始就会显示文字。
The select Tag
在 HTML 中,<select>
元素会创建一个下拉列表,例如,创建一个纯 HTML 的下拉列表:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">lime</option>
<option selected value="coconut">coconut</option>
<option value="mango">mango</option>
</select>
注意,HTML 使用selected
属性来初始化选中的文本, React 则和 textarea
的初始化类似,使用value
在constructor
中初始化选中的文本,
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'Lime'};
}
handleChange = () => {
this.setState({value: event.target.value});
}
handleSubmit = () => {
alert("Submit a flavor: " + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit} >
<label>
Pick your favorite flavor:
<select value={this.state.value} onChange={this.handleChange} >
<option value="Lime">Lime</option>
<option value="coconut">coconut</option>
<option value="mango">mango</option>
</select>
</label>
<input type="submit" value="Submit"/>
</form>
)
}
}
对于<input>
, textarea
, select
来说, React 均使用value
属性来实现对应的受控组件。
对于多选框来说,可以使用数组来初始化选中项:
<select multiple={true} value={['B', 'C']}>
但实际上无法进行多选,笔者目前还没有搞清楚原理,后续查到实现方式再来补全
The file input Tag
在 HTML 中,<input type="file" />
允许用户上传文件。因此上传文件是只读的,因此在React中这不是受控组件,我们将和其他非受控组件一起讲解。
Handling Multiple Inputs
如果你想处理多个input
元素,你可以给每个元素添加name
属性,并且通过event.targer.name
处理指定的input
:
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: false,
numberOfGuests: 2
};
}
handleInputChange = () => {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({[name]: value});
}
render() {
return (
<form>
<label>
Is going:
<input name="isGoing" type="checkbox" checked={this.state.isGoing} onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input name="numberOfGuests" type="number" value={this.state.numberOfGuests} onChange={this.handleInputChange} />
</label>
</form>
)
}
}
这里使用了ES6的计算属性名语法来更新state:以下两种语法是等价的:
ES6:
this.setState({
[name]: value
})
ES5:
let partialState = {};
partialState[name] = value;
this.setState(partialState);
setState()
会将新state和旧state合并。
Controlled Input Null Value
如果将确定的值赋给受控组件的value
,会导致用户无法改变输入,除非你本亦如此。如果你希望如此,但指定了常量后,用户仍可编辑,可能是你指定的值为null
或者undefined
ReactDOM.render(<input value="hi" />, document.getElementById('root'));
setTimeout(function() {
ReactDOM.render(<input value={null} />, document.getElementById('root'));
}, 5000);
上述的输入框在5秒钟后由不可编辑状态变为可编辑状态。
Alternatives to Controlled Components
有时使用受控组件会显得十分繁杂,因为你必须为每个组件都编写不同的handleChange
,并且记录每一个组件的状态。这在你讲已有代码重构为React代码时尤为繁杂,这时你可以采用非受控组件来来实现.
Fully-Fledged Solutions
如果你正在寻找一个成熟的解决方案,包括合法性检验、追踪访问,提交处理,使用 Formik 是不错的选择。然而,它也是建立在受控组件和管理 state 的基础之上 —— 所以不要忽视学习它们。