知海

将 UI 视为树

React指南-教程:描述 UI

title: 将 UI 视为树

当 React 应用逐渐成长时,许多组件会层层嵌套。那么 React 是如何跟踪应用组件结构的?

React 与许多其他 UI 库一样,将 UI 建模为树。将应用视为一棵树,有助于理解组件之间的关系,并调试未来会遇到的概念,比如性能和状态管理。

  • React 如何看待组件结构
  • 渲染树是什么,以及它的用途
  • 模块依赖树是什么,以及它的用途

将 UI 视为树 {/your-ui-as-a-tree/}

树是项目和 UI 之间的关系模型,通常用树结构来表示 UI。例如,浏览器使用树结构来建模 HTML(DOM)和 CSS(CSSOM)。移动平台也使用树来表示其视图层级。

React 从组件创建 UI 树。在这个例子中,UI 树最终被用来渲染 DOM。

与浏览器和移动平台类似,React 也使用树结构来管理和建模 React 应用中组件之间的关系。这些树是有用的工具,用于理解数据如何在 React 应用中流动,以及如何优化渲染和应用体积。

渲染树 {/the-render-tree/}

组件的一个主要特性是能够由其他组件组合而成。在嵌套组件中,有父组件和子组件的概念,每个父组件本身可能是另一个组件的子组件。

当渲染 React 应用时,可以在一种称为渲染树的树中建模这种关系。

下面的 React 应用渲染了一些鼓舞人心的引言。

js src/App.js 复制代码
import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    <>
      <FancyText title text="Get Inspired App" />
      <InspirationGenerator>
        <Copyright year={2004} />
      </InspirationGenerator>
    </>
  );
}
js src/FancyText.js 复制代码
export default function FancyText({title, text}) {
  return title
    ? <h1 className='fancy title'>{text}</h1>
    : <h3 className='fancy cursive'>{text}</h3>
}
js src/InspirationGenerator.js 复制代码
import * as React from 'react';
import quotes from './quotes';
import FancyText from './FancyText';

export default function InspirationGenerator({children}) {
  const [index, setIndex] = React.useState(0);
  const quote = quotes[index];
  const next = () => setIndex((index + 1) % quotes.length);

  return (
    <>
      <p>Your inspirational quote is:</p>
      <FancyText text={quote} />
      <button onClick={next}>Inspire me again</button>
      {children}
    </>
  );
}
js src/Copyright.js 复制代码
export default function Copyright({year}) {
  return <p className='small'>©️ {year}</p>;
}
js src/quotes.js 复制代码
export default [
  "Don’t let yesterday take up too much of today.” — Will Rogers",
  "Ambition is putting a ladder against the sky.",
  "A joy that's shared is a joy made double.",
  ];
css 复制代码
.fancy {
  font-family: 'Georgia';
}
.title {
  color: #007AA3;
  text-decoration: underline;
}
.cursive {
  font-style: italic;
}
.small {
  font-size: 10px;
}

React 创建的由渲染过的组件构成的 UI 树,被称为渲染树。

从上面的示例应用,可以构建出渲染树。

这棵树由节点组成,每个节点代表一个组件。比如,AppFancyTextCopyright 都是树中的节点。

在 React 渲染树中,根节点是应用的根组件。这里的根组件是 App,它是 React 渲染的第一个组件。树中的每个箭头从父组件指向子组件。

那么渲染树中的 HTML 标签在哪里呢? {/where-are-the-html-elements-in-the-render-tree/}

你也许已经注意到,在上面的渲染树中,没有提到每个组件渲染的 HTML 标签。这是因为渲染树仅由 React 组件组成。

React 是一个跨平台的 UI 框架。react.dev 展示了一些渲染到 Web 的示例,这些示例使用 HTML 标签作为 UI 原语。但 React 应用同样可以渲染到移动设备或桌面平台,这些平台可能使用不同的 UI 原语,如 UIViewFrameworkElement

这些平台 UI 原语不是 React 的一部分。无论应用渲染到哪个平台,React 渲染树都能为 React 应用提供见解。

渲染树表示 React 应用单次渲染的过程。在条件渲染中,父组件可以根据传入的数据渲染不同的子组件。

我们可以更新应用,使其有条件地渲染励志引言或颜色。

js src/App.js 复制代码
import FancyText from './FancyText';
import InspirationGenerator from './InspirationGenerator';
import Copyright from './Copyright';

