开篇:通过 state 阐述 React 渲染

前段时间开始着手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

javascript 复制代码
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](#前提:useEffect(() => {}, []) 1只执行一次,不会在组件任何的 props 或 state 发生改变时重新运行。)^只执行一次,不会在组件任何的 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](#根据那一次渲染中 state 的值2)^ 被计算出来的!

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

html 复制代码
<button onClick={() => {
  setNumber(number + 5);
  setTimeout(() => {
    alert(number);
  }, 3000);
}}>+5</button>

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

给 useEeffect 添加响应依赖

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

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

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

javascript 复制代码
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...

延伸:

html 复制代码
<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

javascript 复制代码
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^

javascript 复制代码
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 ↩︎

相关推荐
沉默璇年6 小时前
react中useMemo的使用场景
前端·react.js·前端框架
红绿鲤鱼8 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
loey_ln10 小时前
FIber + webWorker
javascript·react.js
zhenryx10 小时前
前端-react(class组件和Hooks)
前端·react.js·前端框架
老码沉思录14 小时前
React Native 全栈开发实战班 - 性能与调试之打包与发布
javascript·react native·react.js
沉默璇年19 小时前
react中Fragment的使用场景
前端·react.js·前端框架
GISer_Jing20 小时前
React渲染流程与更新diff算法
前端·javascript·react.js
老码沉思录1 天前
React Native 全栈开发实战班 - 性能与调试之内存管理
javascript·react native·react.js
yqcoder1 天前
reactflow 中 reactflowprovider 组件作用
前端·javascript·react.js
前端郭德纲1 天前
ReactNative的环境搭建
javascript·react native·react.js