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.memo 和 useCallback 来优化性能,避免不必要的重渲染。
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 开发有所帮助。如果您需要更深入的解释或有任何疑问,请随时告诉我。