React 中hooks之useInsertionEffect用法总结

1. 基本概念

useInsertionEffect 是 React 18 引入的一个特殊的 Hook,它的执行时机比 useLayoutEffect 更早,主要用于在 DOM 变更之前注入样式。这个 Hook 主要面向 CSS-in-JS 库的开发者使用。

1.1 执行时机对比

jsx 复制代码
function Component() {
  useEffect(() => {
    console.log('useEffect'); // 3. 最后执行
  });

  useLayoutEffect(() => {
    console.log('useLayoutEffect'); // 2. 其次执行
  });

  useInsertionEffect(() => {
    console.log('useInsertionEffect'); // 1. 最先执行
  });

  return <div>Example</div>;
}

2. 主要用途

2.1 动态注入样式

jsx 复制代码
function useDynamicStyles(rule) {
  useInsertionEffect(() => {
    // 在 DOM 变更之前注入样式
    const style = document.createElement('style');
    style.textContent = rule;
    document.head.appendChild(style);
    
    return () => {
      document.head.removeChild(style);
    };
  }, [rule]);
}

// 使用示例
function StyledComponent() {
  const className = 'dynamic-style-' + Math.random().toString(36).slice(2);
  
  useDynamicStyles(`
    .${className} {
      background-color: #f0f0f0;
      padding: 16px;
      border-radius: 4px;
    }
  `);

  return <div className={className}>Styled content</div>;
}

2.2 CSS-in-JS 库实现

jsx 复制代码
function createStyleSheet() {
  let sheet = null;
  let rules = new Map();

  return {
    insert(rule, key) {
      if (!sheet) {
        sheet = document.createElement('style');
        document.head.appendChild(sheet);
      }
      
      if (!rules.has(key)) {
        rules.set(key, rule);
        sheet.textContent += rule;
      }
    },
    remove(key) {
      if (rules.has(key)) {
        rules.delete(key);
        if (sheet) {
          sheet.textContent = Array.from(rules.values()).join('\\n');
        }
      }
    }
  };
}

const styleSheet = createStyleSheet();

function useStyles(styles) {
  const key = useMemo(() => Math.random().toString(36).slice(2), []);
  
  useInsertionEffect(() => {
    // 在 DOM 变更之前注入样式
    styleSheet.insert(styles, key);
    return () => styleSheet.remove(key);
  }, [styles, key]);
}

// 使用示例
function StyledButton({ primary }) {
  const buttonStyles = \`
    .button-\${primary ? 'primary' : 'secondary'} {
      background: \${primary ? '#1a73e8' : '#ffffff'};
      color: \${primary ? '#ffffff' : '#1a73e8'};
      border: 1px solid #1a73e8;
      padding: 8px 16px;
      border-radius: 4px;
    }
  \`;

  useStyles(buttonStyles);

  return (
    <button className={\`button-\${primary ? 'primary' : 'secondary'}\`}>
      Click me
    </button>
  );
}

3. 性能优化

3.1 样式缓存

jsx 复制代码
const styleCache = new Map();

function useOptimizedStyles(styles) {
  const key = useMemo(() => JSON.stringify(styles), [styles]);
  
  useInsertionEffect(() => {
    if (!styleCache.has(key)) {
      const styleElement = document.createElement('style');
      styleElement.textContent = styles;
      styleCache.set(key, styleElement);
      document.head.appendChild(styleElement);
    }
    
    const element = styleCache.get(key);
    let count = element.dataset.count || 0;
    element.dataset.count = ++count;
    
    return () => {
      count = --element.dataset.count;
      if (count === 0) {
        document.head.removeChild(element);
        styleCache.delete(key);
      }
    };
  }, [key]);
}

3.2 批量样式注入

jsx 复制代码
function useBatchStyles(styleRules) {
  useInsertionEffect(() => {
    const style = document.createElement('style');
    const cssText = styleRules.join('\\n');
    
    // 使用 requestAnimationFrame 批量注入样式
    requestAnimationFrame(() => {
      style.textContent = cssText;
      document.head.appendChild(style);
    });
    
    return () => {
      requestAnimationFrame(() => {
        document.head.removeChild(style);
      });
    };
  }, [styleRules]);
}

4. 实际应用场景

4.1 主题系统实现

jsx 复制代码
function useThemeStyles(theme) {
  const themeStyles = useMemo(() => {
    return `
      :root {
        --primary-color: ${theme.primary};
        --secondary-color: ${theme.secondary};
        --text-color: ${theme.text};
        --background-color: ${theme.background};
      }
    `;
  }, [theme]);

  useInsertionEffect(() => {
    const style = document.createElement('style');
    style.setAttribute('data-theme', 'true');
    style.textContent = themeStyles;
    
    // 移除旧主题
    const oldTheme = document.querySelector('style[data-theme]');
    if (oldTheme) {
      document.head.removeChild(oldTheme);
    }
    
    document.head.appendChild(style);
    return () => document.head.removeChild(style);
  }, [themeStyles]);
}

4.2 动态组件样式

jsx 复制代码
function useDynamicComponent(props) {
  const { width, height, color } = props;
  
  const styles = useMemo(() => `
    .dynamic-component {
      width: ${width}px;
      height: ${height}px;
      background-color: ${color};
      transition: all 0.3s ease;
    }
  `, [width, height, color]);

  useInsertionEffect(() => {
    const style = document.createElement('style');
    style.textContent = styles;
    document.head.appendChild(style);
    return () => document.head.removeChild(style);
  }, [styles]);

  return <div className="dynamic-component" />;
}

5. 注意事项

  1. 使用限制

    • 不能访问 refs
    • 不能操作 DOM(除了注入样式)
    • 主要用于 CSS-in-JS 库开发
  2. 执行时机

    • 在 DOM 变更之前执行
    • 比 useLayoutEffect 更早
    • 在服务端渲染时不执行
  3. 性能考虑

    • 避免频繁的样式变更
    • 实现样式缓存机制
    • 批量处理样式注入
  4. 最佳实践

    • 仅用于样式注入
    • 保持样式的可预测性
    • 实现清理机制

6. 与其他 Hooks 的对比

Hook 执行时机 主要用途 是否可以访问 DOM
useEffect DOM 更新后异步执行 副作用处理
useLayoutEffect DOM 更新后同步执行 DOM 测量和更新
useInsertionEffect DOM 更新前执行 样式注入 否(除样式注入外)

通过合理使用 useInsertionEffect,我们可以优化 CSS-in-JS 的性能,避免样式注入导致的布局抖动。但要记住,这个 Hook 主要面向库的开发者,普通应用开发中很少直接使用。

相关推荐
CodeClimb1 小时前
【华为OD-E卷 - 第k个排列 100分(python、java、c++、js、c)】
java·javascript·c++·python·华为od
hunter2062061 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb1 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角1 小时前
CSS 颜色
前端·css
浪浪山小白兔2 小时前
HTML5 新表单属性详解
前端·html·html5
lee5763 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579653 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
光头程序员3 小时前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
limit for me3 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者3 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架