受控組件與非受控組件
受控組件:
在 HTML 中,表單元素(如<input>、 <textarea> 和 <select>)之類的表單元素通常自己維護(hù) state,并根據(jù)用戶輸入進(jìn)行更新。而在 React 中,可變狀態(tài)(mutable state)通常保存在組件的 state 屬性中,并且只能通過使用 setState()來更新。
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('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
該組件實(shí)現(xiàn)了一個(gè)點(diǎn)擊提交,打印名字的功能。
有時(shí)使用受控組件會(huì)很麻煩,因?yàn)槟阈枰獮閿?shù)據(jù)變化的每種方式都編寫事件處理函數(shù)(因?yàn)閿?shù)據(jù)都存在state里,所以需要編寫事件處理函數(shù)更新state,就比如這里都handleChange),并通過一個(gè) React 組件傳遞所有的輸入 state。
非受控組件
要編寫一個(gè)非受控組件,而不是為每個(gè)狀態(tài)更新都編寫數(shù)據(jù)處理函數(shù),你可以 使用 ref 來從 DOM 節(jié)點(diǎn)中獲取表單數(shù)據(jù)。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
這里和受控組件都區(qū)別就在于,我們并沒有給input賦值(value={this.state.xxx}),數(shù)據(jù)本身都是存儲在dom節(jié)點(diǎn)本身的,每次我們需要獲取或者改變的時(shí)候我們應(yīng)該直接去通過ref操作dom節(jié)點(diǎn),而非設(shè)置state。
默認(rèn)值
如果是受控組件,value就可以充當(dāng)默認(rèn)值,而非受控組件由于沒有可以傳遞的地方本應(yīng)該是除了直接操作dom之外沒有設(shè)置默認(rèn)值的方法的,但是react提供了封裝,我們可以在dom上添加default值:
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
defaultValue="Bob"
type="text"
ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
生命周期
常用的生命周期方法
本節(jié)中的方法涵蓋了創(chuàng)建 React 組件時(shí)能遇到的絕大多數(shù)用例。想要更好了解這些方法,可以參考生命周期圖譜。
render()
只能返回:React、fragments、Portals、字符串或者數(shù)值、布爾類型或 null。
constructor()
通常,在 React 中,構(gòu)造函數(shù)僅用于以下兩種情況:
- 通過給
this.state賦值對象來初始化內(nèi)部 state。 - 為事件處理函數(shù)綁定實(shí)例
如果不做這些處理,則不需要constructor()
避免將 props 的值復(fù)制給 state!這是一個(gè)常見的錯(cuò)誤?。?/strong>
constructor(props) {
super(props);
// 不要這樣做
this.state = { color: props.color };
}
componentDidMount()
會(huì)在組件掛載后(插入 DOM 樹中)立即調(diào)用。
這個(gè)方法是比較適合添加訂閱的地方。如果添加了訂閱,請不要忘記在 componentWillUnmount() 里取消訂閱。
你可以在 componentDidMount() 里可以直接調(diào)用 setState()。它將觸發(fā)額外渲染,但此渲染會(huì)發(fā)生在瀏覽器更新屏幕之前。如此保證了即使在 render() 兩次調(diào)用的情況下,用戶也不會(huì)看到中間狀態(tài)。請謹(jǐn)慎使用該模式,因?yàn)樗鼤?huì)導(dǎo)致性能問題。通常,你應(yīng)該在 constructor() 中初始化 state。如果你的渲染依賴于 DOM 節(jié)點(diǎn)的大小或位置,比如實(shí)現(xiàn) modals 和 tooltips 等情況下,你可以使用此方式處理
componentDidUpdate()
componentDidUpdate(prevProps, prevState, snapshot)
componentDidUpdate() 會(huì)在更新后會(huì)被立即調(diào)用。首次渲染不會(huì)執(zhí)行此方法。
當(dāng)組件更新后,可以在此處對 DOM 進(jìn)行操作。如果你對更新前后的 props 進(jìn)行了比較,也可以選擇在此處進(jìn)行網(wǎng)絡(luò)請求。(例如,當(dāng) props 未發(fā)生變化時(shí),則不會(huì)執(zhí)行網(wǎng)絡(luò)請求)。
componentWillUnmount()
componentWillUnmount() 會(huì)在組件卸載及銷毀之前直接調(diào)用。在此方法中執(zhí)行必要的清理操作,例如,清除 timer,取消網(wǎng)絡(luò)請求或清除在 componentDidMount() 中創(chuàng)建的訂閱等。
componentWillUnmount() 中不應(yīng)調(diào)用 setState(),因?yàn)樵摻M件將永遠(yuǎn)不會(huì)重新渲染。組件實(shí)例卸載后,將永遠(yuǎn)不會(huì)再掛載它。
shouldComponentUpdate()
當(dāng) props 或 state 發(fā)生變化時(shí),shouldComponentUpdate() 會(huì)在渲染執(zhí)行之前被調(diào)用。返回值默認(rèn)為 true。首次渲染或使用 forceUpdate() 時(shí)不會(huì)調(diào)用該方法。
此方法僅作為性能優(yōu)化的方式而存在。不要企圖依靠此方法來“阻止”渲染,因?yàn)檫@可能會(huì)產(chǎn)生 bug。
我們不建議在 shouldComponentUpdate() 中進(jìn)行深層比較或使用 JSON.stringify()。這樣非常影響效率,且會(huì)損害性能。
其他API
setState()
setState(updater[, callback])
將 setState() 視為請求而不是立即更新組件的命令。為了更好的感知性能,React 會(huì)延遲調(diào)用它,然后通過一次傳遞更新多個(gè)組件。React 并不會(huì)保證 state 的變更會(huì)立即生效。
setState() 并不總是立即更新組件。它會(huì)批量推遲更新。這使得在調(diào)用 setState() 后立即讀取 this.state 成為了隱患。為了消除隱患,請使用 componentDidUpdate 或者 setState 的回調(diào)函數(shù)(setState(updater, callback)),這兩種方式都可以保證在應(yīng)用更新后觸發(fā)。如需基于之前的 state 來設(shè)置當(dāng)前的 state,請閱讀下述關(guān)于參數(shù) updater 的內(nèi)容。
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
updater 函數(shù)中接收的 state 和 props 都保證為最新。updater 的返回值會(huì)與 state 進(jìn)行淺合并。
有關(guān)更多詳細(xì)信息,請參閱:
forceUpdate()
默認(rèn)情況下,當(dāng)組件的 state 或 props 發(fā)生變化時(shí),組件將重新渲染。如果 render() 方法依賴于其他數(shù)據(jù),則可以調(diào)用 forceUpdate() 強(qiáng)制讓組件重新渲染。
調(diào)用 forceUpdate() 將致使組件調(diào)用 render() 方法,此操作會(huì)跳過該組件的 shouldComponentUpdate()。
ReactDOM
react-dom 的 package 提供了可在應(yīng)用頂層使用的 DOM(DOM-specific)方法,如果有需要,你可以把這些方法用于 React 模型以外的地方。不過一般情況下,大部分組件都不需要使用這個(gè)模塊。
React.Component與React.PureComponent
React.PureComponent 與 React.Component 很相似。兩者的區(qū)別在于 React.Component 并未實(shí)現(xiàn) shouldComponentUpdate(),而 React.PureComponent 中以淺層對比 prop 和 state 的方式來實(shí)現(xiàn)了該函數(shù)。
shouldComponentUpdate在剛剛的生命周期中也說過,當(dāng)props或者state發(fā)生變化時(shí),shouldComponentUpdate() 會(huì)在渲染執(zhí)行之前被調(diào)用。
返回默認(rèn)值true,首次渲染和 forceUpdate() 時(shí)不會(huì)調(diào)用該方法。
如果 shouldComponentUpdate() 返回 false,則不會(huì)調(diào)用 UNSAFE_componentWillUpdate(),render() 和 componentDidUpdate()。
而React.Component的shouldComponentUpdate默認(rèn)就是返回true的,React.PureComponent是實(shí)現(xiàn)一套邏輯,淺比較props和state,并減少了跳過必要更新的可能性。
什么是淺比較?
對于基本類型(primitives),例如數(shù)字或者布爾值,來說,淺拷貝將會(huì)檢查其值是否相同,例如1與1相等,true與true相等。對于引用類型的變量,例如復(fù)雜的javascript對象或者數(shù)組,來說,淺拷貝將僅僅檢查它們的引用值是否相等。這意味著,對于引用類型的變量來說,如果我們只是更新了其中的一個(gè)元素,例如更新了數(shù)組中某一位置的值,那么更新前后的數(shù)組仍是相等的。
因此意味著相比于Component,PureCompoent的性能表現(xiàn)將會(huì)更好。但使用PureCompoent要求滿足如下條件:
- props和state都不可變
- props和state沒有層級
- 如果數(shù)據(jù)改變無法反應(yīng)在淺拷貝上,應(yīng)該調(diào)用forceUpdate更新。
React.PureComponent 中的 shouldComponentUpdate() 僅作對象的淺層比較。如果對象中包含復(fù)雜的數(shù)據(jù)結(jié)構(gòu),則有可能因?yàn)闊o法檢查深層的差別,產(chǎn)生錯(cuò)誤的比對結(jié)果。僅在你的 props 和 state 較為簡單時(shí),才使用 React.PureComponent,或者在深層數(shù)據(jù)結(jié)構(gòu)發(fā)生變化時(shí)調(diào)用 forceUpdate() 來確保組件被正確地更新。