知海

将 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 树,被称为渲染树。

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

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

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

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

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

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

这些平台 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 下,但在渲染树中,Copyright 是 InspirationGenerator 的子组件。这是因为 InspirationGenerator 接受 JSX 作为 children props,所以它将 Copyright 作为子组件渲染,但并不导入该模块。

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

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

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

帮助我们改进文档

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