添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

对 state 进行保留和重置

各个组件的 state 是各自独立的。根据组件在 UI 树中的位置,React 可以跟踪哪些 state 属于哪个组件。你可以控制在重新渲染过程中何时对 state 进行保留和重置。

你将会学习到

  • React 如何“处理”组件结构
  • React 何时选择保留或重置 state
  • 如何强制 React 重置组件的 state
  • key 和组件类型如何影响 state 是否被保留
  • 浏览器使用许多树形结构来为 UI 建立模型。 DOM 用于表示 HTML 元素, CSSOM 则表示 CSS 元素。甚至还有 Accessibility tree

    React 也使用树形结构来对你创造的 UI 进行管理和建模。React 根据你的 JSX 生成 UI 树 。React DOM 根据 UI 树去更新浏览器的 DOM 元素。(React Native 则将这些 UI 树转译成移动平台上特有的元素。)

    Diagram with three sections arranged horizontally. In the first section, there are three rectangles stacked vertically, with labels 'Component A', 'Component B', and 'Component C'. Transitioning to the next pane is an arrow with the React logo on top labeled 'React'. The middle section contains a tree of components, with the root labeled 'A' and two children labeled 'B' and 'C'. The next section is again transitioned using an arrow with the React logo on top labeled 'React'. The third and final section is a wireframe of a browser, containing a tree of 8 nodes, which has only a subset highlighted (indicating the subtree from the middle section).
    Diagram with three sections arranged horizontally. In the first section, there are three rectangles stacked vertically, with labels 'Component A', 'Component B', and 'Component C'. Transitioning to the next pane is an arrow with the React logo on top labeled 'React'. The middle section contains a tree of components, with the root labeled 'A' and two children labeled 'B' and 'C'. The next section is again transitioned using an arrow with the React logo on top labeled 'React'. The third and final section is a wireframe of a browser, containing a tree of 8 nodes, which has only a subset highlighted (indicating the subtree from the middle section).

    React 会根据组件创建了一棵 UI 树,React DOM 用它来渲染 DOM

    state 与树中的某个位置相关联

    当你为一个组件添加 state 时,你可能会觉得 state “活”在组件内部。但实际上,state 被保存在 React 内部。根据组件在 UI 树中的位置,React 将它所持有的每个 state 与正确的组件关联起来。

    下面只定义了一个 <Counter /> JSX 标签,但将它渲染在了两个不同的位置:

