簡體   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>

下面是使用數組(具有 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>
    )
  }

下面是用於設置每個輸入的 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])

下面是被記憶的組件。

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)

如您所見,我認為這不是導致重新渲染的關鍵,我的輸入組件已被記憶並且 onChange 正在使用回調,但是在使用反應分析器時,每個 onChange 都會重新渲染所有我的輸入組件。 有人可以詳細說明這是為什么嗎?

跳出來的一件事是輸入組件上的這個屬性:

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

即使handleChange被記憶,你每次都創建一個的箭頭 function 來調用記憶的 function。 所以即使輸入被記憶,它每次都會看到一個onChange

您需要傳遞一個穩定的 function 以避免重新渲染,例如:

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

接着

onChange={onChange}

(我不清楚你在哪里定義handleChange ;如果它在呈現這些輸入的組件內,你可以將該定義與上面的定義結合在一個useCallback或可能的useMemo ,如果它是多個回調。雖然小塊是也不錯。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM