如何使用 React.memo 优化 React 组件,并在回调更改父级中的状态时使用 Callback



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

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

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

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这样当我编辑一个对象的名称时,其他对象不会重新渲染。但是,由于每次在ObjectEditor的父组件中都会重新创建onChange处理程序,因此所有对象始终呈现。

我无法通过在处理程序上使用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回调函数,我一直无法找到一种方法来使其与钩子一起工作。

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

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

你将能够在更新当前状态值 (prevState( 时对其进行访问。

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

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

以下代码段中的示例

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"/>

好的,所以如果它被包裹在useCallback中,似乎debounce工作 不知道为什么似乎没有必要newObject作为updateParent函数中的依赖项传递。

因此,要完成这项工作,我必须进行以下更改:

首先,useCallback父对象并将其更改为采用整个对象,而不是负责更新键。 然后更新ObjectEditor以具有自己的状态并处理对密钥的更改。 并将将更新父级的onChange处理程序包装在debounce

import React, { useState, useEffect } from 'react'
import debounce from 'lodash.debounce'
const Input = props => {
const { value, onChange } = props
handleChange = e => {
onChange && onChange(e.target.value)
}
return (
<input value={value} onChange={handleChange} />
)
}
const ObjectEditor = React.memo(props => {
const { initialObject, onChange } = props
const [object, setObject] = useState(initialObject)
const updateParent = useCallback(debounce((newObject) => {
onChange(newObject)
}, 500), [onChange])
// synchronize the object if it's changed in the parent
useEffect(() => {
setObject(initialObject)
}, [initialObject])
const handleChange = key => value => {
const newObject = {
...object,
[key]: value
}
setObject(newObject)
updateParent(newObject)
}
return (
<li>
<Input value={object.name} onChange={handleChange('name')} />
</li>
)
})
const Objects = props => {
const { initialObjects } = props
const [objects, setObjects] = useState(initialObjects)
const handleObjectChange = useCallback(newObj => {
const newObjects = objects.map(obj => {
if (newObj.id === id) {
return newObj
}
return obj
})
setObjects(newObjects)
}, [objects])
return (
<ul>
{
objects.map(obj => (
<ObjectEditor key={obj.id} initialObject={obj} onChange={handleObjectChange} />
))
}
</ul>
)
}
export default Objects

最新更新