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

如何避免自定义Input onChange时重渲染同时通知父组件?

解决自定义输入组件不必要重渲染的问题

我希望实现一个自定义输入组件,要求输入值变化时组件自身不触发重渲染,但能通知父组件更新状态。目前的代码中,尽管父组件通过useCallback传递了回调函数,但每次输入时子组件依然会重渲染;移除该回调prop后则不会出现这个问题,请问解决方案是什么?

问题原因

  1. 父组件调用setValue更新状态时会触发自身重渲染,React默认规则下,父组件重渲染会导致所有子组件跟着重渲染,无论props是否变化。
  2. 虽然用useCallback缓存了changeFunction的引用,但子组件没有做任何阻止不必要重渲染的优化,所以父组件重渲染时子组件仍会重新执行。
  3. 子组件中的useEffect未设置依赖数组,每次渲染都会执行,这也直观证明了组件一直在重复渲染。

解决方案

1. 用React.memo包裹子组件

React.memo是高阶组件,会对组件的props做浅比较,只有当props发生实际变化时才会触发组件重渲染。因为父组件通过useCallback保证了changeFunction的引用稳定,所以子组件的props不会变化,也就不会触发不必要的重渲染。

2. 优化子组件的useEffect

给useEffect添加空依赖数组,让它仅在组件挂载时执行一次,避免每次渲染都打印日志,同时符合性能优化的原则。

3. 可选:用useCallback包裹子组件的change函数

将子组件内部的change函数用useCallback包裹,可以避免每次渲染时创建新的函数实例。虽然在React.memo的加持下这一步不是必须的,但能进一步优化组件性能。

修改后的代码

TypeScript版子组件

import { useRef, useEffect, ChangeEvent, useCallback, memo } from "react";

type Props = {
  changeFunction?: (e: ChangeEvent<HTMLInputElement>) => void;
};

const TestInput = memo(({ changeFunction }: Props) => {
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    console.log("Input mount");
  }, []); // 空依赖数组,仅挂载时执行

  const change = useCallback((e: ChangeEvent<HTMLInputElement>) => {
    console.log("Input change", e.target.value);
    if (inputRef.current) inputRef.current.value = e.target.value;
    if (changeFunction) changeFunction(e);
  }, [changeFunction]); // 依赖changeFunction,保证函数引用稳定

  return (
    <input
      ref={inputRef}
      type="search"
      id="test"
      name="test"
      autoComplete="off"
      onChange={change}
    />
  );
});

export default TestInput;

父组件(无需修改)

import { useCallback, useState, ChangeEvent } from "react";
import TestInput from "./Test";

function TestParent() {
  const [value, setValue] = useState("");
  const change = useCallback(
    (e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value),
    []
  );

  return (
    <div>
      <p>Value: {value}</p>
      <TestInput changeFunction={change} />
    </div>
  );
}

export default TestParent;

无TypeScript版完整示例

const { useState, useEffect, useCallback, useRef, memo } = React;

const TestInput = memo(({ changeFunction }) => {
  const inputRef = useRef(null);

  useEffect(() => {
    console.log("Input mount");
  }, []);

  const change = useCallback((e) => {
    console.log("Input change", e.target.value);
    if (inputRef.current) inputRef.current.value = e.target.value;
    if (changeFunction) changeFunction(e);
  }, [changeFunction]);

  return (
    <input
      ref={inputRef}
      type="search"
      id="test"
      name="test"
      autoComplete="off"
      onChange={change}
    />
  );
});

function TestParent() {
  const [value, setValue] = useState("");
  const change = useCallback(
    (e) => setValue(e.target.value),
    []
  );

  return (
    <div>
      <p>Value: {value}</p>
      <TestInput changeFunction={change} />
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<TestParent />);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.development.js"></script>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 13:19:57