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 主要面向库的开发者,普通应用开发中很少直接使用。

相关推荐
网络安全-老纪9 分钟前
网络安全-js安全知识点与XSS常用payloads
javascript·安全·web安全
API_technology12 分钟前
电商API安全防护:JWT令牌与XSS防御实战
前端·安全·xss
yqcoder17 分钟前
Express + MongoDB 实现在筛选时间段中用户名的模糊查询
java·前端·javascript
十八朵郁金香39 分钟前
通俗易懂的DOM1级标准介绍
开发语言·前端·javascript
GDAL2 小时前
HTML 中的 Canvas 样式设置全解
javascript
m0_528723812 小时前
HTML中,title和h1标签的区别是什么?
前端·html
Dark_programmer2 小时前
html - - - - - modal弹窗出现时,页面怎么能限制滚动
前端·html
GDAL2 小时前
HTML Canvas clip 深入全面讲解
前端·javascript·canvas
禾苗种树2 小时前
在 Vue 3 中使用 ECharts 制作多 Y 轴折线图时,若希望 **Y 轴颜色自动匹配折线颜色**且无需手动干预,可以通过以下步骤实现:
前端·vue.js·echarts
GISer_Jing2 小时前
Javascript排序算法(冒泡排序、快速排序、选择排序、堆排序、插入排序、希尔排序)详解
javascript·算法·排序算法