当前位置: 首页 > article >正文

【React】useState及底层处理机制

目录

  • useState
  • useState底层处理机制
    • useState的简写源码
    • useState打印值的问题
    • 批量处理set修改状态值的方法
    • useState函数更新和优化机制
      • 1、循环修改状态值的方法
      • 2、使用flushSync
      • 3、设置修改状态的方法为函数形式(setX(prev=> prev+1))
      • 4、组件更新的惰性处理
        • 案例1
        • 案例2
        • 惰性处理的相关`useState`源码
  • 和React16的区别
  • 好书推荐

在这里插入图片描述

useState

useState:React Hook函数之一,目的是在函数组件中使用状态,并且后期基于状态的修改,可以让组件更新。

  let [num,setNum] = useState(initialValue);

执行useState,传递的initialValue是初始的状态值。执行这个方法,返回结果是一个数组:[状态值,修改状态的方法]

  • num变量存储的是:获取的状态值
  • setNum变量存储的是:修改状态的方法
  • 执行 setNum(value)
  • 修改状态值为value
  • 通知视图更新
const Demo = function Demo() {
    let [num, setNum] = useState(0);

    const handle = () => {
        setNum(num + 10);
    };
    return <div className="demo">
        <span className="num">{num}</span>
        <Button type="primary" size="small" onClick={handle}>新增</Button>
    </div>;
};

useState底层处理机制

函数组件「或者Hooks组件」不是类组件,所以没有实例的概念「调用组件不再是创建类的实例,而是把函数执行,产生一个私有上下文而已」,所以,在函数组件中不涉及this的处理!

函数组件的每一次渲染(或者是更新),都是把函数(重新)执行,产生一个全新的私有上下文

  • 其内部的代码也需要重新执行。
  • 涉及的函数需要重新的构建(这些函数的作用域(函数执行的上级上下文),是一块执行DEMO产生的闭包)
  • 执行useState,只有第一次,设置的初始值会生效,以后执行获取的状态都是最新的状态值「不是初始值」
  • 返回的修改状态的方法,每一次都是返回一个新的

useState的简写源码

var _state;
function useState(initialValue) {
    if (typeof _state === "undefined") {
        if(typeof initialValue==="function"){
            _state = initialValue();
        }else{
            _state = initialValue
        }
    };
    return [_state, setState];
}

useState打印值的问题

为什么在2s后打印出num是0?
num不是定时器私有的变量,所以在获取变量值时就往上找,接着找到handle函数生成私有上下文,发现么有num,就继续往上找,最终发现num为0。
要确定找的作用域,找哪个闭包


const Demo = function Demo() {
    let [num, setNum] = useState(0);

    const handle = () => {
        setNum(100);
        setTimeout(() => {
            console.log(num); // 0
        }, 2000);
    };
    return <div className="demo">
        <span className="num">{num}</span>
        <Button type="primary"
            size="small"
            onClick={handle}>
            新增
        </Button>
    </div>;
};

在这里插入图片描述

批量处理set修改状态值的方法

点击新增按钮之后,页面只会渲染一次


const Demo = function Demo() {
    console.log('RENDER渲染');
    let [x, setX] = useState(10),
        [y, setY] = useState(20),
        [z, setZ] = useState(30);

    const handle = () => {
        setX(x + 1);
        setY(y + 1);
        setZ(z + 1);
    };
    return <div className="demo">
        <span className="num">x:{x}</span>
        <span className="num">y:{y}</span>
        <span className="num">z:{z}</span>
        <Button type="primary"
            size="small"
            onClick={handle}>
            新增
        </Button>
    </div>;
};

在这里插入图片描述

把多个修改状态值的方法放在定时器中,点击按钮也是只渲染一次

  const handle = () => {
        setTimeout(() => {
            setX(x + 1);
            setY(y + 1);
            setZ(z + 1);
        }, 1000)
    };

使用flushSync,此时点击按钮,页面会渲染两次

  const handle = () => {
            setX(x + 1);
            setY(y + 1);
            flushSync();
            setZ(z + 1);
    };

在这里插入图片描述

useState函数更新和优化机制

1、循环修改状态值的方法

在事件中for循环执行 setX(x+1),页面最终显示11
每一次执行handle都会创建新的闭包,而setX(x+1)中的x的值 向上级上下文找,上级上下文中x的值为10,最终结果为1

const Demo = function Demo() {
    console.log('RENDER渲染');
    let [x, setX] = useState(10);

    const handle = () => {
        for (let i = 0; i < 10; i++) {
            setX(x+1);
        }
    };
    return <div className="demo">
        <span className="num">x:{x}</span>
        <Button type="primary"
            size="small"
            onClick={handle}>
            新增
        </Button>
    </div>;
};

