前言
组件在屏幕上显示之前,必须先由 React 进行渲染。理解此过程的步骤将有助于你思考代码的执行方式并解释其行为。想象一下,你的组件就像厨房里的厨师,用各种食材烹制美味佳肴。在这个场景中,React 就像服务员,负责接收顾客的请求并送达订单。这个 UI 请求和服务流程分为三个步骤:
- 触发渲染(将客人的订单送至厨房)——扳机
- 渲染组件(在厨房准备订单)——使成为
- 提交DOM(将订单放在桌子上)——犯罪
步骤一:触发渲染
初始渲染
应用启动时,您需要触发初始渲染。框架和沙盒有时会隐藏此代码,但可以通过调用createRoot目标 DOM 节点,然后render使用组件调用其方法来完成:
import Image from './Image.js';
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root'))
root.render(<Image />);
export default function Image() {
return (
);
状态更新时重新渲染
组件初始渲染完成后,您可以通过使用该set函数更新其状态来触发后续渲染。更新组件状态会自动将渲染加入队列。(您可以想象一下,餐厅的客人在下单后,根据口渴或饥饿的状态,点茶、甜点以及各种各样的东西。)
步骤2:React 渲染你的组件
触发渲染后,React 会调用你的组件来确定在屏幕上显示什么。 “渲染”就是 React 调用你的组件。
- 在初始渲染时, React 将调用根组件。
- 对于后续渲染, React 将调用状态更新触发渲染的函数组件。
这个过程是递归的:如果更新后的组件返回了其他组件,React 会接下来渲染该组件;如果该组件也返回了其他内容,React 会接下来渲染该组件,以此类推。这个过程会一直持续,直到没有嵌套的组件,并且 React 确切地知道应该在屏幕上显示什么。
在下面的例子中,React 将调用Gallery()和Image()多次:
- 在初始渲染时, React 会为、、和三个标签创建 DOM 节点。
<section>``<h1>``<img> - 在重新渲染期间, React 会计算哪些属性(如果有)自上次渲染以来发生了变化。在下一步(提交阶段)之前,它不会处理这些信息。
(渲染必须始终是纯粹的计算:
- 相同的输入,相同的输出。 给定相同的输入,组件应该始终返回相同的 JSX。(当有人点了一份西红柿沙拉时,他们不应该收到一份洋葱沙拉!)
- 它只管自己的事情。 它不应该更改渲染前已存在的任何对象或变量。(一个订单不应该影响其他订单。)
否则,随着代码库复杂度的增加,你可能会遇到令人困惑的 bug 和难以预测的行为。在“严格模式”下开发时,React 会调用每个组件的函数两次,这有助于发现由不纯函数引起的错误。)
步骤 3:React 将更改提交到 DOM
在渲染(调用)你的组件后,React 将修改 DOM。
- 对于初始渲染, React 将使用
appendChild()DOM API 将其创建的所有 DOM 节点放到屏幕上。 - 对于重新渲染, React 将应用最少的必要操作(渲染时计算!)以使 DOM 与最新的渲染输出匹配。
仅当渲染之间存在差异时,React 才会更改 DOM 节点。 例如,下面这个组件每秒都会使用从其父级传递过来的不同 props 重新渲染。请注意,你可以向 中添加一些文本<input>,并更新其value,但当组件重新渲染时,文本不会消失。
结语:浏览器绘制
渲染完成并 React 更新 DOM 后,浏览器将重新绘制屏幕。虽然此过程被称为“浏览器渲染”,但为了避免混淆,我们将其称为“绘制”。
总而言之,- React 应用程序中的任何屏幕更新都分三个步骤进行:
- 扳机
- 使成为
- 犯罪