React 学习笔记(8)—— Fragments、Portals、Profiler

68 阅读3分钟

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: hiddenz-index 样式限制了子组件的展示。

Portal 语法如下:

ReactDOM.createPortal(chlid, container);
  1. 第一个参数 chlid 是要被传送的 React 节点,TS 类型为 ReactNode,如:React 元素、字符串、Fragment 等。

  2. 第二个参数 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:

  1. id

   Profiler 的 id,字符串类型。

  1. 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;
   }