开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情
概念
快照
“正在渲染” 就意味着 React 正在调用你的组件就是一个函数。你从该函数返回的 JSX 就像是 UI 的一张及时的快照。它的 props、事件处理函数和内部变量都是 根据当前渲染时的 state 被计算出来的。
State 是一种类似快照的一种状态
state 可以看做是一个特殊变量,不会在函数调用以后销毁。
组件渲染过程
-
触发 一次渲染(把客人的点单分发到厨房)
-
组件的 初次渲染。
通过调用目标 DOM 节点的
createRoot,然后用你的组件调用render函数完成 -
组件(或者其祖先之一)的 状态发生了改变
-
-
渲染 组件(在厨房准备订单)
- 对于初次渲染, React 会调用根组件。
- 对于重新渲染, React 会调用内部状态更新触发了渲染的函数组件。
-
提交 到 DOM(将菜品放在桌子上)
-
对于初次渲染, React 会使用
appendChild()DOM API 将其创建的所有 DOM 节点放在屏幕上。 -
对于重新渲染, React 将应用最少的必要操作(在渲染时计算),以使得 DOM 与最新的渲染输出相互匹配。
-
React 仅在渲染之间存在差异时才会更改 DOM 节点。
-
-
在渲染完成并且 React 更新 DOM 之后,浏览器就会重新渲染屏幕
使用
const [state, setState] = useState(initialState);
读取 State
直接读取变量
更新 State
惰性初始 state
initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略
传递纯函数本身不能有参数然后返回相关数据
const [state, setState] = useState(someExpensiveComputation);
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
渲染过程中函数内部直接修改 state
- 大部分情况都用不到
- 必须使用判断条件并且内部要修改判断条件
- 只能更新当前组件里面的 state
- set function 纯函数
函数内部修改 state => 组件重新渲染(更改后的 state) => 渲染子组件
import { useState } from 'react';
export default function CountLabel({ count }) {
const [prevCount, setPrevCount] = useState(count);
const [trend, setTrend] = useState(null);
if (prevCount !== count) {
setPrevCount(count);
setTrend(count > prevCount ? 'increasing' : 'decreasing');
}
return (
<>
<h1>{count}</h1>
{trend && <p>The count is {trend}</p>}
</>
);
}
普通更新
const [state, setState] = useState(initialState);
<button onClick={() => setCount(nextState)}>Reset</button>
函数式更新
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
更新 state 中的对象
你不应该直接改变你在 React 状态中持有的对象和数组。需要创建一个新的对象(或复制现有的对象),然后用这个副本来更新状态。
const [person, setPerson] = useState({
name: "Niki de Saint Phalle",
artwork: {
title: "Blue Nana",
},
});
setPerson({
...person,
name: e.target.value,
});
更新 state 中的数组
| 避免使用 (会改变原始数组) | 推荐使用 (会返回一个新数组) | |
|---|---|---|
| 添加元素 | push,unshift | concat,[...arr] 展开语法(例子) |
| 删除元素 | pop,shift,splice | filter,slice(例子) |
| 替换元素 | splice,arr[i] = ... 赋值 | map(例子) |
| 排序 | reverse,sort | 先将数组复制一份(例子) |
如何思考编写 state 逻辑
-
罗列出组件中不同的 state 状态
-
确定如何触发 state 的改变
- 人为输入。比如点击按钮、在表单中输入内容,或导航到链接。通常需要 事件处理函数
- 计算机输入。比如网络请求得到反馈、定时器被触发,或加载一张图片。
-
使用 useState
-
删除任何不必要的 state 变量
- state 是否存在矛盾
- state 是否存在包含关系
- 是否可以通过另一个 state 变量的相反值
-
事件处理函数去设置 state
state 组织原则
-
将相关的状态分组。
如果你总是同时更新两个或更多的状态变量,考虑将它们合并成一个状态变量。
例如,如果你有一个包含多个字段的表单,那么有一个值为对象的 state 变量比每个字段对应一个 state 变量更方便。
const [userInfo, setUserInfo] = useState({ firstName, lastName, school, age, address }); setUserInfo(s=> ({ ...s, fristName, })) -
避免状态中的矛盾。
当状态的结构是几块状态可能相互矛盾和 "不一致 "的时候,你就为错误留下了空间。
-
避免多余的状态。
如果你可以在渲染过程中从组件的 props 或其现有的状态变量中计算出不应该把这些信息放到该组件的状态中。
在项目中同一个数据,保证只存储在一个地方。
-
不要既存在 redux 中,又在组件中定义了一个 state 存储。
-
不要既存在父级组件中,又在当前组件中定义了一个 state 存储。
-
不要既存在 url query 中,又在组件中定义了一个 state 存储。
-
-
避免状态的重复。
当相同的数据在多个状态变量之间或在嵌套对象中重复时,就很难保持它们的同步。
-
避免深度嵌套的状态。
深度分层的状态在更新时不是很方便。
命名惯例
通常可以通过相应 state 变量的第一个字母来命名更新函数的参数
setEnabled(e => !e);
setLastName(ln => ln.reverse());
setFriendCount(fc => fc * 2);
注意
不要依赖 props 或者 state 来更新下一次状态
不要直接修改 State
State 相互独立
设置 state 只会触发一次重新渲染,不会改变你已有的状态变量
在 下一次 渲染改变 state 的值, 异步处理函数一样。
state 相同跳过更新
state 相同不会重新渲染自身和子组件,不会触发 effect
多个 setState() 调用会合并成一次调用
- 所有事件处理函数执行完然后调用 set functions 更新视图
- React 不会跨 多个 需要刻意触发的事件(如点击)进行批处理
- 提前更新视图使用
flushSync
状态更新函数重新渲染之前读取最新的 state 使用
- 必须是纯函数
- 参数是之前的 state ,返回下一次 state
- 调用 setState => 更新函数放入一个队列 => 重新渲染组件 => 根据所有队列更新函数计算新的 state
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
| 更新队列 | n | 返回值 |
|---|---|---|
n => n + 1 | 0 | 0 + 1 = 1 |
n => n + 1 | 1 | 1 + 1 = 2 |
n => n + 1 | 2 | 2 + 1 = 3 |
setNumber(number + 5);
setNumber(n => n + 1);
| 更新队列 | n | 返回值 |
|---|---|---|
“替换为 5” | 0(未使用) | 5 |
n => n + 1 | 5 | 5 + 1 = 6 |
setNumber(number + 5);
setNumber(n => n + 1);
setNumber(42);
| 更新队列 | n | 返回值 |
|---|---|---|
“替换为 5” | 0(未使用) | 5 |
n => n + 1 | 5 | 5 + 1 = 6 |
“替换为 42” | 6(未使用) | 42 |
state 与树中的某个位置的关系
UI 树
React 根据你的 JSX 生成 UI 树。React DOM 根据 UI 树去更新浏览器的 DOM 元素。
条件渲染组件会重置 state
// setShowB 改变 Counter 组件内部 state 会丢弃。
showB && <Counter />
组件 props 改变 state 不会改变
组件 props 改变如何重置 state
-
将组件渲染在不同的位置
{isPlayerA && <Counter person="Taylor" /> } {!isPlayerA && <Counter person="Sarah" /> } -
使用
key赋予每个组件一个明确的身份{isPlayerA ? ( <Counter key="Taylor" person="Taylor" /> ) : ( <Counter key="Sarah" person="Sarah" /> )}
为被移除的组件保留 state
- 可以把所有数据渲染出来,使用 CSS 把其他聊天隐藏起来。
- 你可以进行 状态提升 并在父组件中保存每个收件人的草稿消息。
- 你可以进行 状态提升 并在父组件中保存每个收件人的草稿消息。
问答
为什么更新了 state,console.log 却是原来的数据
传递给 set function 之前获取
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1
为什么更新了 state, 界面没有更新
不要直接修改state 里面的对象
obj.x = 10; // 🚩 Wrong: mutating existing object
setObj(obj); // 🚩 Doesn't do anything
Too many re-renders 错误
事件处理函数使用错误
// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>
// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>
// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button>
初始化逻辑运行了两次
组件内部,初始化逻辑里,set function 里面不是纯函数了
setTodos(prevTodos => {
// 🚩 Mistake: mutating state
prevTodos.push(createTodo());
});
state 设置为函数,但是它却执行
写法错误
const [fn, setFn] = useState(() => someFunction);
function handleClick() {
setFn(() => someOtherFunction);
}
原理
React 如何知道返回哪个 state
在 React 内部,为每个组件保存了一个数组,其中每一项都是一个 state 对。它维护当前 state 对的索引值,在渲染之前将其设置为 “0”。每次调用 useState 时,React 都会为你提供一个 state 对并增加索引值。你可以在文章 React Hooks: not magic, just arrays中阅读有关此机制的更多信息。
<button id="nextButton">
Next
</button>
<h3 id="header"></h3>
<button id="moreButton"></button>
<p id="description"></p>
<img id="image">
<style>
* { box-sizing: border-box; }
body { font-family: sans-serif; margin: 20px; padding: 0; }
button { display: block; margin-bottom: 10px; }
</style>
let componentHooks = [];
let currentHookIndex = 0;
// useState 在 React 中是如何工作的(简化版)
function useState(initialState) {
let pair = componentHooks[currentHookIndex];
if (pair) {
// 这不是第一次渲染
// 所以 state pair 已经存在
// 将其返回并为下一次 hook 的调用做准备
currentHookIndex++;
return pair;
}
// 这是我们第一次进行渲染
// 所以新建一个 state pair 然后存储它
pair = [initialState, setState];
function setState(nextState) {
// 当用户发起 state 的变更,
// 把新的值放入 pair 中
pair[0] = nextState;
updateDOM();
}
// Store the pair for future renders
// and prepare for the next Hook call.
// 存储这个 pair 用于将来的渲染
// 并且为下一次 hook 的调用做准备
componentHooks[currentHookIndex] = pair;
currentHookIndex++;
return pair;
}
function Gallery() {
// 每次调用 useState() 都会得到新的 pair
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculptureList[index];
// 这个例子没有使用 React,所以
// 返回一个对象而不是 JSX
return {
onNextClick: handleNextClick,
onMoreClick: handleMoreClick,
header: `${sculpture.name} by ${sculpture.artist}`,
counter: `${index + 1} of ${sculptureList.length}`,
more: `${showMore ? "Hide" : "Show"} details`,
description: showMore ? sculpture.description : null,
imageSrc: sculpture.url,
imageAlt: sculpture.alt,
};
}
function updateDOM() {
// 在渲染组件之前
// 重置当前 Hook 的下标
currentHookIndex = 0;
let output = Gallery();
// 更新 DOM 以匹配输出结果
// 这部分工作由 React 为你完成
nextButton.onclick = output.onNextClick;
header.textContent = output.header;
moreButton.onclick = output.onMoreClick;
moreButton.textContent = output.more;
image.src = output.imageSrc;
image.alt = output.imageAlt;
if (output.description !== null) {
description.textContent = output.description;
description.style.display = "";
} else {
description.style.display = "none";
}
}
let nextButton = document.getElementById("nextButton");
let header = document.getElementById("header");
let moreButton = document.getElementById("moreButton");
let description = document.getElementById("description");
let image = document.getElementById("image");
let sculptureList = [
{
name: "Homenaje a la Neurocirugía",
artist: "Marta Colvin Andrade",
description:
"Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.",
url: "https://i.imgur.com/Mx7dA2Y.jpg",
alt: "A bronze statue of two crossed hands delicately holding a human brain in their fingertips.",
},
{
name: "Floralis Genérica",
artist: "Eduardo Catalano",
description:
"This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.",
url: "https://i.imgur.com/ZF6s192m.jpg",
alt: "A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.",
},
{
name: "Eternal Presence",
artist: "John Woodrow Wilson",
description:
'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."',
url: "https://i.imgur.com/aTtVpES.jpg",
alt: "The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.",
},
{
name: "Moai",
artist: "Unknown Artist",
description:
"Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.",
url: "https://i.imgur.com/RCwLEoQm.jpg",
alt: "Three monumental stone busts with the heads that are disproportionately large with somber faces.",
},
{
name: "Blue Nana",
artist: "Niki de Saint Phalle",
description:
"The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.",
url: "https://i.imgur.com/Sd1AgUOm.jpg",
alt: "A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.",
},
{
name: "Ultimate Form",
artist: "Barbara Hepworth",
description:
"This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.",
url: "https://i.imgur.com/2heNQDcm.jpg",
alt: "A tall sculpture made of three elements stacked on each other reminding of a human figure.",
},
{
name: "Cavaliere",
artist: "Lamidi Olonade Fakeye",
description:
"Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
url: "https://i.imgur.com/wIdGuZwm.png",
alt: "An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.",
},
{
name: "Big Bellies",
artist: "Alina Szapocznikow",
description:
"Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
url: "https://i.imgur.com/AlHTAdDm.jpg",
alt: "The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.",
},
{
name: "Terracotta Army",
artist: "Unknown Artist",
description:
"The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
url: "https://i.imgur.com/HMFmH6m.jpg",
alt: "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.",
},
{
name: "Lunar Landscape",
artist: "Louise Nevelson",
description:
"Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.",
url: "https://i.imgur.com/rN7hY6om.jpg",
alt: "A black matte sculpture where the individual elements are initially indistinguishable.",
},
{
name: "Aureole",
artist: "Ranjani Shettar",
description:
'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."',
url: "https://i.imgur.com/okTpbHhm.jpg",
alt: "A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.",
},
{
name: "Hippos",
artist: "Taipei Zoo",
description:
"The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.",
url: "https://i.imgur.com/6o5Vuyu.jpg",
alt: "A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.",
},
];
// 使 UI 匹配当前 state
updateDOM();