概述
React Hooks 是 React 16.8 引入的新特性,允许在函数组件中使用状态和其他 React 特性。根据数据的使用场景和更新机制,可以将 Hooks 分为三大类:
1. 保存只读数据
useMemo
用途: 缓存计算结果,避免重复计算
语法:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
特点:
- 只有当依赖项发生变化时才重新计算
- 适用于计算量大的操作
- 返回缓存的值
使用场景:
// 复杂计算
const expensiveValue = useMemo(() => {
return items
.filter((item) => item.price > 100)
.map((item) => ({
...item,
discount: item.price * 0.1,
}));
}, [items]);
// 对象/数组缓存
const memoizedObject = useMemo(
() => ({
id: user.id,
name: user.name,
avatar: user.avatar,
}),
[user.id, user.name, user.avatar]
);
useCallback
用途: 缓存函数,避免子组件不必要的重新渲染
语法:
const memoizedCallback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
特点:
- 只有当依赖项变化时才创建新的函数
- 主要用于性能优化
- 返回缓存的函数引用
使用场景:
// 传递给子组件的回调函数
const handleClick = useCallback((id) => {
setSelectedId(id);
fetchUserDetails(id);
}, []);
// 事件处理函数
const handleSubmit = useCallback(
(formData) => {
submitForm(formData);
},
[submitForm]
);
// 子组件优化
const ChildComponent = React.memo(({ onUpdate }) => {
return <button onClick={onUpdate}>更新</button>;
});
2. 保存可变数据,更改时触发渲染
useState
用途: 管理组件内的简单状态
语法:
const [state, setState] = useState(initialState);
特点:
- 状态更新会触发组件重新渲染
- 异步更新,多个 setState 会被批处理
- 适合简单的状态管理
使用场景:
// 基础状态管理
const [count, setCount] = useState(0);
const [name, setName] = useState("");
const [isLoading, setIsLoading] = useState(false);
// 对象状态
const [user, setUser] = useState({
name: "",
email: "",
age: 0,
});
// 函数式更新
setCount((prevCount) => prevCount + 1);
setUser((prevUser) => ({
...prevUser,
name: "新名字",
}));
useReducer
用途: 管理复杂的状态逻辑
语法:
const [state, dispatch] = useReducer(reducer, initialState, init);
特点:
- 状态更新逻辑集中管理
- 适合复杂的状态转换
- 可以处理多个相关的状态
使用场景:
// 定义 reducer
const todoReducer = (state, action) => {
switch (action.type) {
case "ADD_TODO":
return {
...state,
todos: [...state.todos, action.payload],
};
case "TOGGLE_TODO":
return {
...state,
todos: state.todos.map((todo) =>
todo.id === action.payload
? { ...todo, completed: !todo.completed }
: todo
),
};
case "DELETE_TODO":
return {
...state,
todos: state.todos.filter((todo) => todo.id !== action.payload),
};
default:
return state;
}
};
// 使用 useReducer
const [state, dispatch] = useReducer(todoReducer, {
todos: [],
filter: "all",
});
// 分发 action
dispatch({ type: "ADD_TODO", payload: { id: 1, text: "学习 React" } });
dispatch({ type: "TOGGLE_TODO", payload: 1 });
3. 保存可变数据,更改时不触发渲染
useRef
用途: 保存可变值,不触发重新渲染
语法:
const refContainer = useRef(initialValue);
特点:
- 值的变化不会触发组件重新渲染
- 可以访问 DOM 元素
- 适合存储不需要触发渲染的数据
使用场景:
// 访问 DOM 元素
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
// 存储定时器 ID
const timerRef = useRef(null);
useEffect(() => {
timerRef.current = setInterval(() => {
console.log("定时器执行");
}, 1000);
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
}
};
}, []);
// 存储前一次的值
const prevCountRef = useRef();
useEffect(() => {
prevCountRef.current = count;
});
// 存储实例变量
const instanceRef = useRef({
isMounted: false,
data: null,
});
最佳实践
性能优化
// 1. 合理使用 useMemo 和 useCallback
const expensiveValue = useMemo(() => {
return heavyComputation(data);
}, [data]);
const handleClick = useCallback(() => {
// 处理点击事件
}, [dependencies]);
// 2. 避免在渲染中创建对象/函数
// ❌ 错误做法
const Component = () => {
const [count, setCount] = useState(0);
// 每次渲染都会创建新对象
const style = { color: "red" };
const handleClick = () => setCount(count + 1);
return (
<div style={style} onClick={handleClick}>
{count}
</div>
);
};
// ✅ 正确做法
const Component = () => {
const [count, setCount] = useState(0);
const style = useMemo(() => ({ color: "red" }), []);
const handleClick = useCallback(() => setCount((prev) => prev + 1), []);
return (
<div style={style} onClick={handleClick}>
{count}
</div>
);
};
状态管理选择
// 简单状态 - 使用 useState
const [isVisible, setIsVisible] = useState(false);
const [userName, setUserName] = useState("");
// 复杂状态 - 使用 useReducer
const [formState, dispatch] = useReducer(formReducer, {
fields: {},
errors: {},
isValid: false,
});
// 不需要渲染的数据 - 使用 useRef
const previousValue = useRef(null);
const timeoutId = useRef(null);
依赖项管理
// 1. 正确设置依赖项
useEffect(() => {
fetchData(userId);
}, [userId]); // 包含所有依赖项
// 2. 使用 useCallback 避免依赖项变化
const fetchData = useCallback((id) => {
// 获取数据逻辑
}, []); // 空依赖数组
// 3. 使用 useRef 存储最新值
const latestValue = useRef(value);
useEffect(() => {
latestValue.current = value;
});
useEffect(() => {
const timer = setInterval(() => {
console.log(latestValue.current); // 总是获取最新值
}, 1000);
return () => clearInterval(timer);
}, []); // 不需要依赖 value
常见陷阱
1. 过度优化
// ❌ 不必要的 useMemo
const simpleValue = useMemo(() => count + 1, [count]);
// ✅ 直接计算
const simpleValue = count + 1;
2. 依赖项遗漏
// ❌ 遗漏依赖项
useEffect(() => {
console.log(count);
}, []); // 缺少 count 依赖
// ✅ 正确设置依赖项
useEffect(() => {
console.log(count);
}, [count]);
3. 闭包陷阱
// ❌ 闭包陷阱
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log(count); // 总是打印初始值
}, []); // 空依赖数组
// ✅ 解决方案 1: 添加依赖项
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
// ✅ 解决方案 2: 使用函数式更新
const handleClick = useCallback(() => {
setCount((prevCount) => {
console.log(prevCount);
return prevCount + 1;
});
}, []);
总结
选择合适的 Hook 取决于具体的使用场景:
- useMemo/useCallback: 用于性能优化,缓存计算结果和函数
- useState/useReducer: 用于状态管理,状态变化会触发重新渲染
- useRef: 用于存储不需要触发渲染的可变数据
合理使用这些 Hook 可以提升组件性能,避免不必要的重新渲染,同时保持代码的可读性和可维护性。
原文链接:https://code.ifrontend.net/archives/1095,转载请注明出处。
评论0