在这里插入图片描述

2、使用flushSync

点击新增按钮时,会渲染两次,正常情况应该是只有一次渲染,说明在执行handle时,可能没有识别出x的值已经变成11。

const Demo = function Demo() {
    console.log('RENDER渲染');
    let [x, setX] = useState(10);

    const handle = () => {
        for (let i = 0; i < 10; i++) {
           flushSync(()=>setX(x+1)) 
        }
    };

    return <div className="demo">
        <span className="num">x:{x}</span>
        <span className="num">y:{y}</span>
        <span className="num">z:{z}</span>
        <Button type="primary"
            size="small"
            onClick={handle}>
            新增
        </Button>
    </div>;
};

点击按钮之后,第一次循环setX(11)执行,视图渲染;第二次循环还是setX(11),由于跟初始值一样(第一次循环之后,初始值就变成了11),所以在此执行setX(11)不回更新视图。这是因为useState 自带了性能优化的机制:

  • 每一次修改状态值的时候,会拿最新的修改的值和之前的状态的值做比较「类似Object.is做比较」
  • 如果发现两次的值是一样的,则不会修改状态,也不会让视图更新「可以理解为:类型PureComponent,在shouldComponentUpdate」中做了浅比较和优化」

在这里插入图片描述

在这里插入图片描述

3、设置修改状态的方法为函数形式(setX(prev=> prev+1))

这里我们要求handle函数执行一次,结果为20,如何处理。可以将setX写成函数形式。

setX(prev=> {
 // prev:存储上一次的状态值
  console.log(prev)
  return prev+1
})
const Demo = function Demo() {
    console.log('RENDER渲染');
    let [x, setX] = useState(10);

    const handle = () => {
        for (let i = 0; i < 10; i++) {
            setX(prev => {
                // prev:存储上一次的状态值
                console.log(prev);
                return prev + 1; //返回的信息是我们要修改的状态值
            });
        }
    };
    return <div className="demo">
        <span className="num">x:{x}</span>
        <Button type="primary"
            size="small"
            onClick={handle}>
            新增
        </Button>
    </div>;
};

点击新增按钮之后,打印prev如下:
在这里插入图片描述
在这里插入图片描述

4、组件更新的惰性处理

每一次函数组件更新时,初始化的一些逻辑都会再次执行,但是其实并没有什么用处。
useState的惰性初始化是指在组件的初次渲染时,状态的初始值不一定是直接提供给useState的常量或变量,而是可以通过一个函数来惰性计算。这种机制可以有效优化性能,特别是当初始状态计算较为复杂时。

适用场景:
惰性初始化最有用的场景是计算初始状态值较为昂贵时。例如,假设你需要从本地存储或计算一个复杂的值作为初始状态,而这个过程并不需要每次渲染时都执行,那么使用惰性初始化可以提高性能,因为它只会在组件的第一次渲染时执行

  • 第一次渲染时:React会调用传给useState的函数(如果传了函数),并将该函数的返回值作为初始状态值。
    如果没有传递函数,React直接使用传入的常量值作为初始状态。
  • 后续渲染时:在后续渲染中,React不会再次调用这个函数,而是直接使用第一次渲染时计算出来的状态值。
    只有当状态发生变化时,setState才会触发组件的重新渲染。
案例1

父组件:

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <ConfigProvider locale={zhCN}>
       <Demo x={10} y={20}/>
    </ConfigProvider>
);

子组件:


const Demo = function Demo(props) {
    let { x, y } = props,
    total = 0;
    for (let i = x; i <= y; i++) {
            total += +String(Math.random()).substring(2);
        }
        
    let [num, setNum] = useState( total);

    const handle = () => {
        setNum(1000);
    };
    return <div className="demo">
        <span className="num">{num}</span>
        <Button type="primary"
            size="small"
            onClick={handle}>
            新增
        </Button>
    </div>;
};

比如上面的写法,每次函数组件更新的时候,都会执行一些无用代码。

  let { x, y } = props,
    total = 0;
    for (let i = x; i <= y; i++) {
            total += +String(Math.random()).substring(2);
     }
        

当组件更新时,复杂的逻辑不再重新执行,设置初始值的时候,可以使用惰性写法,只有第一次渲染组件处理这些逻辑,以后组件更新这样的逻辑就不会再运行了。

