zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    UWP开发必备:常用数据列表控件汇总比较
    CodeForces 372 A. Counting Kangaroos is Fun
    ubuntu 13.10 eclipse 菜单栏不可用的问题
    Codeforces Round #219(Div. 2)373 B. Making Sequences is Fun(二分+找规律)
    Git/Github使用方法小记
    Ubuntu 下jdk的安装
    VIM简明教程
    codeforces 371 C-Hamburgers
    codeforces 371A K-Periodic Array
    计算机网络中IP地址和MAC地址
  • 原文地址:https://www.cnblogs.com/taohuaya/p/13176363.html
Copyright © 2011-2022 走看看