在你的组件显示在屏幕上之前,它们必须由 React 渲染。了解此过程中的步骤将帮助你思考代码的执行方式并解释其行为。

¥Before your components are displayed on screen, they must be rendered by React. Understanding the steps in this process will help you think about how your code executes and explain its behavior.

你将学习到

  • React 中的渲染意味着什么

    ¥What rendering means in React

  • React 何时以及为何渲染组件

    ¥When and why React renders a component

  • 在屏幕上显示组件所涉及的步骤

    ¥The steps involved in displaying a component on screen

  • 为什么渲染并不总是产生 DOM 更新

    ¥Why rendering does not always produce a DOM update

想象一下,你的组件是厨房里的厨师,用食材烹制美味佳肴。在这个场景中,React 是一个服务员,负责接收客户的请求并为他们带来订单。这个请求和提供 UI 的过程分为三个步骤:

¥Imagine that your components are cooks in the kitchen, assembling tasty dishes from ingredients. In this scenario, React is the waiter who puts in requests from customers and brings them their orders. This process of requesting and serving UI has three steps:

  1. 触发渲染(将客人的订单送到厨房)

    ¥Triggering a render (delivering the guest’s order to the kitchen)

  2. 渲染组件(在厨房准备订单)

    ¥Rendering the component (preparing the order in the kitchen)

  3. 提交给 DOM(将订单放在表格上)

    ¥Committing to the DOM (placing the order on the table)

  1. React as a server in a restaurant, fetching orders from the users and delivering them to the Component Kitchen.
    Trigger
  2. The Card Chef gives React a fresh Card component.
    Render
  3. React delivers the Card to the user at their table.
    Commit

Illustrated by Rachel Lee Nabors

步骤 1:触发渲染

¥Step 1: Trigger a render

组件渲染的原因有两个:

¥There are two reasons for a component to render:

  1. 这是组件的初始渲染。

    ¥It’s the component’s initial render.

  2. 组件(或其祖级之一)的状态已更新。

    ¥The component’s (or one of its ancestors’) state has been updated.

初始渲染

¥Initial render

当你的应用启动时,你需要触发初始渲染。框架和沙箱有时会隐藏此代码,但它是通过使用目标 DOM 节点调用 createRoot,然后使用你的组件调用其 render 方法来完成的:

¥When your app starts, you need to trigger the initial render. Frameworks and sandboxes sometimes hide this code, but it’s done by calling createRoot with the target DOM node, and then calling its render method with your component:

import Image from './Image.js';
import { createRoot } from 'react-dom/client';

const root = createRoot(document.getElementById('root'))
root.render(<Image />);

尝试注释掉 root.render() 调用,然后看到组件消失了!

¥Try commenting out the root.render() call and see the component disappear!

状态更新时重新渲染

¥Re-renders when state updates

组件最初渲染后,你可以通过使用 set 函数 更新其状态来触发进一步的渲染。更新组件的状态会自动对渲染进行排队。(你可以把这些想象成餐厅客人在第一次点菜后点茶、甜点和各种各样的东西,这取决于他们的口渴或饥饿状态。)

¥Once the component has been initially rendered, you can trigger further renders by updating its state with the set function. Updating your component’s state automatically queues a render. (You can imagine these as a restaurant guest ordering tea, dessert, and all sorts of things after putting in their first order, depending on the state of their thirst or hunger.)

  1. React as a server in a restaurant, serving a Card UI to the user, represented as a patron with a cursor for their head. They patron expresses they want a pink card, not a black one!
    State update...
  2. React returns to the Component Kitchen and tells the Card Chef they need a pink Card.
    ...triggers...
  3. The Card Chef gives React the pink Card.
    ...render!

Illustrated by Rachel Lee Nabors

步骤 2:React 渲染你的组件

¥Step 2: React renders your components

触发渲染后,React 会调用你的组件来确定要在屏幕上显示的内容。“渲染” 是 React 调用你的组件。

¥After you trigger a render, React calls your components to figure out what to display on screen. “Rendering” is React calling your components.

  • 在初始渲染时,React 将调用根组件。

    ¥On initial render, React will call the root component.

  • 对于后续的渲染,React 将调用其状态更新触发渲染的函数组件。

    ¥For subsequent renders, React will call the function component whose state update triggered the render.

这个过程是递归的:如果更新的组件返回一些其他组件,React 将接下来渲染该组件,如果该组件也返回一些东西,它将接下来渲染该组件,依此类推。这个过程将一直持续到没有更多的嵌套组件并且 React 确切地知道应该在屏幕上显示什么。

¥This process is recursive: if the updated component returns some other component, React will render that component next, and if that component also returns something, it will render that component next, and so on. The process will continue until there are no more nested components and React knows exactly what should be displayed on screen.

在下面的例子中,React 将多次调用 Gallery()Image()

¥In the following example, React will call Gallery() and Image() several times:

export default function Gallery() {
  return (
    <section>
      <h1>Inspiring Sculptures</h1>
      <Image />
      <Image />
      <Image />
    </section>
  );
}

