开篇:通过 state 阐述 React 渲染

news/2024/7/15 19:20:13 标签: react.js, react渲染, react哲学, 快照, ahooks

前段时间开始着手React项目的开发,关于React的一些思想也有了一些体会(尤其是同vue之间的差异),特梳理&总结相关内容,便于理解。

✓ 🇨🇳 开篇:通过 state 阐述 React 渲染

说在前面

React中,有两种原因会导致组件的渲染:

  1. 组件的 初次渲染。
  2. 组件(或者其祖先之一)的 状态发生了改变。

State setter 函数更新变量(状态发生改变)并触发 React 再次渲染组件。

useState Hook 提供了这两个功能:

  1. State 变量 用于保存渲染间的数据。
  2. State setter 函数 更新变量并触发 React 再次渲染组件。

核心要点

「React 组件显示到屏幕,包括三个步骤:」

  1. 触发:
    • 组件的初次渲染。
    • 组件(或者其祖先之一)状态发生了改变。
  2. 渲染组件
    • 在进行初次渲染时, React 会调用根组件。
    • 对于后续的渲染, React 会调用内部状态更新触发了渲染的函数组件。
  3. 提交到DOM
    • 对于初次渲染, React 会使用 appendChild() DOM API 将其创建的所有 DOM 节点放在屏幕上。
    • 对于重渲染, React 将应用最少的必要操作(在渲染时计算!),以使得 DOM 与最新的渲染输出相互匹配。

示例

通过 setInterval 实现每秒+1
在这里插入图片描述

import React, { useState, useEffect } from "react";

export default () => {
  // 定义计数
	const [count, setCount] = useState(0);
  
  /* 需求:实现每1秒+1 */
  useEffect(() => {
    const interval = setInterval(() => setCount(count + 1), 1000)
    return () => clearInterval(interval)
  }, [])
  
 return (
   <>
   		<p>count: {count}</p>
   </>);
}  

上述无法实现想要的效果!setInterval 函数每隔1秒执行一次,但 count 结果一直是1。

在这里插入图片描述

以下是 setInterval 函数通知 React 要做的事情:

前提:useEffect(() => {}, []) 1只执行一次,不会在组件任何的 props 或 state 发生改变时重新运行。

在第一次渲染期间,count0

  1. setCount(count + 1)count0 所以 setCount(0 + 1)
    • React 准备在下一次渲染时将 count 更改为 1
  2. setCount(count + 1)count0 所以 setCount(0 + 1)
    • React 准备在下一次渲染时将 count 更改为 1
  3. 每隔1秒,执行一次上述操作

尽管每1秒调用一次 setNumber(count + 1),但在 这次渲染count 一直是 0,每1秒将 state 设置成 1

一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。它的值在 React 通过调用组件“获取 UI 的快照”时就被“固定”了。

React 执行函数 => 计算快照 => 更新 DOM 树

当 React 调用组件时,它会为特定的那一次渲染提供一张 state 快照。组件会在其 JSX 中返回一张包含一整套新的 props 和事件处理函数的 UI 快照 ,其中所有的值都是 根据那一次渲染中 state 的值2 被计算出来的!

下述例子,更容易说明上述「快照」的含义。点击一次按钮,alert 弹出 0 而不是 5

<button onClick={() => {
  setNumber(number + 5);
  setTimeout(() => {
    alert(number);
  }, 3000);
}}>+5</button>

结合上述问题,下述提供一些方案 >>>

给 useEeffect 添加响应依赖

😹性能较差,每次setInterval都会被销毁&重建(导致 Effect 在每次 count 更改时再次执行 cleanup 和 setup)

useEffect(() => {
	const interval = setInterval(() => setCount(count + 1), 1000)
  return () => clearInterval(interval)
}, [count])
通过更新函数设置 state 值

👍函数式更新,该函数将接收先前的 state ,并返回一个更新后的值。这样定时器每次拿到的是最新的值

useEffect(() => {
	const interval = setInterval(() => setCount(v => v + 1), 1000)
	return () => clearInterval(interval)
}, [])

React 将更新函数放入 队列 中。然后,在下一次渲染期间,它将按照相同的顺序调用它们:

  1. v => v + 1 将接收 0 作为待定状态,并返回 1 作为下一个状态。
  2. v => v + 1 将接收 1 作为待定状态,并返回 2 作为下一个状态。
  3. 实现每1秒加1…

延伸:

<button onClick={() => {
  setNumber(number + 5);
  setNumber(n => n + 1);
}}>增加数字</button>
  1. setNumber(number + 5)number0,所以 setNumber(0 + 5)。React 将 “替换为 5 添加到其队列中。
  2. setNumber(n => n + 1)n => n + 1 是一个更新函数。 React 将 该函数 添加到其队列中。

