学习书籍:深入React技术栈
1、前言
在 Web 应用开发中,表单的作用尤为重要。正是因为表单的存在,才使得用户能够与 Web 应用进行富交互。而在React 中,一切数据都是状态,当然也包括表单数据。在这一节中,我们将讲述 React 是如何处理表单的。
2、应用表单组件(受控组件)
表单组件的几个重要属性:
a、状态属性:React 的 form 组件提供了几个重要的属性,用于展示组件的状态。
- value:类型为 text 的 input 组件、textarea 组件以及 select 组件都借助 value prop 来展示应用的状态。
- checked:类型为 radio 或 checkbox 的组件借助值为 boolean 类型的 selected prop 来展示应用的状态。
- selected:该属性可作用于 select 组件下面的 option 上,React 并不建议使用这种方式表示状态,而推荐在 select 组件上使用 value 的方式。
b、事件属性
刚刚提到的状态属性与事件属性存在一定的关联——在状态属性发生变化时,会触发onChange 事件属性。实际上,受控组件中的 change 事件与 HTML DOM 中提供的 input 事件更为类似。同样,React 支持 DOM Level 3 中定义的所有表单事件。
2、1 文本框
JSX 中的 textarea 组件与类型为 text 的 input 组件的用法很类似。同样有一个 value prop 用来表示表单的值,而在 HTML 中 textarea 的值则是通过children 来表示的。此外,得益于 JSX 语法特性,我们可以在标签没有子元素的时候使用单个标签自闭合的语法。
import React, {Component} from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleTextareaChange = this.handleTextareaChange.bind(this);
this.state = {
inputValue: '',
textareaValue: '',
};
}
handleInputChange(e) {
this.setState({
inputValue: e.target.value,
});
}
handleTextareaChange(e) {
this.setState({
textareaValue: e.target.value,
});
}
render() {
const {inputValue, textareaValue} = this.state;
return (
<div>
<p>单行输入框:<input type="text" value={inputValue}
onChange={this.handleInputChange}/></p>
<p>多行输入框:<textarea value={textareaValue}
onChange={this.handleTextareaChange}/></p>
</div>
);
}
}
export default App;
2、2 单选按钮与复选框、select
单选和复选框这两种表单的 value 值一般是不会改变的,而是通过一个布尔类型的checked prop 来表示是否为选中状态。对于select,其中通过设置 select标签的 multiple={true} 来实现一个多选下拉列表。
import React, {Component} from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
radios: '',
select: '',
multipleSelect: [],
checkbox: [],
};
}
handleChange(name,event) {
var _this = this,
setState = {
checkbox: this.state.checkbox
};
// console.log(name,event.target,event.target.value);
if (name == 'checkbox') {
if (event.target.checked) {
if (setState[name].indexOf(event.target.value) == -1) {
setState[name].push(event.target.value);
}
} else {
var findIndex = setState.checkbox.findIndex( item => item == event.target.value);
setState.checkbox.splice(findIndex,1);
}
} else if (name == 'multipleSelect') {
var { options } = event.target,
multipleSelect = Object.keys(options)
.filter(i => options[i].selected === true)
.map(i => options[i].value);
setState[name] = multipleSelect;
} else{
setState[name] = event.target.value;
}
_this.setState(setState);
}
render() {
const { radios,checkbox,select,multipleSelect} = this.state;
return (
<div>
<p>radio:</p>
<label>
male:
<input
type="radio"
name="radios"
value="male"
checked={radios === 'male'}
onChange={this.handleChange.bind(this,'radios')}
/>
</label>
<label>
female:
<input
type="radio"
value="female"
name="radios"
checked={radios === 'female'}
onChange={this.handleChange.bind(this,'radios')}
/>
</label>
<br/>
<p>checkbox:</p>
<label>
<input
type="checkbox"
value="aa"
checked={checkbox.indexOf('aa') !== -1}
onChange={this.handleChange.bind(this,'checkbox')}
/>
aa
</label>
<label>
<input
type="checkbox"
value="asdd"
checked={checkbox.indexOf('asdd') !== -1}
onChange={this.handleChange.bind(this,'checkbox')}
/>
asdd
</label>
<label>
<input
type="checkbox"
value="cc"
checked={checkbox.indexOf('cc') !== -1}
onChange={this.handleChange.bind(this,'checkbox')}
/>
cc
</label>
<br/>
<p>multipleSelect</p>
<label>
<select value={multipleSelect} multiple={true} onChange={this.handleChange.bind(this,'multipleSelect')}>
<option value="beijing" >北京</option>
<option value="shanghai">上海</option>
<option value="hangzhou">杭州</option>
</select>
</label>
<p>select</p>
<label>
<select value={select} onChange={this.handleChange.bind(this,'select')}>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="hangzhou">杭州</option>
</select>
</label>
</div>
)
}
}
export default App;
每当表单的状态发生变化时,都会被写入到组件的 state 中,这种组件在React 中被称为受控组件(controlled component)。在受控组件中,组件渲染出的状态与它的 value或 checked prop 相对应。React 通过这种方式消除了组件的局部状态,使得应用的整个状态更加。可控。React 官方同样推荐使用受控表单组件。总结下 React 受控组件更新 state 的流程:
- 可以通过在初始 state 中设置表单的默认值。
- 每当表单的值发生变化时,调用 onChange 事件处理器。
- 事件处理器通过合成事件对象event 拿到改变后的状态,并更新应用的 state。
- setState 触发视图的重新渲染,完成表单组件值的更新。
在 React 中,数据是单向流动的。从示例中,我们能看出来表单的数据源于组件的 state,并通过 props 传入,这也称为单向数据绑定。然后,我们又通过 onChange 事件处理器将新的表单数据写回到组件的 state,完成了双向数据绑定。
3、非受控组件
一个表单组件没有 value props(单选按钮和复选框对应的是 checked prop)时,就可以称为非受控组件。相应地,你可以使用 defaultValue 和 defaultChecked prop 来表示组件的默认状态。下面通过一个简单的示例来描述非受控组件
import React, {Component} from 'react';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e) {
e.preventDefault();
// 这里使用 React 提供的 ref prop 来操作 DOM
// 当然,也可以使用原生的接口,如 document.querySelector
const { value } = this.refs.name;
console.log(value,document.querySelector('.name').value);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input ref="name" className="name" type="text" defaultValue="Hangzhou" />
<button type="submit">Submit</button>
</form>
)
}
}
export default App;
在 React 中,非受控组件是一种反模式,它的值不受组件自身的 state 或 props 控制。通常,需要通过为其添加 ref prop 来访问渲染后的底层 DOM 元素。
4、非受控组件和受控组件总结
- 过 defaultValue 或者 defaultChecked 来设置表单的默认值,它仅会被渲染一次,在后续的渲染时并不起作用。在受控组件中,可以将用户输入的英文字母转化为大写后输出展示,而在非受控组件中则不会。而如果不对受控组件绑定 change 事件,我们在文本框中输入任何值都不会起作用。多数情况下,对于非受控组件,我们并不需要提供 change 事件。通过上面的示例可以看出,受控组件和非受控组件的最大区别是:非受控组件的状态并不会受应用状态的控制,应用中也多了局部组件状态,而受控组件的值来自于组件的 state
import React, {Component} from 'react'; import './App.css'; class App extends Component { constructor(props) { super(props); this.state = { value1: '', value2: '', }; } render() { return ( <div> <input value={this.state.value1} onChange={e => { this.setState({ value1: e.target.value.toUpperCase() }) }} /> <input defaultValue={this.state.value2} onChange={e => { this.setState({ value2: e.target.value.toUpperCase() }) }} /> </div> ) } } export default App;
。
- 性能上的问题:在受控组件中,每次表单的值发生变化时,都会调用一次 onChange 事件处理器,这确实会有一些性能上的损耗。虽然使用非受控组件不会出现这些问题,但仍然不提倡在 React 中使用非受控组件。这个问题可以通过 Flux/Redux 应用架构等方式来达到统一组件状态的目的。
- 是否需要事件绑定:使用受控组件最令人头疼的就是,我们需要为每个组件绑定一个 change 事件,并且定义一个事件处理器来同步表单值和组件的状态,这是一个必要条件。当然,在某些简单的情况下,也可以使用一个事件处理器来处理多个表单域。