import { useState } from 'react';
export default function App() {
  const counter = <Counter />;
  return (
      {counter}
      {counter}
    </div>
function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);
  let className = 'counter';
  if (hover) {
    className += ' hover';
  return (
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
      </button>
    </div>

下面是它们的树形结构的样子:

Diagram of a tree of React components. The root node is labeled 'div' and has two children. Each of the children are labeled 'Counter' and both contain a state bubble labeled 'count' with value 0.
Diagram of a tree of React components. The root node is labeled 'div' and has two children. Each of the children are labeled 'Counter' and both contain a state bubble labeled 'count' with value 0.

React 树

这是两个独立的 counter,因为它们在树中被渲染在了各自的位置。 一般情况下你不用去考虑这些位置来使用 React,但知道它们是如何工作会很有用。

在 React 中,屏幕中的每个组件都有完全独立的 state。举个例子,当你并排渲染两个 Counter 组件时,它们都会拥有各自独立的 scorehover state。

试试点击两个 counter 你会发现它们互不影响:

import { useState } from 'react';
export default function App() {
  return (
      <Counter />
      <Counter />
    </div>
function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);
  let className = 'counter';
  if (hover) {
    className += ' hover';
  return (
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
      </button>
    </div>

如你所见,当一个计数器被更新时,只有该组件的状态会被更新:

Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 1. The state bubble of the right child is highlighted in yellow to indicate its value has updated.
Diagram of a tree of React components. The root node is labeled 'div' and has two children. The left child is labeled 'Counter' and contains a state bubble labeled 'count' with value 0. The right child is labeled 'Counter' and contains a state bubble labeled 'count' with value 1. The state bubble of the right child is highlighted in yellow to indicate its value has updated.

Updating state

只有当你在相同的位置渲染相同的组件时,React 才会一直保留着组件的 state。想要验证这一点,可以将两个计数器的值递增,取消勾选 “渲染第二个计数器” 复选框,然后再次勾选它:

import { useState } from 'react';
export default function App() {
  const [showB, setShowB] = useState(true);
  return (
      <Counter />
      {showB && <Counter />} 
      <label>
        <input
          type="checkbox"
          checked={showB}
          onChange={e => {
            setShowB(e.target.checked)
        渲染第二个计数器
      </label>
    </div>
function Counter() {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);
  let className = 'counter';
  if (hover) {
    className += ' hover';
  return (
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
      </button>
    </div>

注意,当你停止渲染第二个计数器的那一刻,它的 state 完全消失了。这是因为 React 在移除一个组件时,也会销毁它的 state。

当你重新勾选“渲染第二个计数器”复选框时,另一个计数器及其 state 将从头开始初始化(score = 0)并被添加到 DOM 中。

只要一个组件还被渲染在 UI 树的相同位置,React 就会保留它的 state。 如果它被移除,或者一个不同的组件被渲染在相同的位置,那么 React 就会丢掉它的 state。

相同位置的相同组件会使得 state 被保留下来

在这个例子中,有两个不同的 <Counter /> 标签:

import




    
 { useState } from 'react';
export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  return (
      {isFancy ? (
        <Counter isFancy={true} /> 
      ) : (
        <Counter isFancy={false} /> 
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
        使用好看的样式
      </label>
    </div>
function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);
  let className = 'counter';
  if (hover) {
    className += ' hover';
  if (isFancy) {
    className += ' fancy';
  return (
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
      </button>
    </div>

当你勾选或清空复选框的时候,计数器 state 并没有被重置。不管 isFancytrue 还是 false,根组件 App 返回的 div 的第一个子组件都是 <Counter />

Diagram with two sections separated by an arrow transitioning between them. Each section contains a layout of components with a parent labeled 'App' containing a state bubble labeled isFancy. This component has one child labeled 'div', which leads to a prop bubble containing isFancy (highlighted in purple) passed down to the only child. The last child is labeled 'Counter' and contains a state bubble with label 'count' and value 3 in both diagrams. In the left section of the diagram, nothing is highlighted and the isFancy parent state value is false. In the right section of the diagram, the isFancy parent state value has changed to true and it is highlighted in yellow, and so is the props bubble below, which has also changed its isFancy value to true.
Diagram with two sections separated by an arrow transitioning between them. Each section contains a layout of components with a parent labeled 'App' containing a state bubble labeled isFancy. This component has one child labeled 'div', which leads to a prop bubble containing isFancy (highlighted in purple) passed down to the only child. The last child is labeled 'Counter' and contains a state bubble with label 'count' and value 3 in both diagrams. In the left section of the diagram, nothing is highlighted and the isFancy parent state value is false. In the right section of the diagram, the isFancy parent state value has changed to true and it is highlighted in yellow, and so is the props bubble below, which has also changed its isFancy value to true.

更新 App 的状态不会重置 Counter,因为 Counter 始终保持在同一位置。

它是位于相同位置的相同组件,所以对 React 来说,它是同一个计数器。

陷阱

记住 对 React 来说重要的是组件在 UI 树中的位置,而不是在 JSX 中的位置! 这个组件在 if 内外有两个return 语句,它们带有不同的 <Counter /> JSX 标签:

import { useState } from 'react';
export default function App() {
  const [isFancy, setIsFancy] = useState(false);
  if (isFancy) {
    return (
        <Counter isFancy={true} />
        <label>
          <input
            type="checkbox"
            checked={isFancy}
            onChange={e => {
              setIsFancy(e.target.checked)
          使用好看的样式
        </label>
      </div>
  return (
      <Counter isFancy={false} />
      <label>
        <input
          type="checkbox"
          checked={isFancy}
          onChange={e => {
            setIsFancy(e.target.checked)
        使用好看的样式
      </label>
    </div>
function Counter({ isFancy }) {
  const [score, setScore] = useState(0);
  const [hover, setHover] = useState(false);
  let className = 'counter';
  if (hover) {
    className += ' hover';
  if (isFancy) {
    className += ' fancy';
  return (
      className={className}
      onPointerEnter={() => setHover(true)}
      onPointerLeave={() => setHover(false)}
      <h1>{score}</h1>
      <button onClick={() => setScore(score + 1)}>
      </button>
    </div>