总结:

  • 设置 state 不会更改现有渲染中的变量,但会请求一次新的渲染。
  • React 会在事件处理函数执行完成之后处理 state 更新。这被称为批处理。
  • 要在一个事件中多次更新某些 state,你可以使用 setNumber(n => n + 1) 更新函数。
借助 ref

👉useRef 返回一个可变的 ref 对象,返回的 ref 对象在组件的整个生命周期内保持不变。将定时器函数提取出来,每次定时器触发时,都能取到最新到 count

const counterRef: any = useRef(null)
counterRef.current = () => {setCount(count + 1)}
useEffect(() => {
	const interval = setInterval(() => counterRef.current(), 1000)
	return () => clearInterval(interval)
}, [])
使用 useLatest hook

👉使用返回当前最新值的 Hook(ahooks),可以避免闭包问题。

useLatest 返回的永远是最新值3

const latestCountRef = useLatest(count);
useEffect(() => {
  const interval = setInterval(() => setCount(latestCountRef.current + 1), 1000)
  return () => clearInterval(interval)
}, [])

参考链接


  1. https://react.docschina.org/learn/lifecycle-of-reactive-effects#what-an-effect-with-empty-dependencies-means 依赖项为空数组的 Effect ↩︎

  2. https://react.docschina.org/learn/state-as-a-snapshot state 如同一张快照 ↩︎

  3. https://ahooks.js.org/zh-CN/hooks/use-latest ahooks useLatest ↩︎


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

相关文章

五种多目标优化算法(NSWOA、MOJS、MOAHA、MOPSO、NSGA2)性能对比(提供MATLAB代码)

一、5种多目标优化算法简介 1.1NSWOA 1.2MOJS 1.3MOAHA 1.4MOPSO 1.5NSGA2 二、5种多目标优化算法性能对比 为了测试5种算法的性能将其求解9个多目标测试函数&#xff08;zdt1、zdt2 、zdt3、 zdt4、 zdt6 、Schaffer、 Kursawe 、Viennet2、 Viennet3&#xff09;&#xff0…

Python | OS模块操作

一、介绍 Python的os模块提供了许多与操作系统交互的函数&#xff0c;可以用于文件和目录的操作、进程管理、环境变量的访问等。以下是os模块的一些常用功能&#xff1a; 文件和目录操作&#xff1a;os模块提供了许多函数来进行文件和目录的操作&#xff0c;如创建文件夹(os.mk…

计算机设计大赛 深度学习动物识别 - 卷积神经网络 机器视觉 图像识别

文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…

板块一 Servlet编程:第八节 文件上传下载操作 来自【汤米尼克的JavaEE全套教程专栏】

板块一 Servlet编程&#xff1a;第八节 文件的上传下载操作 一、文件上传&#xff08;1&#xff09;前端内容&#xff08;2&#xff09;后端内容 二、文件下载&#xff08;1&#xff09;前端的超链接下载&#xff08;2&#xff09;后端下载 在之前的内容中我们终于结束了Servle…

LLM 模型融合实践指南:低成本构建高性能语言模型

编者按&#xff1a;随着大语言模型技术的快速发展&#xff0c;模型融合成为一种低成本但高性能的模型构建新途径。本文作者 Maxime Labonne 利用 mergekit 库探索了四种模型融合方法&#xff1a;SLERP、TIES、DARE和passthrough。通过配置示例和案例分析&#xff0c;作者详细阐…

【Unity】双击txt文件以记事本形式(文本文档)打开

在Unity工程任意Editor文件夹下创建C#脚本&#xff1a;CustomAssetHandler using UnityEngine; using UnityEditor; using UnityEditor.Callbacks;public class CustomAssetHandler {[OnOpenAssetAttribute(1)]public static bool step1(int instanceID, int line){string pat…

Vue路由缓存问题

路由缓存问题的产生 VueRouter允许用户在页面中创建多个视图&#xff08;多级路由&#xff09;&#xff0c;并根据路由参数来动态的切换视图。使用带参数的路由时&#xff0c;相同的组件实例将被重复使用。因为两个路由都渲染同一个组件&#xff0c;比起销毁再创建&#xff0c;…

【2.3深度学习开发任务实例】(1)神经网络模型的特点【大厂AI课学习笔记】

从本章开始&#xff0c;我把标题的顺序变了一下&#xff0c;大厂AI课笔记&#xff0c;放到后面。因为我发现App上&#xff0c;标题无法显示完全。 从本章开始&#xff0c;要学习深度学习开发任务的全部过程了。 我们将通过小汽车识别赛道上的标志牌&#xff0c;给出检测框&am…