前段时间开始着手React项目的开发,关于React的一些思想也有了一些体会(尤其是同vue之间的差异),特梳理&总结相关内容,便于理解。
✓ 🇨🇳 开篇:通过 state 阐述 React 渲染
React中,有两种原因会导致组件的渲染:
State setter 函数更新变量(状态发生改变)并触发 React 再次渲染组件。
useState
Hook 提供了这两个功能:
「React 组件显示到屏幕,包括三个步骤:」
appendChild()
DOM API 将其创建的所有 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 发生改变时重新运行。
在第一次渲染期间,count
为 0
。
setCount(count + 1)
:count
是 0
所以 setCount(0 + 1)
count
更改为 1
。setCount(count + 1)
:count
是 0
所以 setCount(0 + 1)
count
更改为 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>
结合上述问题,下述提供一些方案 >>>
😹性能较差,每次setInterval都会被销毁&重建(导致 Effect 在每次 count
更改时再次执行 cleanup 和 setup)
useEffect(() => {
const interval = setInterval(() => setCount(count + 1), 1000)
return () => clearInterval(interval)
}, [count])
👍函数式更新,该函数将接收先前的 state ,并返回一个更新后的值。这样定时器每次拿到的是最新的值
useEffect(() => {
const interval = setInterval(() => setCount(v => v + 1), 1000)
return () => clearInterval(interval)
}, [])
React 将更新函数放入 队列 中。然后,在下一次渲染期间,它将按照相同的顺序调用它们:
v => v + 1
将接收 0
作为待定状态,并返回 1
作为下一个状态。v => v + 1
将接收 1
作为待定状态,并返回 2
作为下一个状态。延伸:
<button onClick={() => {
setNumber(number + 5);
setNumber(n => n + 1);
}}>增加数字</button>
setNumber(number + 5)
:number
为 0
,所以 setNumber(0 + 5)
。React 将 “替换为 5
” 添加到其队列中。setNumber(n => n + 1)
:n => n + 1
是一个更新函数。 React 将 该函数 添加到其队列中。总结:
setNumber(n => n + 1)
更新函数。👉useRef
返回一个可变的 ref 对象,返回的 ref 对象在组件的整个生命周期内保持不变。将定时器函数提取出来,每次定时器触发时,都能取到最新到 count
const counterRef: any = useRef(null)
counterRef.current = () => {setCount(count + 1)}
useEffect(() => {
const interval = setInterval(() => counterRef.current(), 1000)
return () => clearInterval(interval)
}, [])
👉使用返回当前最新值的 Hook(ahooks),可以避免闭包问题。
useLatest 返回的永远是最新值3
const latestCountRef = useLatest(count);
useEffect(() => {
const interval = setInterval(() => setCount(latestCountRef.current + 1), 1000)
return () => clearInterval(interval)
}, [])