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

Material UI TextField在Formik中引发Wrapper组件过度渲染如何解决?

问题:Formik表单中Wrapper组件过度渲染导致输入卡顿

我在React项目中使用Formik,包含以下组件:

Wrapper组件

const Wrapper = ({ children, id }) => {
  console.log("re-render ", id);
  return <div>{children}</div>;
};

Form组件

<Formik
  initialValues={{
    title: "",
    description: "",
  }}
  validationSchema={Yup.object().shape({
    title: Yup.string().required("Title is required"),
    description: Yup.string().required("Description"),
  })}
  onSubmit={(values, { setSubmitting }) => {
    // ...submit stuff
  }}
>
  {({ handleChange, handleSubmit, isSubmitting, values }) => (
    <form onSubmit={handleSubmit} encType="multipart/form-data">
      <Wrapper id="title">
        <TextField
          variant="outlined"
          fullWidth
          label="title"
          name="title"
          margin="dense"
          value={values.title}
          onChange={handleChange}
        />
      </Wrapper>
      <Wrapper id="description">
        <TextField
          variant="outlined"
          fullWidth
          label="description"
          name="description"
          margin="dense"
          value={values.description}
          onChange={handleChange}
        />
      </Wrapper>
      <Button
        type="submit"
      >
        Submit
      </Button>
    </form>
  )}
</Formik>

问题:每当我在TextField中输入内容时,Wrapper组件会多次渲染,甚至未包含当前输入TextField的其他Wrapper组件也会出现过度渲染。实际项目中Wrapper组件更复杂,导致输入时出现卡顿,该如何解决?


原因分析

每次输入时Formik的values对象会更新,触发Formik内部的重新渲染,而表单结构直接定义在Formik的渲染回调中,这个回调每次都会重新执行,导致所有Wrapper组件(包括未关联当前输入的)都跟着重新渲染。另外,handleChange这类Formik提供的方法每次渲染都会生成新的引用,也会间接触发子组件的重新渲染。

解决方案

1. 使用Formik的Field组件精准控制渲染

Formik的Field组件内部做了优化,只会在对应字段变化时重新渲染,能缩小组件的渲染范围:

// 封装可复用的TextField组件
const FormTextField = ({ field, form, ...props }) => (
  <TextField
    variant="outlined"
    fullWidth
    margin="dense"
    {...field}
    {...props}
  />
);

// 改写Form组件
<Formik
  initialValues={{ title: "", description: "" }}
  validationSchema={Yup.object().shape({
    title: Yup.string().required("Title is required"),
    description: Yup.string().required("Description"),
  })}
  onSubmit={(values, { setSubmitting }) => {
    // ...submit stuff
  }}
>
  {({ handleSubmit, isSubmitting }) => (
    <form onSubmit={handleSubmit} encType="multipart/form-data">
      <Wrapper id="title">
        <Field name="title" component={FormTextField} label="title" />
      </Wrapper>
      <Wrapper id="description">
        <Field name="description" component={FormTextField} label="description" />
      </Wrapper>
      <Button type="submit">Submit</Button>
    </form>
  )}
</Formik>

这样只有当前输入的字段对应的Field和Wrapper会重新渲染,其他字段的组件不会触发渲染。

2. 用React.memo包裹Wrapper组件

给Wrapper组件添加React.memo,让它只在props变化时才重新渲染:

import { memo } from 'react';

const Wrapper = memo(({ children, id }) => {
  console.log("re-render ", id);
  return <div>{children}</div>;
});

注意:如果children是每次渲染都重新生成的元素,memo可能失效,需要结合其他优化方法配合使用。

3. 稳定handleChange等方法的引用

使用useFormikContext钩子在子组件中直接获取Formik方法,避免每次渲染生成新的函数引用:

// 在TextField组件内部获取Formik上下文
const FormTextField = ({ name, label }) => {
  const { handleChange, values } = useFormikContext();
  return (
    <TextField
      variant="outlined"
      fullWidth
      label={label}
      name={name}
      margin="dense"
      value={values[name]}
      onChange={handleChange}
    />
  );
};

// 表单中使用封装后的组件
<Wrapper id="title">
  <FormTextField name="title" label="title" />
</Wrapper>

这样handleChange的引用不会每次渲染都变化,配合React.memo的Wrapper就能有效减少不必要的渲染。

4. 拆分独立字段组件

把每个字段的Wrapper和TextField拆成独立的子组件,让每个组件只依赖自己需要的Formik状态:

const TitleField = () => {
  const { values, handleChange } = useFormikContext();
  return (
    <Wrapper id="title">
      <TextField
        variant="outlined"
        fullWidth
        label="title"
        name="title"
        margin="dense"
        value={values.title}
        onChange={handleChange}
      />
    </Wrapper>
  );
};

const DescriptionField = () => {
  const { values, handleChange } = useFormikContext();
  return (
    <Wrapper id="description">
      <TextField
        variant="outlined"
        fullWidth
        label="description"
        name="description"
        margin="dense"
        value={values.description}
        onChange={handleChange}
      />
    </Wrapper>
  );
};

// 改写Form组件
<Formik
  // ...其他配置
>
  {({ handleSubmit, isSubmitting }) => (
    <form onSubmit={handleSubmit} encType="multipart/form-data">
      <TitleField />
      <DescriptionField />
      <Button type="submit">Submit</Button>
    </form>
  )}
</Formik>

每个独立字段组件只会在自己对应的values字段变化时重新渲染,不会受其他字段影响。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 08:30:46