React常见的Hooks
React Hooks 是 React 16.8 引入的特性,允许你在函数组件中使用状态和其他 React 特性。以下是 React 中主要的 Hooks 分类和具体用法:
一、基础Hooks
1、useState:
- 在函数中添加状态管理
- 返回一个状态值和一个更新状态的函数
import { useState } from 'react';function Counter() {const [count, setCount] = useState(0);return (<div><p>You clicked {count} times</p><button onClick={() => setCount(count + 1)}>Click me</button></div>);
}
2、useEffect:
- 处理副作用(数据获取、订阅、手动修改DOM等)
- 可以模拟 componentDidMount、componentDidUpdate 和 componentWillUnmount
import { useState, useEffect } from 'react';function Example() {const [data, setData] = useState(null);useEffect(() => {fetch('https://api.example.com/data').then(res => res.json()).then(data => setData(data));return () => {// 清理函数(如取消订阅)};}, []); // 空数组表示只在组件挂载时运行一次
}
3、useContext:访问React中的上下文(Context),避免多层 props 传递。
import { useContext } from 'react';
const ThemeContext = React.createContext('light');function ThemedButton() {const theme = useContext(ThemeContext);return <button style={{ background: theme === 'dark' ? '#333' : '#eee' }}>按钮</button>;
}
二、额外Hooks
4、useReducer:复杂状态逻辑管理(类似Redux)
useReducer 基本语法:
const [state, dispatch] = useReducer(reducer, initialState, initFunction);
参数说明:
- reducer:一个函数,形式为(state,antion)=> newState
- initialStata:状态初始值
- initFunction(可选):用于惰性初始化状态的函数
工作原理:
- 组件通过dispatch(action)发出动作
- React调用reducer(currentState,action)计算新状态
- 组件使用新状态重新渲染
import { useReducer } from 'react';function reducer(state, action) {switch (action.type) {case 'increment':return { count: state.count + 1 };default:throw new Error();}
}function Counter() {const [state, dispatch] = useReducer(reducer, { count: 0 });return (<>Count: {state.count}<button onClick={() => dispatch({ type: 'increment' })}>+</button></>);
}
5、useRef:访问DOM节点或保存可变值(不会触发重新渲染)
访问DOM节点:
const inputRef = useRef(null);
<input ref={inputRef} />
保存可变值:创建可变引用,修改其值不会触发更新
import { useRef } from 'react';function Counter() {const countRef = useRef(0); // 初始化值为0const handleClick = () => {countRef.current += 1; // 修改ref值不会触发重新渲染console.log(countRef.current);};return <button onClick={handleClick}>点击</button>;
}
特点:
- 不会触发重新渲染:修改 .current属性,不会导致组件重新渲染
- 跨渲染周期保持值:在组件的整个生命周期中保持同一个引用
- 与实例变量类似:类似于类组件中的this.xxx属性
常见使用场景:
1、访问DOM元素
function TextInput() {const inputRef = useRef(null);const focusInput = () => {inputRef.current.focus(); // 访问DOM元素};return (<><input ref={inputRef} type="text" /><button onClick={focusInput}>聚焦输入框</button></>);
}
2、保存可变值(不触发重新渲染)
function Timer() {const countRef = useRef(0);const [dummy, setDummy] = useState(0); // 用于强制渲染useEffect(() => {const id = setInterval(() => {countRef.current += 1; // 修改ref值console.log(countRef.current);// 每5次更新一次UIif (countRef.current % 5 === 0) {setDummy(d => d + 1);}}, 1000);return () => clearInterval(id);}, []);return <div>计数: {countRef.current}</div>;
}
3、保存上一次值
function Counter() {const [count, setCount] = useState(0);const prevCountRef = useRef();useEffect(() => {prevCountRef.current = count; // 在渲染后更新ref}); // 没有依赖数组,每次渲染后都执行return (<div><p>当前: {count}, 之前: {prevCountRef.current}</p><button onClick={() => setCount(c => c + 1)}>增加</button></div>);
}
与useState区别:
特性 | useRef | useState |
---|---|---|
触发重新渲染 | 否 | 是 |
值存储位置 | 返回对象的.current属性 | 直接返回状态值 |
更新方式 | 直接修改.current | 必须通过setState函数 |
使用场景 | 需要保持可变但不触发渲染的值 | 需要触发UI更新的状态 |
4、高级用法-在useCallback中使用ref
function Form() {const [value, setValue] = useState('');const valueRef = useRef(value);useEffect(() => {valueRef.current = value; // 同步value到ref}, [value]);const handleSubmit = useCallback(() => {console.log('提交的值:', valueRef.current); // 总是获取最新值}, []); // 不需要依赖valuereturn (<form onSubmit={handleSubmit}><input value={value} onChange={e => setValue(e.target.value)} /></form>);
}
5、实现组件实例方法
function FancyInput(props, ref) {const inputRef = useRef();useImperativeHandle(ref, () => ({focus: () => {inputRef.current.focus();},scrollIntoView: () => {inputRef.current.scrollIntoView();}}));return <input ref={inputRef} />;
}const ForwardedFancyInput = forwardRef(FancyInput);// 父组件使用
function Parent() {const inputRef = useRef();const handleClick = () => {inputRef.current.focus(); // 调用子组件方法};return (<><ForwardedFancyInput ref={inputRef} /><button onClick={handleClick}>聚焦输入框</button></>);
}
注意事项:
1、不要在渲染期间写入ref
// ❌ 错误写法
function MyComponent() {const myRef = useRef();myRef.current = 123; // 渲染期间修改refreturn ...;
}// ✅ 正确写法
function MyComponent() {const myRef = useRef(123); // 初始化时设置useEffect(() => {myRef.current = 456; // 在effect中修改}, []);return ...;
}
2、与 useEffect 配合使用时注意时序:
useEffect(() => {// 这里的ref.current可能不是最新的
}, []);useEffect(() => {// 更好的做法是在依赖数组中包含ref.current
}, [ref.current]);
6、useMemo:性能优化、缓存计算结果
useMemo 是 React 提供的一个性能优化 Hook,用于缓存计算结果,避免在每次渲染时都进行高开销的计算
基本语法:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
参数说明:
- 计算函数:返回需要缓存的值
- 依赖数组:只有依赖变化的时候,才会重新计算值
常见场景:
1、复杂计算优化
const sortedList = useMemo(() => {return largeList.sort((a, b) => a.value - b.value);
}, [largeList]);
2、避免子组件不必要的渲染
const childProps = useMemo(() => ({ value: props.value }), [props.value]);
return <Child {...childProps} />;
3、引用类型稳定性
const config = useMemo(() => ({color: theme === 'dark' ? 'white' : 'black',size: 'large'
}), [theme]);
错误写法:
缺少依赖项
const badExample = useMemo(() => a + b, [a]); // 缺少b依赖
副作用操作
// 不应该在useMemo中执行副作用
useMemo(() => {fetchData(); // ❌ 副作用应放在useEffect中
}, []);
总是重新计算
// 空依赖数组表示只计算一次
const once = useMemo(() => compute(a), []);
// 如果a可能变化,这会导致使用过期值
注意:
- 所有回调函数内部用到的变量都必须包含在依赖数组中
- 故意省略依赖是危险的,会导致缓存失效或使用过期值
- 如果发现需要省略某些依赖,通常意味着代码结构需要重构
7、useCallback:缓存函数,避免不必要的重新渲染 从而优化性能
useCallback 会返回一个记忆化(memoized)的回调函数,只有当依赖项发生变化时才会重新创建函数。它的主要用途是:
- 避免子组件不必要的重新渲染(配合 React.memo 使用)
- 保持函数引用稳定,避免作为依赖项触发不必要的 effect 执行
- 优化性能敏感的场景(如高频触发的事件处理)
基本语法:
const memoizedCallback = useCallback(
() => {
// 函数逻辑
},
[dep1, dep2], // 依赖项数组
);
典型使用场景:
1、避免子组件不必要的重新渲染
// 子组件
const Child = React.memo(function Child({ onClick }) {console.log('Child 渲染');return <button onClick={onClick}>点击</button>;
});// 父组件
function Parent() {const [count, setCount] = useState(0);// 使用useCallback缓存函数const handleClick = useCallback(() => {console.log('点击处理');}, []); // 空依赖表示函数永远不会改变return (<div><Child onClick={handleClick} /><button onClick={() => setCount(c => c + 1)}>计数: {count}</button></div>);
}
效果:当父组件状态更新时,Child 组件不会重新渲染,因为 onClick 的引用保持不变。