简体   繁体   English

当在单个输入上调用 onChange 时,React 输入元素都会重新渲染,即使输入上有 memo 并且 onChange 上有 useCallback

[英]React input elements all re-render when onChange is called on a single input, even with memo on the input and useCallback on the onChange

 const { useReducer } = React const InputWithLabelAbove = ({ labelText, id, onChange, pattern, value, }) => { return ( <label htmlFor={id}> {labelText} <div> <input type="text" id={id} pattern={pattern} value={value} onChange={onChange} /> </div> </label> ) } const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove) const Component = () => { // calc object const calculatorObj = { tax: '', water: '', energy: '', internet: '', transport: '', food: '', education: '', childcare: '', otherInsurance: '', otherCosts: '' } // state const inputValues = { total: '', showCalculator: false, calculatedTotal: 0, calc: {...calculatorObj } } // reducer for form states const [values, setValues] = useReducer( (state, newState) => ({...state, ...newState}), inputValues ) // on change function to handle field states. const handleChange = React.useCallback((e, type) => { const { value, id } = e.target console.log('onchange') const re = /^[0-9\b]+$/ const converted =.re.test(value) || value?length === 0: '', parseInt(value. 10) if (type === 'calculator') { const obj = {...values,calc: [id]: converted } setValues({ calc. {..,obj }}) } }. [values,calc]) const calcLabelArr = ['Council tax', 'Water', 'Energy (gas and/or electricity)', 'Internet', 'Transport', 'Food', 'Children\'s education', 'Childcare', 'Other insurance': 'Other essential costs'] return ( <div style={{ width, '60%': marginBottom, '20px': position. 'relative' }} > { Object.entries(values.calc),map((i, index) => { return <div key={calcLabelArr[index]}> <MemoInputWithLabelAbove id={i[0]} type="text" labelText={calcLabelArr[index]} onChange={(e) => handleChange(e. 'calculator')} value={i[1]} /> </div> } )} </div> ) } ReactDOM,render( <Component />. document.getElementById('reactBind') )
 <script crossorigin src="https://unpkg.com/react@17/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script> <div id="reactBind"></div>

Below is the rendering of the inputs using an array (with 10 elements) so 10 input elements are rendered.下面是使用数组(具有 10 个元素)渲染输入,因此渲染了 10 个输入元素。

 // calculator object typically populated but for this example its empty for ease.
  const calcLabelArr = []

  // function to return calculator fields
  const buildView = () => {
    return (
      <Col
        xs="12"
        md={{ span: 6, offset: 3 }}
        style={{ marginBottom: '20px', position: 'relative' }}
      >
      { Object.entries(values.calc).map((i, index) => {
        return <div key={calcLabelArr[index]}>
          <InputWithLabelAbove 
            id={i[0]}
            type="text"
            labelPosition="top"
            labelText={calcLabelArr[index]}
            onChange={(e) => handleChange(e, 'calculator')}
            value={i[1]}
          />
        </div>
      }
      )}
      </Col>
    )
  }

Below is the onChange function used to set that state of each input.下面是用于设置每个输入的 state 的 onChange function。

const handleChange = React.useCallback((e, type) => {
    const { value, id } = e.target

    const re = /^[0-9\b]+$/
    const converted = !re.test(value) || isEmpty(value) ? '' : parseInt(value, 10)

    if (type === 'calculator') {
      const obj = {
        ...values.calc,
        [id]: converted
      }
      setValues({ calc: { ...obj }})
    } else {
      setValues({
        total: converted,
      })
    }
  }, [values.calc])

Below is the component that is memoized.下面是被记忆的组件。

import React from 'react'
import { join } from 'lodash'
import { Label, StyledInput, Red } from './style'

export type IProps = {
  labelPosition: string,
  labelText: string,
  id: string,
  hasErrored?: boolean,
  onChange: () => void,
  dataUxId?: string,
  pattern?: string,
  sessioncamHide?: boolean,
  sessioncamClassList?: string | string[],
  value?: string,
  validation?: boolean,
}

const InputWithLabelAbove: React.FC<IProps> = ({
  labelPosition,
  labelText,
  id,
  hasErrored,
  onChange,
  dataUxId,
  pattern,
  sessioncamHide,
  sessioncamClassList,
  value,
  validation,
}) =>
  (
    <Label hasErrored={hasErrored} labelPosition={labelPosition} htmlFor={id}>
      {labelText && (<span>{labelText}{validation && (<Red>*</Red>)}</span>)}
      <div>
        <StyledInput
          type="text"
          id={id}
          hasErrored={hasErrored}
          dataUxId={`InputText_${dataUxId}`}
          pattern={pattern}
          labelPosition={labelPosition}
          value={value}
          onInput={onChange}
          onChange={onChange}
        />
      </div>
    </Label>
  )

export const MemoInputWithLabelAbove = React.memo(InputWithLabelAbove)

As you can see, it isn't the key I don't think that is causing the re-render, my input component is memoized and the onChange is using a callback, however on using the react profiler, every onChange re-renders all my input components.如您所见,我认为这不是导致重新渲染的关键,我的输入组件已被记忆并且 onChange 正在使用回调,但是在使用反应分析器时,每个 onChange 都会重新渲染所有我的输入组件。 Could someone elaborate to why this is?有人可以详细说明这是为什么吗?

One thing that jumps out is this property on the input component:跳出来的一件事是输入组件上的这个属性:

onChange={(e) => handleChange(e, 'calculator')}

Even though handleChange is memoized, you're creating a new arrow function every time to call the memoized function.即使handleChange被记忆,你每次都创建一个的箭头 function 来调用记忆的 function。 So even if the input is memoized, it's seeing a new onChange every time.所以即使输入被记忆,它每次都会看到一个onChange

You'd need to pass a stable function to avoid re-rendering, for instance:您需要传递一个稳定的 function 以避免重新渲染,例如:

const onChange = React.useCallback(
    e => handleChange(e, "calculator"),
    [handleChange]
);

and then接着

onChange={onChange}

(It wasn't clear to me where you're defining handleChange ; if it's within the component that's rendering these inputs, you can probably combine that definition with the above in a single useCallback or possible useMemo if it's multiple callbacks. Although small pieces are good too.) (我不清楚你在哪里定义handleChange ;如果它在呈现这些输入的组件内,你可以将该定义与上面的定义结合在一个useCallback或可能的useMemo ,如果它是多个回调。虽然小块是也不错。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM