topic: frontend
React Hooks 深入理解
用了两年 React,Hooks 天天用,但原理一直没深究。
2022 年底,总算是把原理搞明白了点。
Hooks 是什么
Hook 是 React 16.8 引入的新特性,让函数组件也能用 state 和生命周期。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| class Counter extends React.Component { state = { count: 0 }; render() { return <div>{this.state.count}</div>; } }
function Counter() { const [count, setCount] = useState(0); return <div>{count}</div>; }
|
useState
基本用法
1
| const [count, setCount] = useState(0);
|
返回:
count:当前状态值
setCount:更新状态的函数
原理
每次渲染,useState 返回一个数组:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| let state; let setters = [];
function useState(initialValue) { if (!state) { state = initialValue; } const index = setters.length; if (!setters[index]) { setters[index] = (newValue) => { state = newValue; render(); }; } return [state, setters[index]]; }
|
闭包问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { console.log(count); }, 1000); return () => clearInterval(timer); }, []); return <div>{count}</div>; }
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { setCount(c => c + 1); }, 1000); return () => clearInterval(timer); }, []); return <div>{count}</div>; }
function Counter() { const [count, setCount] = useState(0); useEffect(() => { const timer = setInterval(() => { console.log(count); }, 1000); return () => clearInterval(timer); }, [count]); return <div>{count}</div>; }
|
useEffect
基本用法
1 2 3 4 5 6 7
| useEffect(() => { return () => { }; }, [deps]);
|
执行时机
- 首次渲染后执行
- 依赖变化后执行
- 组件卸载时执行清理
依赖数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| useEffect(() => { console.log('每次都执行'); });
useEffect(() => { console.log('只执行一次'); }, []);
useEffect(() => { console.log('count 变化了', count); }, [count]);
|
清理函数
1 2 3 4 5 6 7 8 9 10 11
| useEffect(() => { const subscription = api.subscribe(data => { setData(data); }); return () => { subscription.unsubscribe(); }; }, []);
|
useRef
基本用法
1 2 3 4 5 6 7
| const inputRef = useRef(null);
function handleClick() { inputRef.current.focus(); }
return <input ref={inputRef} />;
|
特点
用途
1 2 3 4 5 6 7 8 9 10
| const inputRef = useRef(null);
const countRef = useRef(0); countRef.current++;
const prevCountRef = useRef(0); prevCountRef.current = count;
|
useCallback vs useMemo
useCallback
缓存函数:
1 2 3
| const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);
|
useMemo
缓存计算结果:
1 2 3
| const memoizedValue = useMemo(() => { return computeExpensiveValue(a, b); }, [a, b]);
|
区别
useCallback:缓存函数
useMemo:缓存值
1 2 3 4
| const memoizedCallback = useCallback(fn, deps);
const memoizedCallback = useMemo(() => fn, deps);
|
自定义 Hook
什么是自定义 Hook
封装可复用的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| function useLocalStorage(key, initialValue) { const [value, setValue] = useState(() => { const item = localStorage.getItem(key); return item ? JSON.parse(item) : initialValue; }); useEffect(() => { localStorage.setItem(key, JSON.stringify(value)); }, [key, value]); return [value, setValue]; }
function App() { const [name, setName] = useLocalStorage('name', ''); return <input value={name} onChange={e => setName(e.target.value)} />; }
|
常见自定义 Hook
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| function useDebounce(value, delay) { const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => clearTimeout(handler); }, [value, delay]); return debouncedValue; }
function usePrevious(value) { const ref = useRef(); useEffect(() => { ref.current = value; }, [value]); return ref.current; }
function useOnClickOutside(ref, handler) { useEffect(() => { const listener = (event) => { if (!ref.current || ref.current.contains(event.target)) { return; } handler(event); }; document.addEventListener('mousedown', listener); document.addEventListener('touchstart', listener); return () => { document.removeEventListener('mousedown', listener); document.removeEventListener('touchstart', listener); }; }, [ref, handler]); }
|
Hooks 规则
1. 只在顶层调用
1 2 3 4 5 6 7
| for (let i = 0; i < 10; i++) { const [state, setState] = useState(i); }
const [states, setStates] = useState(new Array(10).fill(0));
|
2. 只在 React 函数中调用
1 2 3 4 5 6 7 8 9 10
| function handleClick() { const [count, setCount] = useState(0); }
function App() { const [count, setCount] = useState(0); }
|
3. 使用 ESLint 插件
1
| npm install eslint-plugin-react-hooks
|
总结
Hooks 让函数组件更强大。
- useState:状态管理
- useEffect:副作用
- useRef:DOM 和可变值
- useMemo/useCallback:性能优化
- 自定义 Hook:逻辑复用
理解原理,用起来更顺手。