React Hooks 详解
为什么需要 Hooks
在 Hooks 出现之前,React 存在以下问题:
- 组件之间复用状态逻辑很难
- 复杂组件变得难以理解
- 难以理解的 class 组件
- this 的指向问题
基础 Hooks
useState
useState 是 React 最基础的 Hook,用于在函数组件中添加状态管理。它返回一个数组,包含:
- 当前状态值
- 更新状态的函数
useState 的特点:
- 可以多次调用,管理多个状态
- 状态更新是异步的
- 状态更新会触发组件重新渲染
- 初始值只在组件首次渲染时使用
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
);
}
useState 的进阶用法
- 函数式更新
function Counter() {
const [count, setCount] = useState(0);
// 推荐:使用函数式更新
// 主要是为了解决 状态更新的异步性和依赖性问题
const increment = () => {
setCount(prevCount => prevCount + 1);
};
// 不推荐:直接使用当前值
const decrement = () => {
setCount(count - 1);
};
}
- 惰性初始化
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation();
return initialState;
});
useEffect
用于处理副作用,如数据获取、订阅或手动修改 DOM。
import { useEffect, useState } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// 获取用户数据
const fetchUser = async () => {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
};
fetchUser();
// 清理函数
return () => {
// 在组件卸载时执行清理
};
}, [userId]); // 依赖数组
if (!user) return <div>Loading...</div>;
return <div>{user.name}</div>;
}
useEffect 的依赖项
- 空依赖数组:只在组件挂载和卸载时执行
useEffect(() => {
// 只在挂载时执行
}, []);
- 有依赖项:在依赖项变化时执行
useEffect(() => {
// 在 count 变化时执行
}, [count]);
- 没有依赖数组:每次渲染都执行
useEffect(() => {
// 每次渲染都执行
});
useContext
用于订阅 React 的 Context。
import { createContext, useContext } from 'react';
const ThemeContext = createContext('light');
function ThemedButton() {
const theme = useContext(ThemeContext);
return <button className={theme}>Themed Button</button>;
}
额外的 Hooks
useReducer
用于管理复杂的状态逻辑。
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 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
}
useCallback
useCallback 主要用于性能优化,它可以帮助我们缓存函数引用,避免在每次渲染时都创建新的函数。使用场景:
- 当函数作为 props 传递给子组件时
- 当函数在 useEffect 的依赖数组中使用时
- 当函数创建成本较高时
- 当函数需要保持引用一致性时
用于缓存回调函数,避免不必要的重渲染。
import { useCallback } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // 空依赖数组表示这个函数永远不会改变
return <ChildComponent onClick={handleClick} />;
}
useMemo
useMemo 用于缓存计算结果,适用于以下场景:
- 计算量大的操作
- 需要进行深比较的对象
- 作为其他 hook 的依赖项
- 防止子组件不必要的重渲染
用于缓存计算结果。
import { useMemo } from 'react';
function ExpensiveComponent({ data }) {
const processedData = useMemo(() => {
return data.map(item => expensiveOperation(item));
}, [data]); // 只在 data 变化时重新计算
return <div>{processedData}</div>;
}
useRef
useRef 在 React 中有两个主要用途:
- 访问 DOM 节点或 React 元素
- 获取输入框焦点
- 测量 DOM 节点的尺寸
- 保存可变值
- 存储定时器 ID
- 保存上一次的值
用于保存可变值,不会触发重渲染。
import { useRef, useEffect } from 'react';
function TextInputWithFocus() {
const inputRef = useRef(null);
useEffect(() => {
// 组件挂载时 自动聚焦
inputRef.current.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
自定义 Hooks
可以创建自定义 Hooks 来复用状态逻辑。
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
const handleResize = () => {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
// 使用自定义 Hook
function App() {
const size = useWindowSize();
return <div>Window size: {size.width}x{size.height}</div>;
}