useInsertionEffect

陷阱

useInsertionEffect 适用于 CSS-in-JS 库作者。除非您正在开发 CSS-in-JS 库并且需要一个地方来注入样式,否则您可能需要 useEffectuseLayoutEffect

useInsertionEffect 允许在任何布局 Effects 触发之前将元素插入 DOM。

useInsertionEffect(setup, dependencies?)

参考

useInsertionEffect(setup, dependencies?)

调用 useInsertionEffect 在任何可能需要读取布局的 Effects 触发之前插入样式

import { useInsertionEffect } from 'react';

// Inside your CSS-in-JS library
function useCSS(rule) {
useInsertionEffect(() => {
// ... inject <style> tags here ...
});
return rule;
}

请参阅下面的更多示例。

参数

  • setup:包含 Effect 逻辑的函数。您的 setup 函数还可以选择返回一个 *cleanup* 函数。当您的组件被添加到 DOM 时,但在任何布局 Effects 触发之前,React 将运行您的 setup 函数。每次重新渲染并更改依赖项后,React 将首先使用旧值运行 cleanup 函数(如果您提供了它),然后使用新值运行您的 setup 函数。当您的组件从 DOM 中移除时,React 将运行您的 cleanup 函数。

  • 可选 dependenciessetup 代码中引用的所有响应式值的列表。响应式值包括 props、state 以及直接在组件主体中声明的所有变量和函数。如果您的 linter 配置为 React,它将验证每个响应式值是否都正确指定为依赖项。依赖项列表必须具有恒定的项数,并且像 [dep1, dep2, dep3] 一样内联编写。React 将使用 Object.is 比较算法将每个依赖项与其先前的值进行比较。如果您根本没有指定依赖项,您的 Effect 将在组件的每次重新渲染后重新运行。

返回值

useInsertionEffect 返回 undefined

注意事项

  • Effects 仅在客户端运行。它们在服务器渲染期间不运行。
  • 您不能从 useInsertionEffect 内部更新状态。
  • useInsertionEffect 运行时,
  • useInsertionEffect 的运行时间可能在 DOM 更新之前,也可能在 DOM 更新之后。你不应该依赖于 DOM 在任何特定时间点被更新。
  • 与其他类型的 Effect 不同,其他类型的 Effect 会针对每个 Effect 触发清理函数,然后针对每个 Effect 触发设置函数,而 useInsertionEffect 会一次性针对一个组件触发清理和设置函数。这会导致清理函数和设置函数“交错”执行。

用法

从 CSS-in-JS 库注入动态样式

传统上,你会使用普通的 CSS 来设置 React 组件的样式。

// In your JS file:
<button className="success" />

// In your CSS file:
.success { color: green; }

有些团队更喜欢直接在 JavaScript 代码中编写样式,而不是编写 CSS 文件。这通常需要使用 CSS-in-JS 库或工具。CSS-in-JS 有三种常见的方法

  1. 使用编译器静态提取到 CSS 文件
  2. 内联样式,例如 <div style={{ opacity: 1 }}>
  3. 运行时注入 <style> 标签

如果你使用 CSS-in-JS,我们建议结合使用前两种方法(CSS 文件用于静态样式,内联样式用于动态样式)。我们不建议运行时注入 <style> 标签,原因有两个:

  1. 运行时注入会导致浏览器更频繁地重新计算样式。
  2. 如果在 React 生命周期中的错误时间发生,运行时注入可能会非常慢。

第一个问题无法解决,但 useInsertionEffect 可以帮助你解决第二个问题。

调用 useInsertionEffect 在任何布局 Effect 触发之前插入样式

// Inside your CSS-in-JS library
let isInserted = new Set();
function useCSS(rule) {
useInsertionEffect(() => {
// As explained earlier, we don't recommend runtime injection of <style> tags.
// But if you have to do it, then it's important to do in useInsertionEffect.
if (!isInserted.has(rule)) {
isInserted.add(rule);
document.head.appendChild(getStyleForRule(rule));
}
});
return rule;
}

function Button() {
const className = useCSS('...');
return <div className={className} />;
}

useEffect 类似,useInsertionEffect 不会在服务器上运行。如果你需要收集在服务器上使用了哪些 CSS 规则,你可以在渲染过程中完成

let collectedRulesSet = new Set();

function useCSS(rule) {
if (typeof window === 'undefined') {
collectedRulesSet.add(rule);
}
useInsertionEffect(() => {
// ...
});
return rule;
}

阅读更多关于使用 useInsertionEffect 升级运行时注入的 CSS-in-JS 库的信息。

深入探讨

这比在渲染期间或 useLayoutEffect 中注入样式有什么好处?

如果你在渲染期间插入样式,并且 React 正在处理非阻塞更新,浏览器将在渲染组件树时重新计算每一帧的样式,这可能会非常慢

useInsertionEffect 比在 useLayoutEffectuseEffect 中插入样式更好,因为它可以确保在组件中其他 Effect 运行时,<style> 标签已经被插入。否则,由于样式过时,常规 Effect 中的布局计算将是错误的。