export default function App() {
  return (
    <>
      <FancyText title text="Get Inspired App" />
      <InspirationGenerator>
        <Copyright year={2004} />
      </InspirationGenerator>
    </>
  );
}
js src/FancyText.js 复制代码
export default function FancyText({title, text}) {
  return title
    ? <h1 className='fancy title'>{text}</h1>
    : <h3 className='fancy cursive'>{text}</h3>
}
js src/Color.js 复制代码
export default function Color({value}) {
  return <div className="colorbox" style={{backgroundColor: value}} />
}
js src/InspirationGenerator.js 复制代码
import * as React from 'react';
import inspirations from './inspirations';
import FancyText from './FancyText';
import Color from './Color';

export default function InspirationGenerator({children}) {
  const [index, setIndex] = React.useState(0);
  const inspiration = inspirations[index];
  const next = () => setIndex((index + 1) % inspirations.length);

  return (
    <>
      <p>Your inspirational {inspiration.type} is:</p>
      {inspiration.type === 'quote'
      ? <FancyText text={inspiration.value} />
      : <Color value={inspiration.value} />}

      <button onClick={next}>Inspire me again</button>
      {children}
    </>
  );
}
js src/Copyright.js 复制代码
export default function Copyright({year}) {
  return <p className='small'>©️ {year}</p>;
}
js src/inspirations.js 复制代码
export default [
  {type: 'quote', value: "Don’t let yesterday take up too much of today.” — Will Rogers"},
  {type: 'color', value: "#B73636"},
  {type: 'quote', value: "Ambition is putting a ladder against the sky."},
  {type: 'color', value: "#256266"},
  {type: 'quote', value: "A joy that's shared is a joy made double."},
  {type: 'color', value: "#F9F2B4"},
];
css 复制代码
.fancy {
  font-family: 'Georgia';
}
.title {
  color: #007AA3;
  text-decoration: underline;
}
.cursive {
  font-style: italic;
}
.small {
  font-size: 10px;
}
.colorbox {
  height: 100px;
  width: 100px;
  margin: 8px;
}

在条件渲染的不同渲染过程中,渲染树可能会渲染不同的组件。

在这个示例中,根据 inspiration.type 的值,可能会渲染 <FancyText><Color>。因此,每次渲染过程的渲染树可能都不一样。

尽管渲染树在不同渲染过程中可能不同,但这些树通常有助于识别 React 应用中的顶层组件和叶子组件。顶层组件是离根组件最近的组件,它们影响其下所有组件的渲染性能,通常包含最多的复杂性。叶子组件位于树的底部,没有子组件,通常会被频繁重新渲染。

识别这些组件类别,有助于理解应用的数据流和性能。

模块依赖树 {/the-module-dependency-tree/}

在 React 应用中,可以用树来建模的另一个关系是应用的模块依赖关系。当你将组件和逻辑拆分到不同文件时,就创建了 JavaScript 模块,这些模块可以导出组件、函数或常量。

模块依赖树中的每个节点都是一个模块,每个分支代表该模块中的 import 语句。

以之前的 Inspirations 应用为例,可以构建一棵模块依赖树,简称依赖树。

Inspirations 应用的模块依赖树。

树的根节点是根模块,也称为入口文件。它通常包含根组件的模块。

与同一应用的渲染树相比,依赖树有相似的结构,但也有一些显著差异:

  • 树的节点代表模块,而不是组件。
  • 非组件模块(如 inspirations.js)也会出现在树中,而渲染树只封装组件。
  • Copyright.js 出现在 App.js 下,但在渲染树中,CopyrightInspirationGenerator 的子组件。这是因为 InspirationGenerator 接受 JSX 作为 children props,所以它将 Copyright 作为子组件渲染,但并不导入该模块。

依赖树对于确定运行 React 应用所需的模块非常有用。在生产环境构建 React 应用时,通常有一个构建步骤,会将所有必要的 JavaScript 打包以供客户端使用。负责这项工作的工具称为 bundler(打包器),打包器会利用依赖树来决定需要包含哪些模块。

随着应用的增长,打包体积通常也会增加。过大的打包体积会让客户端的下载和运行成本变高,并延迟 UI 绘制的时间。了解应用的依赖树有助于调试这些问题。

  • 树是表示实体之间关系的常见方式,它们经常被用来建模 UI。
  • 渲染树表示单次渲染中 React 组件之间的嵌套关系。
  • 使用条件渲染时,渲染树可能会在不同渲染过程中发生变化。不同属性值会让组件渲染不同的子组件。
  • 渲染树有助于识别顶层组件和叶子组件。顶层组件会影响其下所有组件的渲染性能,而叶子组件通常会频繁重新渲染。识别它们有助于理解和调试渲染性能问题。
  • 依赖树表示 React 应用中的模块依赖关系。
  • 构建工具使用依赖树来打包必要的代码以部署应用。
  • 依赖树有助于调试因打包体积过大而导致的渲染速度过慢,以及发现哪些打包代码可以被优化。

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。