
将 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 应用中的模块依赖关系。
- 构建工具使用依赖树来打包必要的代码以部署应用。
- 依赖树有助于调试因打包体积过大而导致的渲染速度过慢,以及发现哪些打包代码可以被优化。
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
