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 分钟前
扩展:React 项目执行 yarn eject 后的 package.json 变化详解及参数解析
react.js·前端框架·react
我是Superman丶1 小时前
【技巧】前端VUE用中文方法名调用没效果的问题
前端·javascript·vue.js
斯~内克1 小时前
Vue 3 中 watch 的使用与深入理解
前端·javascript·vue.js
蜡笔小柯南3 小时前
解决:npm install报错,reason: certificate has expired
前端·npm·node.js
lqj_本人4 小时前
鸿蒙OS&UniApp制作多选框与单选框组件#三方框架 #Uniapp
前端·javascript·uni-app
@PHARAOH5 小时前
WHAT - 前端开发流程 SOP(标准操作流程)参考
前端·领导力
松树戈5 小时前
plus-ui&RuoYi-Vue-Plus 基于pgSql本地运行实践
前端·vue.js·spring boot·ui
new6669995 小时前
css画图形
前端·css
Yvonne爱编码6 小时前
CSS- 1.1 css选择器
前端·css·状态模式·html5·hbuilder
SHIPKING3936 小时前
【HTML】个人博客页面
javascript·css·html