react Hook踩坑指北—一文解決你所有關於setState的疑惑


0.前言
目前react已全面擁抱hook,但使用hook進行開發時,你會發現state的值往往跟你想象的不一樣,為什么state會這么奇怪呢,通過以下案例,讓我們一探究竟吧。

1. state類型為Object或Array時,setState無法生效。
說明
當我們state所定義的state類型為Object或Array時,在回調中直接setState是無法成功的,demo如下:

function App() {
  const [obj,setObj] = useState({
    num:1
  });
  const clickMe = () => {
     setObj(v => {
       let newObj = v
       newObj.num = v.num + 1 // 直接修改num的值不成功
      return newObj
     })
  }
    return (
     <button onClick={clickMe}>{obj.num}</button>
    );
}

樣例——此時num的值一直為1。

 

原因
由於Object為引用類型,setState通過回調函數的形式賦值,其參數v存的是obj的地址,此時let newObj = v操作將newObj指向obj的地址,由於react中state是只讀的,因此newObj.num = v.num + 1這個操作相當於obj.num = obj.num +1,因此無法成功。

解決方案
通過淺拷貝或者深拷貝(相關資料網上很多)可解決此問題,將代碼修改如下:

function App() {
  const [obj,setObj] = useState({
    num:1
  });
  const clickMe = () => {
    setObj(v => {
      let newObj = Object.assign({},v)   // 對v進行淺拷貝
      newObj.num = v.num + 1
      return newObj
    })
  }
    return (
     <button onClick={clickMe}>{obj.num}</button>
    );
}

樣例此時newObj指向一個新的拷貝對象,可以任意修改newObj值,原值保持不變。

2. setState后值未立即發生改變

  • 說明

修改state后,如果直接調用此state,你會發現state的值未發生改變,demo如下:

function App() {
  const [num,setNum] = useState(0);
  const clickMe = () => {
    setNum(num+1)
   console.log(num)
  }
    return (
     <button onClick={clickMe}>{num}</button>
    );
}

此時點擊button,第一次button顯示的num值變為1,而后台的num值顯示為0,多次點擊,后台總比視圖要少1。

 

 

 

原因
與react的更新有關,當調用setState時,react是異步更新state的,如果setState后立即獲取state的值,此時state尚未更新,因此為舊的狀態。

解決方案
修改state的同時需要使用state的值時,建議使用函數的方式修改並進行相關的使用操作,將上面的方法修改如下:

function App() {
  const [num,setNum] = useState(0);
  const clickMe = () => {
    setNum(num => {
    let newVal = num + 1
    console.log(newVal)
    return num+1
    })
  }
    return (
     <button onClick={clickMe}>{num}</button>
    );
}

3. 異步獲取的state值不是最新的state的值

  • 說明

當我們在一個異步函數中獲取state值時,如果異步未執行完成時修改這個state的值,異步結束后獲取的值仍然為原來的值,具體demo如下:

function App() {
  const [num, setNum] = useState(0);
  const clickMe = () => {
    setTimeout(() => alert(num), 2000);
  };
  return (
    <>
      <button onClick={clickMe}>click me</button>
      <input
        onChange={e => {
          setNum(e.target.value);
        }}
      />
    </>
  );
}

樣例——在輸入框先輸入一組數字,點擊click me后再輸入幾個數字,彈出的信息為click時的數字。

 

 

 

原因
這是由於函數組件中state是閉包的,因此每次調用函數獲取的state只與當時的值有關(為什么要這樣設計可查看dan的文章:函數式組件與類組件有何不同?)。設想如果setTimeout是一個請求,那么請求成功后我們想要的應該是調用這個函數時的state,但有時候我們就是需要修改后的state,所以我們要使用其他方法去獲取這個值。

解決方案
通過useRef獲取當前值,useRef 返回一個可變的 ref 對象,num變化時修改numRecent.current的值,可將numRecent的值保持最新狀態。

function App() {
 const [num, setNum] = useState(0);
 const numRecent = useRef('');
 const clickMe = () => {
   setTimeout(() => alert(numRecent.current), 2000);
 };
 return (
   <>
     <button onClick={clickMe}>click me</button>
     <input
       onChange={e => {
         numrecent.current = e.target.value;
         setNum(e.target.value);
       }}
     />
   </>
 );
}

樣例-此時state始終與視圖保持一致。

4.利用通用方法避坑
實際開發中會經常遇到如上幾個問題,通過setState修改狀態的同時需要根據新的狀態進行一些操作,比如進行請求,修改obj的結構等,每次都要進行拷貝操作會讓代碼顯得冗余,狀態不一致性也讓人頭痛,因此建議將其簡單封裝為一個通用函數,具體如下:

  const setState = (newState,changeStateFn, callback) => {
    changeStateFn((state) => {
      if(state.constructor === Object) {
        state = Object.assign({},state,newState)
      }
      if(state.construct === Array) {
        state = newState.slice()
      }
      callback(state)
      return state
    })
  }

然后修改第1部分的方法如下:

  const clickMe = () => {
    setState({num:obj.num+1},setObj,(v) =>{
      console.log(v.num)
    })
  }
    return (
     <button onClick={clickMe}>{obj.num}</button>
    );

是不是清晰了很多呢?
附完整代碼:

import React, { Component,useState } from 'react';
import { render } from 'react-dom';

function App() {
  const [obj,setObj] = useState({
    num:1
  });
  const setState = (newState,changeStateFn, callback) => {
    changeStateFn((state) => {
      if(state.constructor === Object) {
        state = Object.assign({},state,newState)
      }
      if(state.construct === Array) {
        state = newState.slice()
      }
      callback(state)
      return state
    })
  }
  const clickMe = () => {
    setState({num:obj.num+1},setObj,(v) =>{
      console.log(v.num)
    })
  }
    return (
     <button onClick={clickMe}>{obj.num}</button>
    );
}

render(<App />, document.getElementById('root'));

 

5. 總結

以上都是開發中經常遇到的問題,希望能夠幫到大家,如果對您有幫助,還請幫忙點個贊呦。

 

轉載:https://blog.csdn.net/qq27229639/article/details/105531459


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM