Appearance
React Hooks 深入理解
React Hooks 是 React 16.8 引入的革命性特性,它让我们可以在函数组件中使用状态和生命周期等特性。本文将深入探讨 React Hooks 的原理、使用场景和最佳实践。
什么是 Hooks
Hooks 是一些特殊的函数,让你可以在函数组件中"钩入" React 的特性。它们让你无需编写类组件就能使用 React 的功能。
为什么需要 Hooks
在 Hooks 出现之前,函数组件只能作为"无状态组件"使用。如果需要状态或生命周期,必须使用类组件。Hooks 的出现解决了以下问题:
- 逻辑复用困难:高阶组件和渲染属性模式复杂
- 复杂组件难以理解:相关逻辑分散在不同生命周期中
- 类组件的复杂性:
this绑定、性能优化等问题
基础 Hooks
useState
useState 是最常用的 Hook,用于在函数组件中添加状态。
javascript
import { useState } from 'react'
function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>你点击了 {count} 次</p>
<button onClick={() => setCount(count + 1)}>
点击我
</button>
</div>
)
}函数式更新:当新的状态依赖于旧状态时,使用函数式更新:
javascript
const [count, setCount] = useState(0)
// ❌ 可能不正确
setCount(count + 1)
setCount(count + 1)
// ✅ 正确
setCount(prevCount => prevCount + 1)
setCount(prevCount => prevCount + 1)useEffect
useEffect 用于处理副作用,相当于类组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 的组合。
javascript
import { useState, useEffect } from 'react'
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
useEffect(() => {
// 组件挂载和更新时执行
fetchUser(userId).then(setUser)
// 清理函数(可选)
return () => {
// 组件卸载时执行清理
}
}, [userId]) // 依赖数组
return user ? <div>{user.name}</div> : <div>加载中...</div>
}依赖数组的作用:
- 空数组
[]:只在组件挂载时执行一次 - 有依赖项:依赖项变化时执行
- 无依赖数组:每次渲染都执行
useContext
useContext 用于在函数组件中访问 Context。
javascript
import { createContext, useContext } from 'react'
const ThemeContext = createContext('light')
function ThemedButton() {
const theme = useContext(ThemeContext)
return <button className={theme}>按钮</button>
}高级 Hooks
useReducer
useReducer 是 useState 的替代方案,适用于复杂的状态逻辑。
javascript
import { useReducer } from 'react'
const initialState = { count: 0 }
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
case 'reset':
return initialState
default:
throw new Error()
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState)
return (
<>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'reset' })}>重置</button>
</>
)
}useMemo
useMemo 用于缓存计算结果,避免不必要的重复计算。
javascript
import { useMemo } from 'react'
function ExpensiveComponent({ items, filter }) {
const filteredItems = useMemo(() => {
return items.filter(item => item.category === filter)
}, [items, filter])
return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}useCallback
useCallback 用于缓存函数,避免子组件不必要的重新渲染。
javascript
import { useState, useCallback } from 'react'
function Parent() {
const [count, setCount] = useState(0)
const handleClick = useCallback(() => {
console.log('点击了')
}, []) // 依赖数组
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={handleClick} />
</div>
)
}
function Child({ onClick }) {
return <button onClick={onClick}>子组件</button>
}useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数。
javascript
import { useRef, useEffect } from 'react'
function TextInputWithFocusButton() {
const inputEl = useRef(null)
const onButtonClick = () => {
inputEl.current.focus()
}
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>聚焦输入框</button>
</>
)
}保存可变值:useRef 还可以用来保存任何可变值,类似于类组件的实例属性。
javascript
function Timer() {
const intervalRef = useRef(null)
useEffect(() => {
intervalRef.current = setInterval(() => {
console.log('定时器运行中')
}, 1000)
return () => {
clearInterval(intervalRef.current)
}
}, [])
return <div>定时器组件</div>
}自定义 Hooks
自定义 Hook 是一个以 "use" 开头的函数,可以在其中调用其他 Hooks。
数据获取 Hook
javascript
import { useState, useEffect } from 'react'
function useFetch(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
setLoading(true)
fetch(url)
.then(res => res.json())
.then(data => {
setData(data)
setLoading(false)
})
.catch(error => {
setError(error)
setLoading(false)
})
}, [url])
return { data, loading, error }
}
// 使用
function UserProfile({ userId }) {
const { data: user, loading, error } = useFetch(`/api/users/${userId}`)
if (loading) return <div>加载中...</div>
if (error) return <div>错误: {error.message}</div>
return <div>{user.name}</div>
}本地存储 Hook
javascript
import { useState, useEffect } from 'react'
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
} catch (error) {
return initialValue
}
})
const setValue = (value) => {
try {
setStoredValue(value)
window.localStorage.setItem(key, JSON.stringify(value))
} catch (error) {
console.error(error)
}
}
return [storedValue, setValue]
}
// 使用
function App() {
const [name, setName] = useLocalStorage('name', '')
return (
<input
value={name}
onChange={e => setName(e.target.value)}
placeholder="输入你的名字"
/>
)
}Hooks 规则
只在顶层调用 Hooks
不要在循环、条件或嵌套函数中调用 Hooks。
javascript
// ❌ 错误
function Component({ condition }) {
if (condition) {
const [state, setState] = useState(0) // 错误!
}
}
// ✅ 正确
function Component({ condition }) {
const [state, setState] = useState(0) // 正确
if (condition) {
// 使用 state
}
}只在 React 函数中调用 Hooks
- ✅ 在 React 函数组件中调用
- ✅ 在自定义 Hook 中调用
- ❌ 在普通 JavaScript 函数中调用
性能优化
避免不必要的渲染
javascript
import { memo, useMemo, useCallback } from 'react'
// 使用 memo 包装组件
const ExpensiveComponent = memo(function ExpensiveComponent({ data }) {
// 组件逻辑
})
// 使用 useMemo 缓存计算结果
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b)
}, [a, b])
// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
doSomething(a, b)
}, [a, b])最佳实践
- 按功能组织 Hooks:将相关的状态和副作用放在一起
- 提取自定义 Hooks:将可复用的逻辑提取到自定义 Hook 中
- 合理使用依赖数组:确保依赖数组包含所有使用的值
- 避免过度优化:不要过早优化,先确保代码正确
- 遵循 Hooks 规则:始终在顶层调用 Hooks
总结
React Hooks 为函数组件带来了强大的能力,让我们可以编写更简洁、可复用的代码。通过合理使用基础 Hooks 和自定义 Hooks,我们可以构建出高效、易维护的 React 应用。掌握 Hooks 是现代 React 开发的关键技能。