1. 程式人生 > >react的setState使用中遇到的問題

react的setState使用中遇到的問題

dir ret man 重復 prop length 根據 sync 函數

setState()更新的數據和自己預期的不一致

對 React 新手來說,使用 setState 是一件很復雜的事情。即使是熟練的 React 開發,也很有可能因為 React 的一些機制而產生一些bug,react文檔 中也說明了當使用 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 。 onSelect 方法永遠傳遞的是之前的 state.selection 值,因為當 fireOnSelect 調用的時候, setState 還沒有完成它的工作。我認為 React 至少要把 setState 改名為 scheduleState 或者把回掉函數設為必須參數。

2. setState會造成不必要的渲染

setState 造成的第二個問題是:每次調用都會造成重新渲染。很多時候,這些重新渲染是不必要的。你可以用 React performance tools 中的 printWasted 來查看什麽時候會發生不必要渲染。但是,大概的說,不必要的渲染有以下幾個原因:

  • 新的 state 其實和之前的是一樣的。這個問題通常可以通過 shouldComponentUpdate 來解決。也可以用 pure render 或者其他的庫賴解決這個問題。
  • 通常發生改變的 state 是和渲染有關的,但是也有例外。比如,有些數據是根據某些狀態來顯示的。
  • 第三,有些 state 和渲染一點關系都沒有。有一些 state 可能是和事件、 timer ID 有關的。

3.setState並不能很有效的管理所有的組件狀態

基於上面的最後一條,並不是所有的組件狀態都應該用 setState 來進行保存和更新的。復雜的組件可能會有各種各樣的狀態需要管理。用 setState 來管理這些狀態不但會造成很多不需要的重新渲染,也會造成相關的生命周期鉤子一直被調用,從而造成很多奇怪的問題。

總結

基於上面提出的三點,我認為新手應該註意的地方是:

setState 是不保證同步的

setState 是不保證同步的,是不保證同步的,是不保證同步的。重要的事情說三遍。之所以不說它是異步的,是因為 setState 在某些情況下也是同步更新的。 可以參考這篇文章

如果需要在 setState 後直接獲取修改後的值,那麽有幾個方案:

1.傳入對應的參數,不通過 this.state 獲取

針對於之前的例子,完全可以在調用 fireOnSelect 的時候,傳入需要的值。而不是在方法中在通過 this.state 來獲取

2.使用回調函數

setState 方法接收一個 function 作為回調函數。這個回掉函數會在 setState 完成以後直接調用,這樣就可以獲取最新的 state 。對於之前的例子,就可以這樣:

this.setState({
  selection: value
}, this.fireOnSelect)

3.使用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

4.和渲染無關的狀態盡量不要放在 state 中來管理

通常 state 中只來管理和渲染有關的狀態 ,從而保證 setState 改變的狀態都是和渲染有關的狀態。這樣子就可以避免不必要的重復渲染。其他和渲染無關的狀態,可以直接以屬性的形式保存在組件中,在需要的時候調用和改變,不會造成渲染。

避免不必要的修改,當 state 的值沒有發生改變的時候,盡量不要使用 setState 。雖然 shouldComponentUpdatePureComponent 可以避免不必要的重復渲染,但是還是增加了一層 shallowEqual 的調用,造成多余的浪費。

參考:https://www.tuicool.com/articles/zEfEfua

react的setState使用中遇到的問題