function Image() {
  return (
    <img
      src="https://i.imgur.com/ZF6s192.jpg"
      alt="'Floralis Genérica' by Eduardo Catalano: a gigantic metallic flower sculpture with reflective petals"
    />
  );
}

  • 在初始渲染期间,React 将为 <section><h1> 和三个 <img> 标签使用 创建 DOM 节点

    ¥During the initial render, React will create the DOM nodes for <section>, <h1>, and three <img> tags.

  • 在重新渲染期间,React 将计算自上次渲染以来它们的哪些属性(如果有)发生了更改。在下一步,即提交阶段之前,它不会对这些信息做任何事情。

    ¥During a re-render, React will calculate which of their properties, if any, have changed since the previous render. It won’t do anything with that information until the next step, the commit phase.

易犯错误

渲染必须始终为 纯计算

¥Rendering must always be a pure calculation:

  • 相同的输入,相同的输出。给定相同的输入,组件应该始终返回相同的 JSX。(当有人点西红柿沙拉时,他们不应该收到洋葱沙拉!)

    ¥Same inputs, same output. Given the same inputs, a component should always return the same JSX. (When someone orders a salad with tomatoes, they should not receive a salad with onions!)

  • 它只管自己的事。它不应更改渲染前存在的任何对象或变量。(一个订单不应更改任何其他人的订单。)

    ¥It minds its own business. It should not change any objects or variables that existed before rendering. (One order should not change anyone else’s order.)

否则,随着代码库变得越来越复杂,你可能会遇到令人困惑的错误和不可预知的行为。在 “严格模式” 开发时,React 会调用每个组件的函数两次,这可以帮助表面因函数不纯而导致的错误。

¥Otherwise, you can encounter confusing bugs and unpredictable behavior as your codebase grows in complexity. When developing in “Strict Mode”, React calls each component’s function twice, which can help surface mistakes caused by impure functions.

深入研究

优化性能

¥Optimizing performance

如果更新组件在树中的位置非常高,则渲染嵌套在更新组件中的所有组件的默认行为对于性能来说并不是最佳的。如果遇到性能问题,性能 部分中描述了几种选择加入的方法来解决它。不要过早优化!

¥The default behavior of rendering all components nested within the updated component is not optimal for performance if the updated component is very high in the tree. If you run into a performance issue, there are several opt-in ways to solve it described in the Performance section. Don’t optimize prematurely!

步骤 3:React 提交对 DOM 的更改

¥Step 3: React commits changes to the DOM

在渲染(调用)你的组件后,React 将修改 DOM。

¥After rendering (calling) your components, React will modify the DOM.

  • 对于初始渲染,React 将使用 appendChild() DOM API 将其创建的所有 DOM 节点放在屏幕上。

    ¥For the initial render, React will use the appendChild() DOM API to put all the DOM nodes it has created on screen.

  • 对于重新渲染,React 将应用最少的必要操作(在渲染时计算!)以使 DOM 匹配最新的渲染输出。

    ¥For re-renders, React will apply the minimal necessary operations (calculated while rendering!) to make the DOM match the latest rendering output.

如果渲染之间存在差异,React 只会更改 DOM 节点。例如,这里有一个组件每秒使用从其父级传递的不同属性重新渲染。请注意如何将一些文本添加到 <input> 中,更新其 value,但当组件重新渲染时文本不会消失:

¥React only changes the DOM nodes if there’s a difference between renders. For example, here is a component that re-renders with different props passed from its parent every second. Notice how you can add some text into the <input>, updating its value, but the text doesn’t disappear when the component re-renders:

export default function Clock({ time }) {
  return (
    <>
      <h1>{time}</h1>
      <input />
    </>
  );
}

这是可行的,因为在最后一步中,React 仅使用新的 time 更新 <h1> 的内容。它看到 <input> 出现在 JSX 中与上次相同的位置,因此 React 不会触及 <input> — 或其 value

¥This works because during this last step, React only updates the content of <h1> with the new time. It sees that the <input> appears in the JSX in the same place as last time, so React doesn’t touch the <input>—or its value!

结语:浏览器画图

¥Epilogue: Browser paint

渲染完成并且 React 更新 DOM 后,浏览器将重新绘制屏幕。尽管此过程称为 “浏览器渲染”,但我们将其称为 “绘制” 以避免在整个文档中造成混淆。

¥After rendering is done and React updated the DOM, the browser will repaint the screen. Although this process is known as “browser rendering”, we’ll refer to it as “painting” to avoid confusion throughout the docs.

A browser painting 'still life with card element'.

Illustrated by Rachel Lee Nabors

回顾

  • React 应用中的任何屏幕更新都分三步进行:

    ¥Any screen update in a React app happens in three steps:

    1. 触发

      ¥Trigger

    2. 渲染

      ¥Render

    3. 提交

      ¥Commit

  • 你可以使用严格模式查找组件中的错误

    ¥You can use Strict Mode to find mistakes in your components

  • 如果渲染结果与上次相同,React 不会触及 DOM

    ¥React does not touch the DOM if the rendering result is the same as last time


React 中文网 - 粤ICP备13048890号