原文: https://medium.com/@mweststra...
做者: Michel Weststratejavascript
這篇文章原標題是3 Reasons why I stopped using React.setState,可是我對原文做者提出的論點不是很感冒,可是做者提出的三點對React
新手來講是很容易忽略的地方,因此我在這裏只提出部份內容,並且把標題改成使用React.setState須要注意的三點。html
對React
新手來講,使用setState
是一件很複雜的事情。即便是熟練的React
開發,也頗有可能由於React
的一些機制而產生一些bug,好比下面這個例子:java
文檔中也說明了當使用setState
的時候,須要注意什麼問題:react
注意:
絕對不要 直接改變this.state
,由於以後調用setState()
可能會替換掉你作的改變。把this.state
當作是不可變的。git
setState()
不會馬上改變this.state
,而是建立一個即將處理的state
轉變。在調用該方法以後訪問this.state
可能會返回現有的值。github對
setState
的調用沒有任何同步性的保證,而且調用可能會爲了性能收益批量執行。app
setState()
將老是觸發一次重繪,除非在shouldComponentUpdate()
中實現了條件渲染邏輯。若是可變對象被使用了,但又不能在shouldComponentUpdate()
中實現這種邏輯,僅在新state
和以前的state
存在差別的時候調用setState()
能夠避免沒必要要的從新渲染。異步
總結出來,當使用setState
的時候,有三個問題須要注意:函數
不少開發剛開始沒有注意到setState
是異步的。若是你修改一些state
,而後直接查看它,你會看到以前的state
。這是setState
中最容易出錯的地方。 setState
這個詞看起來並不像是異步的,因此若是你不假思索的用它,可能會形成bugs
。下面這個例子很好的展現了這個問題:性能
class Select extends React.Component { constructor(props, context) { super(props, context) this.state = { selection: props.values[0] }; } render() { return ( <ul onKeyDown={this.onKeyDown} tabIndex={0}> {this.props.values.map(value => <li className={value === this.state.selection ? 'selected' : ''} key={value} onClick={() => this.onSelect(value)} > {value} </li> )} </ul> ) } onSelect(value) { this.setState({ selection: value }) this.fireOnSelect() } onKeyDown = (e) => { const {values} = this.props const idx = values.indexOf(this.state.selection) if (e.keyCode === 38 && idx > 0) { /* up */ this.setState({ selection: values[idx - 1] }) } else if (e.keyCode === 40 && idx < values.length -1) { /* down */ this.setState({ selection: values[idx + 1] }) } this.fireOnSelect() } fireOnSelect() { if (typeof this.props.onSelect === "function") this.props.onSelect(this.state.selection) /* not what you expected..*/ } } ReactDOM.render( <Select values={["State.", "Should.", "Be.", "Synchronous."]} onSelect={value => console.log(value)} />, document.getElementById("app") )
第一眼看上去,這個代碼彷佛沒有什麼問題。兩個事件處理中調用onSelect
方法。可是,這個Select
組件中有一個bug
很好的展示了以前的GIF
圖。onSelect
方法永遠傳遞的是以前的state.selection
值,由於當fireOnSelect
調用的時候,setState
尚未完成它的工做。我認爲React
至少要把setState
更名爲scheduleState
或者把回掉函數設爲必須參數。
這個bug很容易修改,最難的地方在於你要知道有這個問題。
setState
形成的第二個問題是:每次調用都會形成從新渲染。不少時候,這些從新渲染是沒必要要的。你能夠用React performance tools
中的printWasted來查看何時會發生沒必要要渲染。可是,大概的說,沒必要要的渲染有如下幾個緣由:
新的state
其實和以前的是同樣的。這個問題一般能夠經過shouldComponentUpdate
來解決。也能夠用pure render
或者其餘的庫來解決這個問題。
一般發生改變的state
是和渲染有關的,可是也有例外。好比,有些數據是根據某些狀態來顯示的。
第三,有些state
和渲染一點關係都沒有。有一些state
多是和事件、timer ID
有關的。
基於上面的最後一條,並非全部的組件狀態都應該用setState
來進行保存和更新的。複雜的組件可能會有各類各樣的狀態須要管理。用setState
來管理這些狀態不但會形成不少不須要的從新渲染,也會形成相關的生命週期鉤子一直被調用,從而形成不少奇怪的問題。
在原文中做者推薦了一個叫作MobX
的庫來管理部分狀態,我不是很感冒,因此我就不介紹。若是感興趣的,能夠經過最上面的連接看看原文中的介紹。
基於上面提出的三點,我認爲新手應該注意的地方是:
setState
是不保證同步的setState
是不保證同步的,是不保證同步的,是不保證同步的。重要的事情說三遍。之因此不說它是異步的,是由於setState
在某些狀況下也是同步更新的。能夠參考這篇文章
若是須要在setState
後直接獲取修改後的值,那麼有幾個方案:
this.state
獲取針對於以前的例子,徹底能夠在調用fireOnSelect
的時候,傳入須要的值。而不是在方法中在經過this.state
來獲取
setState
方法接收一個function
做爲回調函數。這個回掉函數會在setState
完成之後直接調用,這樣就能夠獲取最新的state
。對於以前的例子,就能夠這樣:
this.setState({ selection: value }, this.fireOnSelect)
在setState
使用setTimeout
來讓setState
先完成之後再執行裏面內容。這樣子:
this.setState({ selection: value }); setTimeout(this.fireOnSelect, 0);
直接輸出,回調函數,setTimeout
對比
componentDidMount(){ this.setState({val: this.state.val + 1}, ()=>{ console.log("In callback " + this.state.val); }); console.log("Direct call " + this.state.val); setTimeout(()=>{ console.log("begin of setTimeout" + this.state.val); this.setState({val: this.state.val + 1}, ()=>{ console.log("setTimeout setState callback " + this.state.val); }); setTimeout(()=>{ console.log("setTimeout of settimeout " + this.state.val); }, 0); console.log("end of setTimeout " + this.state.val); }, 0); }
若是val默認爲0, 輸入的結果是:
> Direct call 0 > In callback 1 > begin of setTimeout 1 > setTimeout setState callback 2 > end of setTimeout 2 > setTimeout of settimeout 2
state
中來管理一般state
中只來管理和渲染有關的狀態,從而保證setState
改變的狀態都是和渲染有關的狀態。這樣子就能夠避免沒必要要的重複渲染。其餘和渲染無關的狀態,能夠直接以屬性的形式保存在組件中,在須要的時候調用和改變,不會形成渲染。或者參考原文中的MobX
。
避免沒必要要的修改,當state
的值沒有發生改變的時候,儘可能不要使用setState
。雖然shouldComponentUpdate
和PureComponent
能夠避免沒必要要的重複渲染,可是仍是增長了一層shallowEqual
的調用,形成多餘的浪費。
以上