函数组件useState用法 以及 useState异步回调获取不到最新值

news/2024/7/15 17:14:45 标签: react.js, javascript, 前端

目录

 1、useState用法: 三个参数用法

 2、如何监听state的变化

 3、dispatch更新特点

 4、解决上述demo中使用useState 异步回调获取不到最新值

4.1   下面这种情况的写法,仍获取不到最新值:

4.2   解决方案1:dispatch参数,作为函数。

4.3  解决方案2

4.4  解决方案3


类组件中定义state,通过this.setState更新state里的值。而函数组件中的useState可以使函数组件像类组件一样拥有state。

类组件中通过this.setState更新state,进而改变UI视图。函数组件中通过useState来改变UI视图。

 1、useState用法: 三个参数用法

javascript">import React, { useState } from 'react';

const  A  =  props => {

    const [state, dispatch] = useState(initData)

}
export default A
  • state:目的提供给 UI ,作为渲染视图的数据源。
  • dispatch:改变 state 的函数,推动函数组件渲染的渲染函数

                      第一种:作为非函数,作为新的值,赋予给 state,作为下一次渲染使用

javascript">const [ count, setCount ] = React.useState(0)

// 点击事件
const handleClick=()=>{
   setNumber(1)
}
console.log(count, 'count') // 1

                      第二种:作为函数,这里可以称它为reducer,reducer 参数,是上一次返回最新的 state,返回值作为新的 state。

javascript">const [ number , setNumber ] = React.useState(0)
const handleClick=()=>{
   setNumber((state)=> state + 1)  // state - > 0 + 1 = 1
   setNumber(8)  // state - > 8
   setNumber((state)=> state + 1)  // state - > 8 + 1 = 9
}
  • initData:有两种情况。

                     第一种:作为非函数,作为 state 初始化的值。

javascript">const [count, setCount] = useState(0)

                     第二种:作为函数,函数的返回值作为 useState 初始化的值

javascript">// props中的参数c小于数字5, 返回0,即count=0。 
// c小于数字10,大于5, 返回1,即count=1。
// c大于10,返回2,即count=2

const [count, setCount] = React.useState(() => {
    if(props.c < 5) return 0
    if(props.c < 10) return 1
    return 2
})

 2、如何监听state的变化

类组件 setState 中,有第二个参数 callback 或者是生命周期componentDidUpdate 可以检测监听到 state 改变或是组件更新。

在函数组件中,通过useEffect监听。把 state 作为依赖项传入 useEffect 第二个参数 deps ,但是注意 useEffect 初始化会默认执行一次。

javascript">import React, { useState } from 'react';


export default function A (props){
    const [ number , setNumber ] = useState(0)

    /* 监听 number 变化 */
    React.useEffect(()=>{
        console.log('监听number变化,此时的number是:  ' + number )
    },[ number ])

    const handerClick = ()=>{

        /** 高优先级更新 **/
        ReactDOM.flushSync(()=>{
            setNumber(2) 
        })

        /* 批量更新 */
        setNumber(1) 

        /* 滞后更新 ,批量更新规则被打破 */
        setTimeout(()=>{
            setNumber(3) 
        })
       
    } 
    console.log(number)
    return <div>
        <span> { number }</span>
        <button onClick={ handerClick }>点击</button> 
    </div>
}

//  2
//  监听number变化,此时的number是: 2
//  1
//  监听number变化,此时的number是: 1
//  3
//  监听number变化,此时的number是: 3

 3、dispatch更新特点

在使用useState 有一点值得注意,当调用改变 state 的函数dispatch,函数组件中dispatch 更新效果和类组件是一样的,是获取不到最新的 state 值的

javascript">const [ number , setNumber ] = React.useState(0)
const handleClick = ()=>{

    ReactDOM.flushSync(()=>{
        setNumber(2) 
        console.log(number, '2') 
    })

    setNumber(1) 
    console.log(number, '1')

    setTimeout(()=>{
        setNumber(3) 
        console.log(number, '3')
    })   
}

// 0  '2'
// 0  '1'
// 0  '3'

 4、解决上述demo中使用useState 异步回调获取不到最新值

useState中的dispatch参数,既可以作为非函数,也可以作为函数。

4.1   下面这种情况的写法,仍获取不到最新值:

        本应输出 [0, 1, 2],打印结果却不是

javascript">import React, { useState, useEffect } from 'react';
 
