普通视图

发现新文章,点击刷新页面。
昨天 — 2025年5月18日首页

React Hooks 的优势和使用场景

作者 Riesenzahn
2025年5月18日 07:37

React Hooks 的优势和使用场景

核心优势

  1. 简化组件逻辑
    • 告别 class 组件的繁琐生命周期
    • 将相关逻辑聚合到单个 Hook 中
    • 消除 this 绑定问题
// 类组件
class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return <button onClick={this.handleClick}>点击 {this.state.count} 次</button>;
  }
}

// 函数组件 + Hooks
function Example() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>点击 {count} 次</button>;
}
  1. 逻辑复用
    • 自定义 Hook 实现跨组件逻辑复用
    • 替代高阶组件和 render props 模式
// 自定义 Hook
function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });

  useEffect(() => {
    const handleResize = () => setSize({
      width: window.innerWidth,
      height: window.innerHeight
    });
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

// 在组件中使用
function MyComponent() {
  const { width } = useWindowSize();
  return <div>窗口宽度: {width}px</div>;
}
  1. 性能优化
    • 细粒度的状态更新控制
    • 避免不必要的渲染
function ExpensiveComponent() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState('');

  // 只有 count 变化时才重新计算
  const computedValue = useMemo(() => {
    return expensiveCalculation(count);
  }, [count]);

  // 只有在组件挂载时执行
  useEffect(() => {
    fetchInitialData();
  }, []);

  return (
    <div>
      <p>{computedValue}</p>
      <input value={text} onChange={e => setText(e.target.value)} />
      <button onClick={() => setCount(count + 1)}>增加</button>
    </div>
  );
}

主要使用场景

  1. 状态管理
    • useState: 基础状态管理
    • useReducer: 复杂状态逻辑
function TodoApp() {
  const [todos, dispatch] = useReducer(todoReducer, []);

  function handleAddTodo(text) {
    dispatch({ type: 'ADD_TODO', text });
  }

  // ...
}
  1. 副作用处理
    • useEffect: 数据获取、订阅、手动 DOM 操作
    • useLayoutEffect: DOM 变更后同步执行
function DataFetcher({ id }) {
  const [data, setData] = useState(null);

  useEffect(() => {
    let ignore = false;
    
    async function fetchData() {
      const result = await fetch(`/api/data/${id}`);
      if (!ignore) {
        setData(await result.json());
      }
    }

    fetchData();
    
    return () => { ignore = true; };
  }, [id]); // id 变化时重新获取

  // ...
}
  1. 性能优化
    • useMemo: 缓存计算结果
    • useCallback: 缓存函数引用
function Parent() {
  const [count, setCount] = useState(0);
  
  // 避免子组件不必要的重渲染
  const handleClick = useCallback(() => {
    setCount(c => c + 1);
  }, []);

  return (
    <>
      <Child onClick={handleClick} />
      <div>计数: {count}</div>
    </>
  );
}
  1. 访问 DOM 元素
    • useRef: 获取 DOM 引用或保存可变值
function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  
  const onButtonClick = () => {
    inputEl.current.focus();
  };

  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>聚焦输入框</button>
    </>
  );
}
  1. 上下文访问
    • useContext: 简化上下文使用
const ThemeContext = React.createContext('light');

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return <button style={{ background: theme === 'dark' ? '#333' : '#eee' }}>按钮</button>;
}

最佳实践

  1. Hook 使用规则

    • 只在 React 函数组件顶层调用 Hook
    • 不要在循环、条件或嵌套函数中调用 Hook
    • 自定义 Hook 必须以 "use" 开头
  2. 组织代码

    • 将复杂逻辑拆分为多个小 Hook
    • 相关逻辑组织在一起
  3. 性能考量

    • 合理使用依赖数组
    • 避免不必要的 effect 执行
    • 大型列表考虑虚拟化
  4. 测试策略

    • 使用 @testing-library/react-hooks 测试自定义 Hook
    • 模拟依赖项进行隔离测试

常见问题解决方案

  1. 无限循环

    // 错误示例
    useEffect(() => {
      setCount(count + 1); // 会导致无限循环
    }, [count]);
    
    // 正确方式
    useEffect(() => {
      setCount(c => c + 1); // 使用函数式更新
    }, []); // 空依赖数组
    
  2. 过时闭包

    function Counter() {
      const [count, setCount] = useState(0);
    
      useEffect(() => {
        const id = setInterval(() => {
          setCount(count + 1); // 总是使用初始值
        }, 1000);
        return () => clearInterval(id);
      }, []); // 缺少 count 依赖
    
      // 正确方式
      useEffect(() => {
        const id = setInterval(() => {
          setCount(c => c + 1); // 使用函数式更新
        }, 1000);
        return () => clearInterval(id);
      }, []);
    }
    
  3. 条件执行

    // 错误示例
    if (condition) {
      useEffect(() => { ... }); // 违反 Hook 规则
    }
    
    // 正确方式
    useEffect(() => {
      if (condition) {
        // 在 effect 内部进行条件判断
      }
    }, [condition]);
    

