如何避免自定义hook与Student组件的不必要重渲染?
问题描述
当前代码中,仅操作第一个<Student {...studentStateA} />组件(比如在输入框输入值并按回车)时,useStudentState自定义Hook和所有Student组件都会触发重渲染——自定义Hook执行两次,每个Student组件各渲染一次。希望将Student作为纯展示的哑组件,请问当前架构是否存在问题?如何避免其他子组件(如<Student {...studentStateB} />)及App中自定义Hook的不必要重渲染?
现有代码
App.jsx
import Student from "./Student"; import useStudentState from "./useStudentState"; const initialStudentsA = ["foo", "bar"]; const initialStudentsB = ["bat", "baz"]; function App() { const studentStateA = useStudentState({ initialStudents: initialStudentsA }); const studentStateB = useStudentState({ initialStudents: initialStudentsB }); return ( <> <Student {...studentStateA} /> <Student {...studentStateB} /> </> ); } export default App;
Student.jsx
import { useState } from "react"; export default function Student({ addStudent, students } = {}) { console.log("Student component rendered!"); const [newLabel, setNewLabel] = useState(""); const handleInputChange = (event) => { setNewLabel(event.target.value); }; const handleSubmit = (event) => { addStudent?.(newLabel); setNewLabel(""); event.preventDefault(); }; return ( <> <form onSubmit={handleSubmit}> <input type="text" value={newLabel} onChange={handleInputChange} placeholder="Add a student" /> </form> {students && students.length > 0 && students.map((name, i) => <span key={i}>{name}, </span>)} </> ); }
useStudentState.jsx
import { useState } from 'react'; export default function useStudentState({ initialStudents } = {}) { console.log('custom hook'); const [students, setStudents] = useState(initialStudents || []); const addStudent = name => { setStudents([...students, name]); }; return { addStudent, students, }; }
问题分析与解决方案
问题根源
- App重渲染触发全量更新:任意子组件触发状态更新都会导致App重渲染,进而使两个
useStudentStateHook重复执行,所有Student组件也会收到新的props(哪怕对应状态未变化)。 addStudent函数不稳定:useStudentState每次执行都会生成新的addStudent函数,导致Student组件的props始终变化,即使students数组未更新也会触发重渲染。Student未做纯组件优化:没有阻止相同props下的不必要重渲染。
优化步骤
1. 用useCallback缓存addStudent函数
在自定义Hook中,通过useCallback包裹addStudent,并改用函数式状态更新,确保函数仅在必要时重新生成:
// useStudentState.jsx import { useState, useCallback } from 'react'; export default function useStudentState({ initialStudents } = {}) { console.log('custom hook'); const [students, setStudents] = useState(initialStudents || []); const addStudent = useCallback(name => { // 函数式更新避免依赖外部students变量 setStudents(prevStudents => [...prevStudents, name]); }, []); return { addStudent, students, }; }
2. 用React.memo包裹Student组件
将Student转换为纯组件,仅在props真正变化时才重渲染:
// Student.jsx import { useState, memo } from "react"; const Student = ({ addStudent, students } = {}) => { console.log("Student component rendered!"); const [newLabel, setNewLabel] = useState(""); const handleInputChange = (event) => { setNewLabel(event.target.value); }; const handleSubmit = (event) => { addStudent?.(newLabel); setNewLabel(""); event.preventDefault(); }; return ( <> <form onSubmit={handleSubmit}> <input type="text" value={newLabel} onChange={handleInputChange} placeholder="Add a student" /> </form> {students && students.length > 0 && students.map((name, i) => <span key={i}>{name}, </span>)} </> ); }; export default memo(Student);
3. 稳定useStudentState的参数传递
App组件每次渲染都会创建新的参数对象,导致useStudentState重复执行非必要逻辑。可以直接传递数组,或用useMemo缓存参数对象:
// App.jsx import Student from "./Student"; import useStudentState from "./useStudentState"; const initialStudentsA = ["foo", "bar"]; const initialStudentsB = ["bat", "baz"]; function App() { // 直接传递数组,避免每次创建新对象 const studentStateA = useStudentState({ initialStudents: initialStudentsA }); const studentStateB = useStudentState({ initialStudents: initialStudentsB }); return ( <> <Student {...studentStateA} /> <Student {...studentStateB} /> </> ); } export default App;
优化效果
完成修改后,操作第一个Student组件时:
- 仅对应的
useStudentStateHook会执行状态更新逻辑(App重渲染时Hook仍会按顺序调用,但useState会直接读取缓存状态,无额外性能开销); - 第二个
Student组件因props未变化,不会触发不必要的重渲染。
内容的提问来源于stack exchange,提问作者newbie
相关产品推荐
相关产品推荐

