You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免自定义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}, &nbsp;</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,
  };
}
问题分析与解决方案

问题根源

  1. App重渲染触发全量更新:任意子组件触发状态更新都会导致App重渲染,进而使两个useStudentState Hook重复执行,所有Student组件也会收到新的props(哪怕对应状态未变化)。
  2. addStudent函数不稳定:useStudentState每次执行都会生成新的addStudent函数,导致Student组件的props始终变化,即使students数组未更新也会触发重渲染。
  3. 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}, &nbsp;</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组件时:

  • 仅对应的useStudentState Hook会执行状态更新逻辑(App重渲染时Hook仍会按顺序调用,但useState会直接读取缓存状态,无额外性能开销);
  • 第二个Student组件因props未变化,不会触发不必要的重渲染。

内容的提问来源于stack exchange,提问作者newbie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 21:25:07