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

React Native中Formik多字段表单性能卡顿,如何避免不必要重渲染?

Formik多字段表单性能优化问题

我在React Native中使用Formik时遇到严重性能问题:4个字段时轻微卡顿尚可接受,扩展到12个字段后卡顿明显,点击Checkbox需要3-4秒才响应。排查后发现每次操作都会触发所有字段重渲染。尝试过用FastField优化,但它在React Native里无法正常工作。请问如何优化性能,避免未修改字段的不必要重渲染?

我的每个字段组件包含两部分:

  • 控制输入框禁用状态的Checkbox
  • 可通过键盘或按钮输入数值的输入框

代码示例

<Formik innerRef={formRef} initialValues={updatedValues} validationSchema={validationSchema} onSubmit={() => {}}>
      <Box>
        <CheckboxWithInputControls
          checkboxFieldName={'addedPipeline'}
          inputFieldName={'pipeline'}
          label={t('additionals.pipeline')}
          step={amount}
        />

        <Divider />

        <CheckboxWithInputControls
          checkboxFieldName={'addedHose'}
          inputFieldName={'hose'}
          label={t('additionals.hose')}
          step={amount}
        />

        .....
        <FormChangesListener setIsValid={setIsValid} setValues={setValues} />
      </Box>
    </Formik>


export const CheckboxWithInputControls = ({
  checkboxFieldName,
  inputFieldName,
  label,
  step,
  inputRightElement
}: CheckboxWithInputControlsProps) => {
  const [{ value: isChecked }, _meta, { setValue }] = useField(checkboxFieldName)

  return (
    <Box mb="4">
      <Box my="2" p="2" bg={isChecked ? 'blue.100' : 'white'} borderRadius="md">
        <Checkbox
          value={checkboxFieldName}
          isChecked={isChecked}
          onChange={() => setValue(!isChecked, false)}
        >
          {label}
        </Checkbox>
      </Box>
      <Box pl="10">
        <NumberInputWithControls
          controlName={inputFieldName}
          disabled={!isChecked}
          step={step}
          inputRightElement={inputRightElement}
        />
      </Box>
    </Box>
  )
}

const FormChangesListener =({ setIsValid, setValues }:AdditionsFormProps) => {
  const { values, isValid } = useFormikContext()
  useEffect(() => {
    setIsValid(isValid)
    setValues(values as FormProps)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [values, isValid])
  return null
}

Why-do-you-render检测结果

GROUP    CheckboxWithInputControls
 LOG    {"CheckboxWithInputControls": [Function CheckboxWithInputControls]} Re-rendered because of hook changes:
 GROUP      [hook useContext result]
 LOG      different objects that are equal by value.

临时解决方案(不满意)

放弃在CheckboxWithInputControls中使用useField,改为通过props传递values等数据,此时仅修改的字段会重渲染,性能恢复正常。代码示例:

({ values, setFieldValue, touched, errors }) => (
        <Box>
          <CheckboxWithInputControls
            checkboxFieldName={'addedPipeline'}
            inputFieldName={'pipeline'}
            label={t('additionals.pipeline')}
            step={amount}
            value={values.addedPipeline}
            subValue={values.pipeline}
            setFieldValue={setFieldValue}
            touched={touched.pipeline}
            error={errors.pipeline}
          />
    )})

优化方案

1. 用React.memo包裹字段组件

将CheckboxWithInputControls和NumberInputWithControls用React.memo包裹,避免父组件重渲染时触发无意义的子组件更新。注意确保组件接收的props是可浅比较的(比如避免传递匿名函数、每次渲染生成新对象的props)。

修改后的CheckboxWithInputControls示例:

export const CheckboxWithInputControls = React.memo(({
  checkboxFieldName,
  inputFieldName,
  label,
  step,
  inputRightElement,
  value,
  subValue,
  setFieldValue,
  touched,
  error
}: CheckboxWithInputControlsProps) => {
  // 原有组件逻辑保持不变
})

2. 精准订阅Formik上下文,替代useField

useField会订阅整个Formik上下文的变化,导致全表单重渲染。改用useFormikContext并仅提取当前组件需要的字段值,结合useCallback缓存事件处理函数:

export const CheckboxWithInputControls = React.memo(({
  checkboxFieldName,
  inputFieldName,
  label,
  step,
  inputRightElement
}: CheckboxWithInputControlsProps) => {
  const { values, setFieldValue, touched, errors } = useFormikContext()
  
  // 只提取当前组件依赖的字段,避免订阅整个values对象
  const isChecked = values[checkboxFieldName]
  const inputValue = values[inputFieldName]
  const inputTouched = touched[inputFieldName]
  const inputError = errors[inputFieldName]

  // 缓存Checkbox的change事件,避免每次渲染生成新函数
  const handleCheckboxToggle = useCallback(() => {
    setFieldValue(checkboxFieldName, !isChecked, false)
  }, [checkboxFieldName, isChecked, setFieldValue])

  return (
    <Box mb="4">
      <Box my="2" p="2" bg={isChecked ? 'blue.100' : 'white'} borderRadius="md">
        <Checkbox
          value={checkboxFieldName}
          isChecked={isChecked}
          onChange={handleCheckboxToggle}
        >
          {label}
        </Checkbox>
      </Box>
      <Box pl="10">
        <NumberInputWithControls
          controlName={inputFieldName}
          disabled={!isChecked}
          step={step}
          inputRightElement={inputRightElement}
          value={inputValue}
          touched={inputTouched}
          error={inputError}
          setFieldValue={setFieldValue}
        />
      </Box>
    </Box>
  )
})

3. 优化FormChangesListener的订阅逻辑

当前组件订阅了整个values对象,任何字段变化都会触发外部状态更新。可以通过useMemo对values进行深比较缓存,避免浅比较导致的不必要更新:

const FormChangesListener = ({ setIsValid, setValues }: AdditionsFormProps) => {
  const { values, isValid } = useFormikContext()
  
  // 对values进行深比较缓存,避免嵌套对象变化时的频繁更新
  const memoizedValues = useMemo(() => values, [JSON.stringify(values)])

  useEffect(() => {
    setIsValid(isValid)
    setValues(memoizedValues as FormProps)
  }, [memoizedValues, isValid, setIsValid, setValues])
  
  return null
}

4. 减少不必要的表单验证触发

在调用setFieldValue时保持第三个参数为false(你已经在使用),跳过实时验证。如果业务允许,可以只在提交前执行完整验证,进一步减少重渲染次数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 14:50:16