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

原文地址:https://www.cnblogs.com/taohuaya/p/13176363.html