当前位置: 首页 > article >正文

React 中hooks之useInsertionEffect用法总结

1. 基本概念

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

1.1 执行时机对比

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

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

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

  return <div>Example</div>;
}

2. 主要用途

2.1 动态注入样式

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 库实现

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 样式缓存

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 批量样式注入

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 主题系统实现

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 动态组件样式

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
useEffectDOM 更新后异步执行副作用处理
useLayoutEffectDOM 更新后同步执行DOM 测量和更新
useInsertionEffectDOM 更新前执行样式注入否(除样式注入外)

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


http://www.kler.cn/a/515799.html

相关文章:

  • k8s使用nfs持久卷
  • Kafka后台启动命令
  • ubuntu20.04有亮度调节条但是调节时亮度不变
  • python创建一个httpServer网页上传文件到httpServer
  • 1. 基于图像的三维重建
  • AI Agent:AutoGPT的使用方法
  • 99.11 金融难点通俗解释:净资产收益率(ROE)VS投资资本回报率(ROIC)VS总资产收益率(ROA)
  • 【算法笔记】力扣热题100(LeetCode hot-100)438. 找到字符串中所有字母异位词 滑动窗口
  • 安卓程序作为web服务端的技术实现:AndServer 实现登录权限拦截
  • js为table列宽度添加拖拽调整
  • 原生toFixed的bug
  • 多版本并发控制:MVCC的作用和基本原理
  • javaweb之HTML
  • 【spring】集成JWT实现登录验证
  • Grafana系列之面板接入Prometheus Alertmanager
  • C#树图显示目录下所有文件以及文件大小
  • 深入探究 YOLOv5:从优势到模型导出全方位解析
  • 简识JVM的栈帧优化共享技术
  • SQL-leetcode—1174. 即时食物配送 II
  • 【设计模式-行为型】观察者模式
  • Git报错:refusing to merge unrelated histories
  • 基于ESP32-IDF驱动GPIO输出控制LED
  • ChatGPT大模型极简应用开发-CH2-深入了解 GPT-4 和 ChatGPT 的 API
  • linux CentOS 创建账号,并设置权限
  • PL/SQL语言的图形用户界面
  • Haskell语言的正则表达式