React开发技巧总结

1. useImperativeHandle 暴露部分 API

useImperativeHandle 允许我们自定义通过 ref 暴露给父组件的实例值。

1useImperativeHandle(ref, () => ({
2  // 只暴露 focus,没有别的
3  focus() {
4    realInputRef.current.focus()
5  },
6}))

2. flushSync 同步更新DOM

flushSync 中的代码执行后,立即同步更新 DOM。

1function handleAdd() {
2  const newTodo = { id: nextId++, text }
3  flushSync(() => {
4    setText('')
5    setTodos([...todos, newTodo])
6  })
7  listRef.current.lastChild.scrollIntoView({
8    behavior: 'smooth',
9    block: 'nearest'
10  })
11}

3. useMemo 缓存计算结果

useMemo Hook 类似于 Vue 的 computed,用于缓存(或者说记忆(memoize))一个昂贵的计算。

1import { useMemo, useState } from 'react'
2
3function TodoList({ todos, filter }) {
4  const [newTodo, setNewTodo] = useState('')
5  const visibleTodos = useMemo(() => {
6    // ✅ 除非 todos 或 filter 发生变化,否则不会重新执行
7    return getFilteredTodos(todos, filter)
8  }, [todos, filter])
9  // ...
10}

4. 灵活运用组件 key

通过给组件设置 key,我们可以控制组件的重新创建和状态重置。

1<Profile
2  userId={userId}
3  key={userId} // 👈 当 userId 变化时,Profile 组件将被重新创建
4/>

5. composeRefs 组合多个 ref

当需要将多个 ref 应用到同一个元素时,可以使用 composeRefs 函数。

1function setRef<T>(ref: PossibleRef<T>, value: T) {
2  if (typeof ref === 'function')
3    ref(value)
4  else if (ref !== null && ref !== undefined)
5    (ref as React.MutableRefObject<T>).current = value
6}
7
8export function composeRefs<T>(...refs: PossibleRef<T>[]) {
9  return (node: T) => refs.forEach(ref => setRef(ref, node))
10}

6. useResizeObserver 监听元素大小变化

自定义 Hook 来监听元素大小变化并执行回调。

1import { throttle } from 'lodash'
2import { RefObject, useLayoutEffect, useMemo } from 'react'
3
4export function useResizeObserver(ref: RefObject<HTMLElement>, callBack: (element: HTMLElement) => void, delay = 50) {
5  const trigger = useMemo(
6    () =>
7      throttle((ele) => {
8        callBack(ele)
9      }, delay),
10    [],
11  )
12
13  useLayoutEffect(() => {
14    if (!ref.current)
15      return
16
17    const observer
18      = 'ResizeObserver' in window
19        ? new ResizeObserver(() => {
20          trigger(ref.current)
21        })
22        : null
23    if (observer)
24      observer.observe(ref.current)
25
26    // initial check
27    trigger(ref.current)
28    return () => {
29      // disconnect
30      if (observer)
31        observer.disconnect()
32    }
33  }, [ref])
34}

7. React 深色模式/暗黑模式

使用 media 查询来设置主题色。

1<meta
2  name="theme-color"
3  media="(prefers-color-scheme: light)"
4  content="white" />
5<meta 
6  name="theme-color" 
7  media="(prefers-color-scheme: dark)" 
8  content="black" />

8. useEffect 使用心得

  • 空依赖数组 [] 表示只在组件挂载时执行一次。
  • 不提供依赖数组会在每次渲染后执行。
  • 小心使用 DOM refs 作为依赖。

9. useReducer 管理复杂状态

用于管理复杂的状态逻辑,类似于 Redux 中的 reducer 概念。

1import React, { useReducer } from 'react';
2
3const initialState = { count: 0 };
4
5function reducer(state, action) {
6  switch (action.type) {
7    case 'increment':
8      return { count: state.count + 1 };
9    case 'decrement':
10      return { count: state.count - 1 };
11    default:
12      throw new Error();
13  }
14}
15
16function Counter() {
17  const [state, dispatch] = useReducer(reducer, initialState);
18  return (
19    <>
20      Count: {state.count}
21      <button onClick={() => dispatch({type: 'increment'})}>+</button>
22      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
23    </>
24  );
25}

10. 自定义 Hook

提取可重用的组件逻辑到自定义 Hook 中。

1import { useState, useEffect } from 'react';
2
3function useWindowSize() {
4  const [windowSize, setWindowSize] = useState({
5    width: undefined,
6    height: undefined,
7  });
8
9  useEffect(() => {
10    function handleResize() {
11      setWindowSize({
12        width: window.innerWidth,
13        height: window.innerHeight,
14      });
15    }
16    
17    window.addEventListener("resize", handleResize);
18    handleResize();
19    
20    return () => window.removeEventListener("resize", handleResize);
21  }, []);
22
23  return windowSize;
24}

11. React.memo 和 useCallback

使用 React.memouseCallback 来优化性能,避免不必要的重渲染。

1import React, { useCallback, useState } from 'react';
2
3const MyComponent = React.memo(function MyComponent(props) {
4  /* 只有当props改变时才会重新渲染 */
5});
6
7function Parent() {
8  const [count, setCount] = useState(0);
9
10  const increment = useCallback(() => {
11    setCount(c => c + 1);
12  }, []);
13
14  return <MyComponent onIncrement={increment} />;
15}

12. Error Boundaries

使用错误边界捕获子组件树中的 JavaScript 错误。

1class ErrorBoundary extends React.Component {
2  constructor(props) {
3    super(props);
4    this.state = { hasError: false };
5  }
6
7  static getDerivedStateFromError(error) {
8    return { hasError: true };
9  }
10
11  componentDidCatch(error, errorInfo) {
12    logErrorToMyService(error, errorInfo);
13  }
14
15  render() {
16    if (this.state.hasError) {
17      return <h1>Something went wrong.</h1>;
18    }
19
20    return this.props.children; 
21  }
22}

13. Portals

使用 Portals 将子节点渲染到父组件 DOM 树之外的 DOM 节点。

1import ReactDOM from 'react-dom';
2
3function Modal({ children }) {
4  return ReactDOM.createPortal(
5    children,
6    document.getElementById('modal-root')
7  );
8}

14. useLatestCallback

用于替代 useCallback,解决返回函数地址不同的问题。

1export function useLatestCallback<F extends (...args: any[]) => any>(fn: F): LatestCallback<F> {
2  const cb = useRef<LatestCallback<F>>()
3  if ('fn' in fn)
4    return fn as any
5
6  if (!cb.current)
7    cb.current = Object.assign<any, any>((...args: any[]) => cb.current!.fn(...args), { fn })
8  else if (cb.current.fn !== fn)
9    cb.current.fn = fn
10
11  return cb.current!
12}

这篇总结涵盖了 React 开发中的多种技巧和最佳实践,从基本的 Hook 使用到高级的性能优化技巧。希望这些内容对您的 React 开发有所帮助。如果您需要更深入的解释或有任何疑问,请随时告诉我。