Fragments
React 中,Fragments 可以将子元素组织起来,而无需添加任何多余的 DOM。这尤其适用于父元素只需要一个子元素列表,而不接收多余的、仅用于包装的 HTML 标签的情形。
用例
import React from "react";
export function Table() {
return (
<table>
<tbody>
<tr>
<Columns />
</tr>
</tbody>
</table>
);
}
function Columns() {
return (
<React.Fragment>
<td>1</td>
<td>2</td>
<td>3</td>
</React.Fragment>
);
}
Fragments 的空标签语法糖:
function Columns() {
return (
<>
<td>1</td>
<td>2</td>
<td>3</td>
</>
);
}
<React.Fragment> 的属性 key
空标签语法糖不支持任何属性,使用 <React.Fragment> 显式语法可以添加 key 属性,目前 <React.Fragment> 只支持 key 属性。
export function Glossary(props) {
return (
<dl>
{props.items.map((item) => (
<React.Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</React.Fragment>
))}
</dl>
);
}
Portals
React 中,使用 Portal 可以将子节点渲染到所在父组件以外的 DOM 中。
应用场景:弹窗(modal)、悬浮卡(hovercard)、提示框(tooltip)等。
基本语法
通常,React 组件渲染出来的元素将被挂载到离它最近的父节点中。但有时候,可能需要将子元素插入到 DOM 树中的不同位置,比如:父组件的 overflow: hidden 或 z-index 样式限制了子组件的展示。
Portal 语法如下:
ReactDOM.createPortal(chlid, container);
-
第一个参数
chlid是要被传送的 React 节点,TS 类型为ReactNode,如:React 元素、字符串、Fragment 等。 -
第二个参数
container是接收chlid的容器 DOM。
注意事项
Portal 不会改变 React 组件树的结构,也就是说 portal 仍位于 React 组件树中原有的位置,与 DOM 树中的位置无关。这让 portal 拥有和普通 React 节点一样的功能特性,如:Context、事件冒泡等。
portal 上的合成事件仍然沿着 React 组件树冒泡。
用例
HTML 结构如下:
<html>
<body>
<div id="root"></div>
<div id="modal-root"></div>
</body>
</html>
tsx 组件文件如下:
import { ReactNode, useCallback, useEffect, useState } from "react";
import { createPortal } from "react-dom";
const modalRoot = document.getElementById("modal-root");
interface Prop {
children: ReactNode;
}
export function Modal({ children }: Prop) {
const [container] = useState(document.createElement("div"));
useEffect(() => {
modalRoot?.appendChild(container);
return () => {
modalRoot?.removeChild(container);
};
}, [container]);
return createPortal(children, container);
}
export function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div className="parent" onClick={handleClick}>
<p>click count: {count}</p>
<Modal>
<Child />
</Modal>
</div>
);
}
export function Child() {
return (
<div className="child">
<button>click</button>
</div>
);
}
Profiler
React 提供了 <Profiler> 用于测量一个 React 应用渲染一次需要花费多少时间,它的目的是识别出应用中渲染较慢的部分并进行优化。
Profiler 增加了额外的开支,所以在生产构建中被禁用。
用例
Profiler 可以添加到 React 树中的任意位置,用来测量树中相应部分的渲染开销。可以使用多个 Profiler 测量应用中的不同部分,也可以嵌套使用 Profiler 测量同一子树下的不同组件。
虽然 Profiler 是一个轻量级组件,但仍然应该按需使用。
import { Profiler } from "react";
import { Layout } from "antd";
const { Header, Content } = Layout;
const onRender = (...args: any[]) => {
console.log(args);
};
export function Test() {
return (
<Layout>
<Profiler id="header" onRender={onRender}>
<Header />
</Profiler>
<Profiler id="content" onRender={onRender}>
<Content>
<Profiler id="div" onRender={onRender}>
<div>content</div>
</Profiler>
</Content>
</Profiler>
</Layout>
);
}
Profiler 的 prop
Profiler 接收两个 prop:
id
Profiler 的 id,字符串类型。
onRender
profiler 包含的组件树中的组件 commit 更新时的回调函数,onRender 的类型如下:
type ProfilerOnRenderCallback = (
id: string, // 发生 commit 的 Profiler 树的 id
phase: "mount" | "update", // 渲染阶段,首次挂载或 props/state/hooks 导致的重渲染
actualDuration: number, // 本次更新中渲染 Profiler 及其子树所花费的时间
baseDuration: number, // Profiler 树中单个组件最近一次渲染时间
startTime: number, // React 开始渲染本次更新的时间戳
commitTime: number, // React commit 本次更新的时间戳,在一次 commit 中,所有 profiler 共享这个值
interactions: Set<SchedulerInteraction> // 当更新被 scheduled 时所追踪的 interactions,实验性 API
) => void;
interface SchedulerInteraction {
__count: number;
id: number;
name: string;
timestamp: number;
}