React 中hooks之 React useCallback使用方法总结
1. useCallback 基础概念
useCallback 是 React 的一个 Hook,用于记忆函数定义,避免在每次渲染时创建新的函数实例。它在需要将回调函数传递给经过优化的子组件时特别有用。
当state变化的时候引起组件重新渲染执行会导致某个方法被反复创建增加内存负担,这个时候可以使用useCallback将该函数进行缓存,只创建一次
1.1 基本语法
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b], // 依赖项数组
);
同样的当依赖项省略时组件重新渲染都会执行,当依赖项为空数组的时候只有组件初始化的时候会执行一次,数组里有依赖项的时候依赖项发生变化的时候都会缓存一次
1.2 与普通函数的区别
function ParentComponent() {
const [count, setCount] = useState(0);
// ❌ 每次渲染都会创建新的函数实例
const handleClick = () => {
console.log('Clicked');
};
// ✅ 函数实例会被记忆,只在依赖项变化时更新
const handleClickMemoized = useCallback(() => {
console.log('Clicked');
}, []); // 空依赖数组,函数永远不会改变
return <ChildComponent onClick={handleClickMemoized} />;
}
2. useCallback 配合 React.memo 使用
2.1 基本示例
// 子组件使用 React.memo 优化
const ChildComponent = React.memo(function ChildComponent({ onClick }) {
console.log("ChildComponent rendered");
return <button onClick={onClick}>Click me</button>;
});
// 父组件使用 useCallback
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// 使用 useCallback 记忆回调函数
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []); // 空依赖数组,因为不依赖任何值
return (
<div>
<input value={text} onChange={e => setText(e.target.value)} />
<p>Count: {count}</p>
<ChildComponent onClick={handleClick} />
</div>
);
}
2.2 带有依赖项的示例
function SearchComponent({ onSearch }) {
const [searchTerm, setSearchTerm] = useState("");
const [searchHistory, setSearchHistory] = useState([]);
// 使用 useCallback 记忆搜索函数
const handleSearch = useCallback(() => {
if (searchTerm.trim()) {
onSearch(searchTerm);
setSearchHistory(prev => [...prev, searchTerm]);
}
}, [searchTerm, onSearch]); // 依赖 searchTerm 和 onSearch
return (
<div>
<input
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
/>
<SearchButton onClick={handleSearch} />
<SearchHistory items={searchHistory} />
</div>
);
}
// 优化的子组件
const SearchButton = React.memo(function SearchButton({ onClick }) {
console.log("SearchButton rendered");
return <button onClick={onClick}>搜索</button>;
});
const SearchHistory = React.memo(function SearchHistory({ items }) {
return (
<ul>
{items.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
);
});
3. 实际应用场景
3.1 表单处理
function ComplexForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
// 记忆表单字段更新函数
const handleFieldChange = useCallback((fieldName) => (event) => {
setFormData(prev => ({
...prev,
[fieldName]: event.target.value
}));
}, []); // 不需要依赖项,因为使用了函数式更新
return (
<form>
<FormField
label="Name"
value={formData.name}
onChange={handleFieldChange('name')}
/>
<FormField
label="Email"
value={formData.email}
onChange={handleFieldChange('email')}
/>
<FormField
label="Message"
value={formData.message}
onChange={handleFieldChange('message')}
/>
</form>
);
}
const FormField = React.memo(function FormField({ label, value, onChange }) {
console.log(`${label} field rendered`);
return (
<div>
<label>{label}</label>
<input value={value} onChange={onChange} />
</div>
);
});
3.2 列表渲染优化
function TodoList() {
const [todos, setTodos] = useState([]);
// 记忆添加任务函数
const handleAdd = useCallback((text) => {
setTodos(prev => [...prev, { id: Date.now(), text, completed: false }]);
}, []);
// 记忆切换完成状态函数
const handleToggle = useCallback((id) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []);
// 记忆删除函数
const handleDelete = useCallback((id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
return (
<div>
<AddTodo onAdd={handleAdd} />
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
/>
))}
</div>
);
}
const TodoItem = React.memo(function TodoItem({ todo, onToggle, onDelete }) {
return (
<div>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>
{todo.text}
</span>
<button onClick={() => onDelete(todo.id)}>删除</button>
</div>
);
});
4. 性能优化最佳实践
4.1 合理使用依赖项
function UserProfile({ userId, onUpdate }) {
// ✅ 只在 userId 或 onUpdate 变化时更新
const handleUpdate = useCallback(() => {
onUpdate(userId);
}, [userId, onUpdate]);
// ❌ 不必要的依赖项
const handleClick = useCallback(() => {
console.log('Clicked');
}, [userId]); // userId 不需要作为依赖项
}
4.2 避免过度优化
// ❌ 简单组件不需要使用 useCallback
function SimpleButton({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
// ✅ 复杂组件或频繁重渲染的组件使用 useCallback
const ComplexComponent = React.memo(function ComplexComponent({ onAction }) {
// 复杂的渲染逻辑
return (
// ...
);
});
5. useCallback 与其他 Hooks 配合
5.1 配合 useEffect 使用
function DataFetcher({ query }) {
const [data, setData] = useState(null);
// 记忆获取数据的函数
const fetchData = useCallback(async () => {
const response = await fetch(`/api/search?q=${query}`);
const result = await response.json();
setData(result);
}, [query]);
// 在 effect 中使用记忆的函数
useEffect(() => {
fetchData();
}, [fetchData]); // fetchData 作为依赖项
return <div>{/* 渲染数据 */}</div>;
}
5.2 配合 useMemo 使用
function DataProcessor({ data, onProcess }) {
// 记忆处理函数
const processData = useCallback((item) => {
// 复杂的数据处理逻辑
return someExpensiveOperation(item);
}, []);
// 使用记忆的函数处理数据
const processedData = useMemo(() => {
return data.map(processData);
}, [data, processData]);
return (
<div>
{processedData.map(item => (
<ProcessedItem
key={item.id}
item={item}
onProcess={onProcess}
/>
))}
</div>
);
}
6. 注意事项
-
避免过度使用
- 只在性能确实受影响时使用
- 简单组件和回调不需要使用 useCallback
-
正确设置依赖项
- 包含所有回调中使用的变量
- 避免不必要的依赖项
-
配合 React.memo 使用
- 单独使用 useCallback 可能无法带来性能提升
- 需要配合 React.memo 等优化手段
-
考虑使用场景
- 频繁重渲染的组件
- 复杂的计算或操作
- 传递给多个子组件的回调
通过合理使用 useCallback 和 React.memo,我们可以有效优化 React 应用的性能。但要记住,过度优化可能会适得其反,应该在实际需要时才进行优化。