表單
HTML表單元素與React中的其他DOM元素有所不同,因為表單元素生來就保留一些內(nèi)部狀態(tài)。例如嗤疯,下面這個表單只接受一個唯一的name革骨。
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
當(dāng)用戶提交表單時是复,HTML的默認行為會使這個表單跳轉(zhuǎn)到一個新頁面募强。在React中亦是如此幔睬。但大多數(shù)情況下背亥,我們都會構(gòu)造一個處理提交表單并可訪問用戶輸入表單數(shù)據(jù)的函數(shù)般眉。實現(xiàn)這一點的標(biāo)準(zhǔn)方法是使用一種稱為“受控組件”的技術(shù)我纪。
受控組件
在HTML當(dāng)中慎宾,像<input>
,<textarea>
, 和 <select>
這類表單元素會維持自身狀態(tài),并根據(jù)用戶輸入進行更新浅悉。但在React中趟据,可變的狀態(tài)通常保存在組件的狀態(tài)屬性中,并且只能用 setState()
方法進行更新术健。
我們通過使react變成一種單一數(shù)據(jù)源的狀態(tài)來結(jié)合二者汹碱。React負責(zé)渲染表單的組件仍然控制用戶后續(xù)輸入時所發(fā)生的變化。相應(yīng)的荞估,其值由React控制的輸入表單元素稱為“受控組件”咳促。
例如,我們想要使上個例子中在提交表單時輸出name,我們可以寫成“受控組件”的形式:
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>
);
}
}
由于 value
屬性是在我們的表單元素上設(shè)置的跪腹,因此顯示的值將始終為 React數(shù)據(jù)源上this.state.value
的值。由于每次按鍵都會觸發(fā) handleChange
來更新當(dāng)前React的state飞醉,所展示的值也會隨著不同用戶的輸入而更新冲茸。
使用"受控組件",每個狀態(tài)的改變都有一個與之相關(guān)的處理函數(shù)。這樣就可以直接修改或驗證用戶輸入冒掌。例如噪裕,我們?nèi)绻胂拗戚斎肴渴谴髮懽帜福覀兛梢詫?code>handleChange 寫為如下:
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
textarea 標(biāo)簽
在HTML當(dāng)中股毫,<textarea>
元素通過子節(jié)點來定義它的文本內(nèi)容
<textarea>
Hello there, this is some text in a text area
</textarea>
在React中膳音,<textarea>
會用value
屬性來代替。這樣的話铃诬,表單中的<textarea>
非常類似于使用單行輸入的表單:
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
注意this.state.value
是在構(gòu)造函數(shù)中初始化祭陷,這樣文本區(qū)域就能獲取到其中的文本。
select 標(biāo)簽
在HTML當(dāng)中趣席,<select>
會創(chuàng)建一個下拉列表兵志。例如這個HTML就創(chuàng)建了一個下拉列表的原型。
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
請注意宣肚,Coconut選項最初由于selected
屬性是被選中的想罕。在React中,并不使用之前的selected
屬性,而在根select
標(biāo)簽上用value
屬性來表示選中項按价。這在受控組件中更為方便惭适,因為你只需要在一個地方來更新組件。例如:
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite La Croix flavor:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
總之癞志,<input type="text">
, <textarea>
, 和 <select>
都十分類似 - 他們都通過傳入一個value
屬性來實現(xiàn)對組件的控制。
file input 標(biāo)簽
在HTML當(dāng)中框产,<input type="file">
允許用戶從他們的存儲設(shè)備中選擇一個或多個文件以提交表單的方式上傳到服務(wù)器上, 或者通過 Javascript 的 File API 對文件進行操作 凄杯。
<input type="file" />
由于該標(biāo)簽的 value
屬性是只讀的, 所以它是 React 中的一個非受控組件秉宿。我們會把它和其他非受控組件一起在后面的章節(jié)進行詳細的介紹戒突。
多個輸入的解決方法
當(dāng)你有處理多個受控的input
元素時,你可以通過給每個元素添加一個name
屬性蘸鲸,來讓處理函數(shù)根據(jù) event.target.name
的值來選擇做什么妖谴。
例如:
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
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>
);
}
}
注意我們?nèi)绾问褂肊S6當(dāng)中的計算屬性名語法來更新與給定輸入名稱相對應(yīng)的狀態(tài)鍵:
this.setState({
[name]: value
});
相當(dāng)于如下ES5語法
var partialState = {};
partialState[name] = value;
this.setState(partialState);
同樣由于 setState()
自動將部分狀態(tài)合并到當(dāng)前狀態(tài)酌摇,因此我們只需要使用發(fā)生變化的部分調(diào)用它。
受控組件的替代方法
有時使用受控組件可能很繁瑣嗡载,因為您要為數(shù)據(jù)可能發(fā)生變化的每一種方式都編寫一個事件處理程序窑多,并通過一個組件來管理全部的狀態(tài)。當(dāng)您將預(yù)先存在的代碼庫轉(zhuǎn)換為React或?qū)eact應(yīng)用程序與非React庫集成時洼滚,這可能變得特別煩人埂息。在以上情況下,你或許應(yīng)該看看非受控組件遥巴,這是一種表單的替代技術(shù)千康。
綜合自定義表單校驗案例
import React, { Component } from 'react';
class FormSub extends Component {
constructor(opt) {
super(opt);
this.state = {
Title: 'hi',
Validate: {
Title: {
required: true,
minLen: 6,
maxLen: 10,
validate: true,
msg: '*ToDo不能為空!'
}
}
}
}
handlerChange = (e) => {
// 設(shè)置狀態(tài):是異步執(zhí)行铲掐。
this.setState({
[e.target.name]: e.target.value
}, () => {
this.validateInput();
});
}
handlerSubmit = (e) => {
e.preventDefault();
// 第一: 做表單的校驗
this.validateInput();
// 第二: 做表單提交到后臺ajax請求
};
validateInput() {
let { Title, Validate } = this.state;
let tempValidate = false;
const len = Title.length;
const min = Validate.Title.minLen;
const max = Validate.Title.maxLen;
if(len >= min && len <= max) {
tempValidate = true;
}
this.setState(preState => {
return Object.assign({}, preState, {
Validate: {
Title: Object.assign({}, preState.Validate.Title,{
validate: tempValidate,
})
}
});
})
}
render() {
return (
<form onSubmit={this.handlerSubmit}>
<label>
ToDo:
<input
type="text"
name="Title"
onChange={this.handlerChange}
value={this.state.Title}
/>
{
!this.state.Validate.Title.validate &&
<span
style={{color: 'red'}}
>
{this.state.Validate.Title.msg}
</span>
}
</label>
<br/>
<input type="submit" value="提交"/>
</form>
);
}
}
export default FormSub;
狀態(tài)提升
使用 react 經(jīng)常會遇到幾個組件需要共用狀態(tài)數(shù)據(jù)的情況拾弃。這種情況下,我們最好將這部分共享的狀態(tài)提升至他們最近的父組件當(dāng)中進行管理摆霉。我們來看一下具體如何操作吧
我們一個計數(shù)的父組件豪椿,兩個按鈕組件,兩個按鈕組件分別對父組件中的數(shù)據(jù)進行添加和減少操作携栋。
// Counter.js 父組件
import React, { Component } from 'react';
import ButtonAdd from './ButtonAdd';
import ButtonMinus from './ButtonMinus';
class Counter extends Component {
constructor(option) {
super(option);
this.state = { num: 0, age: 19 };
}
minusCount(num, e) {
this.setState((preState) => {
return { num: preState.num - num }
});
}
addCount(num, e) {
this.setState((preState) => {
return { num: preState.num + num }
});
}
render() {
return (
<div>
<p>parent: { this.state.num } -{ this.state.age }</p>
<hr />
<ButtonAdd addCount={ this.addCount.bind(this) } num={ this.state.num } />
<ButtonMinus minusCount={ this.minusCount.bind(this) } num={ this.state.num } />
</div>
);
}
}
export default Counter;
// 子組件 添加按鈕組件
import React, { Component } from 'react';
class ButtonAdd extends Component {
render() {
return (
<div>
<span>child:state {this.props.num}</span>
<button onClick={ () => {
this.props.addCount(1);
}}>
+1
</button>
</div>
);
}
}
export default ButtonAdd;
// 子組件: 減少按鈕組件
import React, { Component } from 'react';
class ButtonMinus extends Component {
render() {
return (
<div>
<span>child:state { this.props.num }</span>
<button onClick={ () => {
this.props.minusCount(1);
}}>
-1
</button>
</div>
);
}
}
export default ButtonMinus;
組合與props.children
React 具有強大的組合模型搭盾,我們建議使用組合而不是繼承來復(fù)用組件之間的代碼。
在本節(jié)中婉支,我們將圍繞幾個 React 新手經(jīng)常使用繼承解決的問題鸯隅,我們將展示如何用組合來解決它們。
包含關(guān)系
一些組件不能提前知道它們的子組件是什么向挖。這對于 Sidebar
或 Dialog
這類通用容器尤其常見蝌以。
我們建議這些組件使用 children
屬性將子元素直接傳遞到輸出霎奢。
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
這樣做還允許其他組件通過嵌套 JSX 來傳遞子組件。
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
<FancyBorder>
JSX 標(biāo)簽內(nèi)的任何內(nèi)容都將通過 children
屬性傳入 FancyBorder
幕侠。由于 FancyBorder
在一個 <div>
內(nèi)渲染了 {props.children}
,所以被傳遞的所有元素都會出現(xiàn)在最終輸出中碍彭。
雖然不太常見晤硕,但有時你可能需要在組件中有多個入口,這種情況下你可以使用自己約定的屬性而不是 children
:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
} />
);
}
類似 <Contacts />
和 <Chat />
這樣的 React 元素都是對象舞箍,所以你可以像任何其他元素一樣傳遞它們。
特殊實例
有時我們認為組件是其他組件的特殊實例皆疹。例如疏橄,我們會說 WelcomeDialog
是 Dialog
的特殊實例。
在 React 中略就,這也是通過組合來實現(xiàn)的捎迫,通過配置屬性用較特殊的組件來渲染較通用的組件。
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />
);
}
組合對于定義為類的組件同樣適用:
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}