当回调改变父级中的状态时,如何使用 React.memo 和 useCallback 优化 React 组件

Geo*_*Hug 6 reactjs react-hooks

我遇到了一个性能优化问题,我觉得可以以某种方式解决该问题,但我不确定如何解决。

假设我有一组想要编辑的对象。父组件包含所有对象,并使用显示值并允许修改对象的编辑器组件呈现列表。

一个简化的例子是这样的:

import React, { useState } from 'react'

const Input = props => {

    const { value, onChange } = props

    handleChange = e => {
        onChange && onChange(e.target.value)
    }

    return (
        <input value={value} onChange={handleChange} />
    )
}

const ObjectEditor = props => {
    const { object, onChange } = props

    return (
        <li>
            <Input value={object.name} onChange={onChange('name')} />
        </li>
    )
}

const Objects = props => {

    const { initialObjects } = props

    const [objects, setObjects] = useState(initialObjects)

    const handleObjectChange = id => key => value => {
        const newObjects = objects.map(obj => {
            if (obj.id === id) {
                return {
                    ...obj,
                    [key]: value
                }
            }
            return obj
        })
        setObjects(newObjects)
    }

    return (
        <ul>
            {
                objects.map(obj => (
                    <ObjectEditor key={obj.id} object={obj} onChange={handleObjectChange(obj.id)} />
                ))
            }
        </ul>
    )
}

export default Objects
Run Code Online (Sandbox Code Playgroud)

所以我可以使用React.memo这样当我编辑一个对象的名称时,其他对象不会重新渲染。但是,由于onChange每次都在 的父组件中重新创建处理程序ObjectEditor,因此所有对象始终都会呈现。

我无法通过useCallback在我的处理程序上使用来解决它,因为我必须将它objects作为依赖项传递,每次对象的名称更改时都会重新创建它。

在我看来,所有没有更改的对象都没有必要因为处理程序更改而重新渲染。应该有办法改善这一点。

有任何想法吗 ?

我在 React Sortly 存储库中看到,它们debounce与每个对象编辑器结合使用来改变它自己的状态。这仅允许编辑的组件在有人输入时更改和重新渲染,如果在给定的延迟内没有其他更改事件出现,则仅更新一次父组件。

handleChangeName = (e) => {
    this.setState({ name: e.target.value }, () => this.change());
  }

  change = debounce(() => {
    const { index, onChange } = this.props;
    const { name } = this.state;
    onChange(index, { name });
  }, 300);
Run Code Online (Sandbox Code Playgroud)

这是我现在能看到的最好的解决方案,但由于他们使用setState回调函数,我一直无法找到一种方法来使用钩子进行这项工作。

cbd*_*per 7

您必须使用以下函数形式setState

setState((prevState) => {
  // ACCESS prevState
  return someNewState;
});
Run Code Online (Sandbox Code Playgroud)

您将能够在更新当前状态值 (prevState) 时访问它。

然后,您可以使用useCallback钩子而无需将状态对象添加到依赖项数组。该setState函数不需要在依赖数组中,因为它不会在渲染中改变。

因此,您将能够React.memo在孩子上使用,并且只有那些收到不同道具(浅比较)的才会重新渲染。

以下代码段中的示例

setState((prevState) => {
  // ACCESS prevState
  return someNewState;
});
Run Code Online (Sandbox Code Playgroud)
const InputField = React.memo((props) => {
  console.log('Rendering InputField '+ props.index + '...');
  return(
    <div>
      <input 
        type='text' 
        value={props.value}
        onChange={()=>
          props.handleChange(event.target.value,props.index)
        }
      />  
    </div>
  );
});

function App() {
  
  console.log('Rendering App...');
  
  const [inputValues,setInputValues] = React.useState(
    ['0','1','2']
  );
  
  const handleChange = React.useCallback((newValue,index)=>{
    setInputValues((prevState)=>{
      const aux = Array.from(prevState);
      aux[index] = newValue;
      return aux;
    });
  },[]);
  
  const inputItems = inputValues.map((item,index) => 
    <InputField 
      value={item}
      index={index}
      handleChange={handleChange}
    />
  );

  return(
    <div>
      {inputItems}
    </div>
  );
}



ReactDOM.render(<App/>, document.getElementById('root'));
Run Code Online (Sandbox Code Playgroud)