React Hooks 通过简化组件逻辑、提高代码复用性和优化性能,已经成为现代 React 开发的标准方式。合理运用各种 Hook 可以显著提升开发效率和代码可维护性。

如何提高前端应用的性能?

作者 Riesenzahn
2025年5月18日 07:37

如何提高前端应用的性能

1. 代码优化

1.1 减少不必要的DOM操作

// 差: 频繁操作DOM
for(let i=0; i<100; i++) {
  document.getElementById('list').innerHTML += `<li>${i}</li>`;
}

// 好: 使用文档片段批量操作
const fragment = document.createDocumentFragment();
for(let i=0; i<100; i++) {
  const li = document.createElement('li');
  li.textContent = i;
  fragment.appendChild(li);
}
document.getElementById('list').appendChild(fragment);

1.2 使用事件委托

// 差: 为每个元素绑定事件
document.querySelectorAll('.btn').forEach(btn => {
  btn.addEventListener('click', handleClick);
});

// 好: 使用事件委托
document.getElementById('container').addEventListener('click', (e) => {
  if(e.target.classList.contains('btn')) {
    handleClick(e);
  }
});

2. 资源优化

2.1 图片优化

  • 使用WebP格式替代JPEG/PNG
  • 实现懒加载(Lazy Loading)
  • 使用响应式图片(srcset)
  • 压缩图片(TinyPNG等工具)

2.2 代码分割

// 动态导入实现代码分割
const module = await import('./module.js');

2.3 缓存策略

  • 设置合理的Cache-Control头
  • 使用Service Worker实现离线缓存
  • 资源文件使用内容哈希命名

3. 网络优化

3.1 使用CDN

  • 将静态资源部署到CDN
  • 选择离用户最近的CDN节点

3.2 启用HTTP/2

  • 多路复用减少连接数
  • 头部压缩减少传输量
  • 服务器推送预加载资源

3.3 预加载关键资源

<link rel="preload" href="critical.css" as="style">
<link rel="prefetch" href="next-page.js" as="script">

4. 渲染优化

4.1 减少重排和重绘

// 获取布局信息前进行批量修改
const width = element.offsetWidth; // 触发重排
element.style.width = width + 10 + 'px'; 

// 使用requestAnimationFrame优化动画
function animate() {
  // 动画逻辑
  requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

4.2 使用CSS硬件加速

.transform-element {
  transform: translateZ(0);
  will-change: transform;
}

5. 监控与分析

5.1 性能指标

  • First Contentful Paint (FCP)
  • Largest Contentful Paint (LCP)
  • Cumulative Layout Shift (CLS)
  • Time to Interactive (TTI)

5.2 性能工具

  • Lighthouse
  • WebPageTest
  • Chrome DevTools Performance面板
  • 真实用户监控(RUM)

6. 框架优化

6.1 React优化

// 使用React.memo避免不必要渲染
const MemoComponent = React.memo(MyComponent);

// 使用useCallback/useMemo缓存计算结果
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

6.2 Vue优化

// 使用v-once处理静态内容
<div v-once>{{ staticContent }}</div>

// 合理使用计算属性
computed: {
  filteredList() {
    return this.list.filter(item => item.active);
  }
}

7. 构建优化

7.1 Tree Shaking

// package.json配置sideEffects
{
  "sideEffects": ["*.css", "*.scss"]
}

7.2 压缩代码

  • 使用Terser压缩JavaScript
  • 使用CSSNano压缩CSS
  • 使用HTMLMinifier压缩HTML

8. 移动端优化

8.1 减少首屏资源

  • 关键CSS内联
  • 非关键JS延迟加载
  • 使用骨架屏提升感知性能

8.2 优化触摸事件

/* 禁用触摸高亮 */
button {
  -webkit-tap-highlight-color: transparent;
}

/* 优化滚动性能 */
.scroll-container {
  overflow-y: scroll;
  -webkit-overflow-scrolling: touch;
}

最佳实践总结

  1. 测量优先:使用性能工具找出瓶颈
  2. 渐进增强:确保核心功能在低端设备可用
  3. 按需加载:只加载当前需要的资源
  4. 持续监控:建立性能基准和报警机制
  5. 团队协作:将性能指标纳入开发流程

通过综合应用以上技术,可以显著提升前端应用的加载速度、交互流畅度和整体用户体验。性能优化是一个持续的过程,需要定期评估和调整策略。

❌
❌