const App = () => {
  const [arr, setArr] = useState([0]);
 
  useEffect(() => {
    console.log(arr);
  }, [arr]);
 
  const handleClick = () => {
    Promise.resolve().then(() => {
      setArr([...arr, 1]); // 此时赋值前 arr 为:[0]
    })
      .then(() => {
        setArr([...arr, 2]); // 此时赋值前 arr 为旧状态仍然为:[0]
      });
  }
 
  return (
    <>
      <button onClick={handleClick}>change</button>
    </>
  );
}
 
export default App;
 
// 输出结果
[0]   useEffect 初始化会默认执行一次
[0,1]
[0,2]





// 例2:
  const [number, setNumber] = useState([0]);

  const handleClick= () => {
    setTimeout(()=>{
      setNumber([...number, 1])
    },100)
    setTimeout(()=>{
      setNumber([...number, 2])
    },500)
  }
  
  useEffect(() => {
    console.log(number, 'number' )
  }, [number]);

return (
    <>
      <button onClick={handleClick}>change</button>
    </>
  );


// [0]  useEffect 初始化会默认执行一次
// [0,1]
// [0,2]

        第一次 setArr 后 arr 的值确实更新了,我们也可以在上面输出结果中看到,但此次执行的 handleClick 事件处理函数作用域还是旧的,里面引用的 arr 仍然为旧的,导致第二次 setArr 后结果为 [0, 2]

4.2   解决方案1:dispatch参数,作为函数。
javascript">import React, { useState, useEffect } from 'react';
 
const App = () => {
  const [arr, setArr] = useState([0]); // 声明一个数组 const arr = [0]
 
  useEffect(() => {
    console.log(arr);
  }, [arr]);
 
  const handleClick = () => {
    Promise.resolve().then(() => {
      setArr(prevState => [...prevState, 1]); // 或者这样写:setArr([...arr, 1]); 
    })
      .then(() => {
        setArr(prevState => [...prevState, 2]); // 这里必须改成回调函数传参方式,否则会读取旧状态,导致异常
      });
  }
 
  return (
    <>
      <button onClick={handleClick}>change</button>
    </>
  );
}
 
export default App;
 
// 输出结果
[0]   useEffect 初始化会默认执行一次
[0,1]
[0,1,2]


// 例2:

const [fields, setfields] = React.useState([0]);
const intervalRef = React.useRef();
function change(){
    setTimeout(()=>{
      setfields([...intervalRef.current, 1])
    },100)
    setTimeout(()=>{
      setfields([...intervalRef.current, 2])
    },500)
}

React.useEffect(() => {
    intervalRef.current = fields;
    console.log(fields, '123' )
}, [fields]);


// [0]  '123'    useEffect 初始化会默认执行一次
// [0,1]  '123'
// [0,1,2]  '123'
4.3  解决方案2

        利用 ref ,state 发生改变同时将值映射到 ref, ref 的改变不会触发页面更新,但在异步中一定能拿到最新值,所以需要在页面上用就使用 state,在异步逻辑中用就使用 ref

javascript">import React, { useState, useRef, useEffect } from 'react';
 
const App = () => {
  const [arr, setArr] = useState([0]);

  let refArr = useRef(); // 1. 声明一个refArr

  useEffect(() => {
    refArr.current = arr; // 2. 把最新的arr的值赋值给refArr.current。第一次时refArr.current = [0]
    console.log(arr);
  }, [arr]);

 
  const handleClick = () => {

    Promise.resolve().then(() => {
      const now = [...refArr.current, 1]; // 3. ...拓展运算符展开refArr.current,然后再与1进行合并,声明一个变量now接受新合并的值,得到 const now = [0,1],
      setArr(now);  // 4. 通过setArr更新arr数组
      refArr.current = now;  // 5. 这一步代码必须有,也是最关键的一步,这一步代码中把拿到最新now的值 再 重新赋值给refArr.current,保证了第6步中2能添加进去。如果不写这一步,里面引用的 arr 仍然为旧的,导致第二次 setArr 后结果为 [0, 2]
    })
      .then(() => {
        setArr([...ref.current, 2]);  // 6. ...拓展运算符展开refArr.current,然后再与2进行合并
      });
  }
 
  return (
    <>
      <h1>{arr.toString()}</h1>
      <button onClick={handleClick}>change</button>
    </>
  );
}
 
