from:http://www.tuicool.com/articles/zEfEfua
原文: https://medium.com/@mweststra...
作者: Michel Weststrate
前言
這篇文章原標題是 3 Reasons why I stopped using React.setState ,但是我對原文作者提出的論點不是很感冒,但是作者提出的三點對 React
新手來說是很容易忽略的地方,所以我在這里只提出部分內容,而且把標題改為 使用React.setState需要注意的三點 。
正文
對 React
新手來說,使用 setState
是一件很復雜的事情。即使是熟練的 React
開發,也很有可能因為 React
的一些機制而產生一些bug,比如下面這個例子:
文檔 中也說明了當使用 setState
的時候,需要注意什么問題:
注意:
絕對不要 直接改變 this.state
,因為之后調用 setState()
可能會替換掉你做的改
變。把 this.state
當做是不可變的。
setState()
不會立刻改變 this.state
,而是創建一個即將處理的 state
轉變。在調用該方法之后訪問 this.state
可能會返回現有的值。
對 setState
的調用沒有任何同步性的保證,並且調用可能會為了性能收益批量執行。
setState()
將總是觸發一次重繪,除非在 shouldComponentUpdate()
中實現了條件渲染邏輯。如果可變對象被使用了,但又不能在 shouldComponentUpdate()
中實現這種邏輯,僅在新 state
和之前的 state
存在差異的時候調用 setState()
可以避免不必要的重新渲染。
總結出來,當使用 setState
的時候,有三個問題需要注意:
1. 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很容易修改,最難的地方在於你要知道有這個問題。
2. setState會造成不必要的渲染
setState
造成的第二個問題是:每次調用都會造成重新渲染。很多時候,這些重新渲染是不必要的。你可以用 React performance tools
中的 printWasted 來查看什么時候會發生不必要渲染。但是,大概的說,不必要的渲染有以下幾個原因:
-
新的
state
其實和之前的是一樣的。這個問題通常可以通過shouldComponentUpdate
來解決。也可以用pure render
或者其他的庫賴解決這個問題。 -
通常發生改變的
state
是和渲染有關的,但是也有例外。比如,有些數據是根據某些狀態來顯示的。 -
第三,有些
state
和渲染一點關系都沒有。有一些state
可能是和事件、timer ID
有關的。
3.setState並不能很有效的管理所有的組件狀態
基於上面的最后一條,並不是所有的組件狀態都應該用 setState
來進行保存和更新的。復雜的組件可能會有各種各樣的狀態需要管理。用 setState
來管理這些狀態不但會造成很多不需要的重新渲染,也會造成相關的生命周期鈎子一直被調用,從而造成很多奇怪的問題。
后話
在原文中作者推薦了一個叫做 MobX
的庫來管理部分狀態,我不是很感冒,所以我就不介紹。如果感興趣的,可以通過最上面的鏈接看看原文中的介紹。
基於上面提出的三點,我認為新手應該注意的地方是:
setState
是不保證同步的
setState
是不保證同步的,是不保證同步的,是不保證同步的。重要的事情說三遍。之所以不說它是異步的,是因為 setState
在某些情況下也是同步更新的。 可以參考這篇文章
如果需要在 setState
后直接獲取修改后的值,那么有幾個方案:
傳入對應的參數,不通過 this.state
獲取
針對於之前的例子,完全可以在調用 fireOnSelect
的時候,傳入需要的值。而不是在方法中在通過 this.state
來獲取
使用回調函數
setState
方法接收一個 function
作為回調函數。這個回掉函數會在 setState
完成以后直接調用,這樣就可以獲取最新的 state
。對於之前的例子,就可以這樣:
this.setState({ selection: value }, this.fireOnSelect)
使用setTimeout
在 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
改變的狀態都是和渲染有關的狀態。這樣子就可以避免不必要的重復渲染。其他和渲染無關的狀態,可以直接以屬性的形式保存在組件中,在需要的時候調用和改變,不會造成渲染。
避免不必要的修改,當 state
的值沒有發生改變的時候,盡量不要使用 setState
。雖然 shouldComponentUpdate
和 PureComponent
可以避免不必要的重復渲染,但是還是增加了一層 shallowEqual
的調用,造成多余的浪費。
以上