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

标签 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

所以我可以使用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);

这是我现在能看到的最好的解决方案,但由于他们使用 setState 回调函数,我无法找到一种方法来使其与钩子(Hook)一起工作。

最佳答案

您必须使用setState的函数形式:

setState((prevState) => {
  // ACCESS prevState
  return someNewState;
});

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

这样您就可以使用 useCallback Hook ,而无需将状态对象添加到依赖项数组中。 setState 函数不需要位于依赖项数组中,因为它不会在渲染过程中发生更改。

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

下面片段中的示例

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'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>

关于reactjs - 当回调在父级中更改状态时,如何使用 React.memo 和 useCallback 优化 React 组件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57822144/

相关文章:

Reactjs 分离 UI 和业务逻辑

javascript - 我为每个 react 的 child 提供了一个 key ,但我仍然收到错误?

javascript - react 路由器 : access history in rendered Route component

javascript - 在 props 渲染上取消 componentDidMount 中的 setInterval

reactjs - 具有大写函数名称的组件在 React Hooks 中被渲染多次

javascript - ReactJs 在按钮单击时更改文本问题

reactjs - 我可以为自定义 Hook 提供 react-hooks/exhaustive-deps 吗?

javascript - react ;如何收集用户状态输入并在提交时附加到新组件

javascript - 在 React useEffect Hook 中引用过时的状态

node.js - 类型错误 : stream is undefined