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 开发有所帮助。如果您需要更深入的解释或有任何疑问,请随时告诉我。