export default App;

// [0]   useEffect 初始化会默认执行一次
// [0,1]
// [0,1,2]



// 例2 
const [fields, setfields] = React.useState([0]);
const intervalRef = React.useRef();

React.useEffect(() => {
    intervalRef.current = fields;
    console.log(fields, '123' )
}, [fields]);

function change(){
    setTimeout(()=>{
      setfields([...intervalRef.current, 1])
    },100)
    setTimeout(()=>{
      setfields([...intervalRef.current, 2])
    },500)
}


// [0]  '123'
// [0,1]  '123'
// [0,1,2]  '123'
4.4  解决方案3

        优化方案2:

封装一个 hooks 将 state 和 ref 进行关联,同时再提供一个方法供异步中获取最新值使用,例如:

javascript">const useGetState = (initVal) => {

  const [state, setState] = useState(initVal);
  const ref = useRef(initVal);

  const setStateCopy = (newVal) => {
    ref.current = newVal;
    setState(newVal);
  }
  const getState = () => ref.current;
  return [state, setStateCopy, getState];
}
 
const App = () => {
  const [arr, setArr, getArr] = useGetState([0]);

  useEffect(() => {
    console.log(arr);
  }, [arr]);
 
  const handleClick = () => {
    Promise.resolve().then(() => {
      setArr([...getArr(), 1]);
    })
      .then(() => {
        setArr([...getArr(), 2]);
      });
  }
 
  return (
    <>
      <h1>{arr.toString()}</h1>
      <button onClick={handleClick}>change</button>
    </>
  );
}

http://www.niftyadmin.cn/n/5343651.html

相关文章

Java:获取pdf中某一页的宽度和高度

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、背景介绍 二、用Java中的方法 三、用Python中的方法 总结 提示&#xff1a;以下是本篇文章正文内容&#xff0c;下面案例可供参考 一、背景介绍 金融业务&…

小蓝和小桥的挑战*

题目 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int t sc.nextInt();sc.nextLine();while(t-- > 0) {int n sc.nextInt();sc.nextLine();int[] a new int[n];for(int i0;i<n;i)a[i…

Lua简介和应用场景介绍

Lua 的介绍 起源&#xff1a;Lua 于 1993 年在巴西里约热内卢的天主教大学&#xff08;PUC-Rio&#xff09;由 Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo 开发。 设计目的&#xff1a;Lua 设计的主要目标是为了嵌入到其他应用程序中&#xff0c;…

前端开发WebStorm

WebStorm是一款功能强大的JavaScript集成开发环境&#xff0c;凭借智能代码补全、实时分析和代码重构、集成版本控制、强大的调试和测试工具、实时预览和集成前端工具以及自定义配置和插件支持等功能&#xff0c;成为开发者首选的利器。 前端开发WebStorm WebStorm是一款功能强…

【Linux工具篇】软件包管理器yum

目录 什么是软件包 什么是yum Linux系统的生态 yum的相关操作 如何搜索软件 如何安装软件 如何卸载软件 关于rzsz rz&window->Linux sz&Linux->windows wget&scp&Linux<->Linux yum本地配置 如何配置&#xff1f; 有趣好玩的Linux …

整理了一下常用的LaTeX数学公式语法,未完待续

为了方便对应&#xff0c;后面会拆一下 公式代码放入LaTeX编译环境中时&#xff0c;两边需要加入$$: $$公式代码$$ 1&#xff0c;分解示例 L^{A}T_{E}X\,2_{\epsilon} c^{2}a^{2}b^{2} \tau\phi \cos2\pi1 f\, \,a^{x}\,\,b \heartsuit \cos^{2}\theta \sin^{2}\theta 1.0…

Pycharm运行提示(运行‘Python测试(00.py内)‘(u)

为什么有时候我在pycharm中运行代码会出现图片中的问题&#xff1f; 我们该如何改过来&#xff1f; 很简单 点击文件-设置 点击Python集成工具&#xff0c;在默认测试运行程序里修改为Unittest即可 再次运行代码就会显示正常的运行 你的pycharm可能是英文 如何英文变中文&…

在x86上安装fastdeploy-TRT版本

文章目录 安装显卡驱动和cuda安装cudnn安装TensorRT源码编译可能遇到的问题nvcc fatal : Unsupported gpu architecture compute_35参考安装显卡驱动和cuda 进入官网进行下载