主题
React Hooks 的设计哲学
从类组件到函数组件的革命性转变
如何在函数组件中管理状态和副作用?
hooks 的解决方案:
- 状态管理:
useState、useReducer - 副作用管理:
useEffect、useLayoutEffect - 上下文管理:
useContext - 性能优化:
useMemo、useCallback - 引用管理:
useRef - ...
Hooks 的设计原则
1. 稳定的调用顺序
Hooks 链表机制
React 内部将组件的 Hooks 组织成一个链表结构:
jsx
function UserProfile() {
const [name, setName] = useState('Alice') // Hook 1
const [age, setAge] = useState(25) // Hook 2
useEffect(() => {}) // Hook 3
}❌ 错误示例:条件性调用 Hooks
jsx
function UserProfile({ showAge }) {
const [name, setName] = useState('Alice') // Hook 1
if (showAge) {
const [age, setAge] = useState(25) // ❌ Hook 2
}
useEffect(() => {}) // Hook 3
}2. 明确的依赖关系
依赖数组的三种模式:无依赖数组、空依赖数组、包含依赖项
无依赖数组
jsxuseEffect(() => { console.log('每次渲染后都会执行') })空依赖数组
jsxuseEffect(() => { console.log('仅在首次渲染后执行') }, [])包含依赖项
jsxuseEffect(() => { console.log('仅在 userId 变化时执行') }, [userId])
依赖检查机制
React 使用 Object.is 比较:
- 基本类型:比较值
- 引用类型:比较引用地址
❌ 错误示例:陈旧闭包问题
先看问题:
jsx
function Timer() {
const [count, setCount] = useState(0)
useEffect(() => {
const timerId = setInterval(() => {
console.log(count) // 闭包捕获了初始的 count 值
setCount(count + 1) // 闭包捕获了初始的 count 值
}, 1000)
return () => clearInterval(timerId)
}, []) // 空依赖数组,副作用只在首次渲染时执行
return <h1>{count}</h1>
}解决方案:
jsx
// 虽然可以,但是需要频繁地创建和销毁定时器,性能较差
function Timer() {
const [count, setCount] = useState(0)
useEffect(() => {
const timerId = setInterval(() => {
console.log(count)
setCount(count + 1)
}, 1000)
return () => clearInterval(timerId)
}, [count])
return <h1>{count}</h1>
}jsx
// 推荐使用函数式更新,避免闭包问题
function Timer() {
const [count, setCount] = useState(0)
useEffect(() => {
const timerId = setInterval(() => {
setCount((count) => {
console.log(count)
return count + 1
})
}, 1000)
return () => clearInterval(timerId)
}, [])
return <h1>{count}</h1>
}