const Demo = function Demo(props) {
    // 我们需要把基于属性传递进来的x/y,经过其他处理的结果作为初始值
    // 此时我们需要对初始值的操作,进行惰性化处理:只有第一次渲染组件处理这些逻辑,以后组件更新,这样的逻辑就不要再运行了!!
    let [num, setNum] = useState(() => {
        let { x, y } = props,
            total = 0;
        for (let i = x; i <= y; i++) {
            total += +String(Math.random()).substring(2);
        }
        return total;
    });

    const handle = () => {
        setNum(1000);
    };
    return <div className="demo">
        <span className="num">{num}</span>
        <Button type="primary"
            size="small"
            onClick={handle}>
            新增
        </Button>
    </div>;
};

案例2
import React, { useState } from 'react';

const Demo = () => {
    const [count, setCount] = useState(() => {
        // 假设从本地存储获取初始值
        const savedValue = localStorage.getItem('count');
        return savedValue ? parseInt(savedValue) : 0;
    });

    const increment = () => {
        setCount(count + 1);
    };

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={increment}>Increment</button>
        </div>
    );
};

在这个例子中,useState的初始值是通过一个函数来计算的,该函数在第一次渲染时读取localStorage中的值。如果存在这个值,就使用它作为初始值,否则使用0。这个计算只会在第一次渲染时执行,而不会在后续渲染时重复执行

惰性处理的相关useState源码
var _state;
function useState(initialValue) {
    if (typeof _state === "undefined") {
        if(typeof initialValue==="function"){
            _state = initialValue();
        }else{
            _state = initialValue
        }
    };
    var setState = function setState(value) {
        if(Object.is(_state,value)) return;
        if(typeof value==="function"){
            _state = value(_state);
        }else{
            _state = value;
        }
        // 通知视图更新
    };
    return [_state, setState];
}

和React16的区别

注意:在React 16中,也和this.setState一样,放在合成事件中,是异步的;放在其他的一步操作中(比如:定时器,手动的事件绑定)它是同步的。

下面是异步的操作,跟React 18一样,点击按钮更新一次

在这里插入图片描述

此时是同步的,点击按钮会渲染三次
在这里插入图片描述
此时更新两次,和React18一样

在这里插入图片描述

简单总结一下:在这里插入图片描述

好书推荐

鸿蒙HarmonyOS NEXT开发之路 卷1:ArkTS语言篇

《鸿蒙HarmonyOS NEXT开发之路 卷1:ArkTS语言篇》为有志于掌握HarmonyOS NEXT应用开发的读者提供系统性学习资源,从语法讲解到性能优化全面覆盖,可以作为读者学习ArkTS语言和开发HarmonyOS应用的参考教材。

《鸿蒙HarmonyOS NEXT开发之路 卷1:ArkTS语言篇》全面、深入地介绍华为HarmonyOS NEXT操作系统中的ArkTS语言。《鸿蒙HarmonyOS NEXT开发之路 卷1:ArkTS语言篇》分为基础知识、ArkTS进阶和高级特性三部分,引领读者逐步掌握从ArkTS基础到高级特性的开发能力。基础知识部分涵盖ArkTS的核心语法,包括声明式UI、函数、类、接口、泛型类型、空安全和模块化开发,为读者打下坚实的开发基础。ArkTS进阶部分深入探讨ArkTS语言的高级特性和最佳实践,例如高性能编程、声明式UI描述、自定义组件和装饰器,全面提升读者在HarmonyOS NEXT平台上的开发能力。高级特性部分则聚焦于状态管理机制,详细讲解状态变量的声明和管理,以及它们在UI渲染中的实际应用,帮助读者优化应用性能,实现从TypeScript到ArkTS的平滑过渡。

在这里插入图片描述


http://www.kler.cn/a/585339.html

相关文章:

  • 一篇博客搞定时间复杂度
  • Pytorch的入门
  • Java 8 + Tomcat 9.0.102 的稳定环境搭建方案,适用于生产环境
  • 使用curl随机间隔访问URL-使用curl每秒访问一次URL-nginx
  • Vue配置和安装教程(2025最新)
  • CGI程序处理每一帧VDEC视频数据并输出到HTML页面
  • 【Unity】TextMesh Pro显示中文部分字体异常
  • Cascadeur-3D关键帧动画软件
  • Redis--zset类型
  • 信号处理抽取多项滤波的数学推导与仿真
  • 警惕!Ollama大模型工具的安全风险及应对策略
  • Webpack 和 Vite 的主要区别
  • C# net deepseek RAG AI开发 全流程 介绍
  • flinkOracleCdc源码介绍
  • Python 与 sklearn 库:轻松构建 KNN 算法双版本
  • 如何撰写一份清晰专业的软件功能测试报告
  • Vue项目搜索引擎优化(SEO)终极指南:从原理到实战
  • JVM 垃圾回收器的选择
  • 海量数据查询加速:Presto、Trino、Apache Arrow
  • 在Vue3中集成XGPlayer视频播放器的完整指南