我遇到了一个性能优化问题,我认为可以以某种方式解决该问题,但是我不确定如何解决。
假设我有一个想要编辑的对象集合。父组件包含所有对象,并使用编辑器组件呈现列表,该编辑器组件显示值并允许修改对象。
一个简单的例子是这样:
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
回调函数,所以我一直无法找到一种方法来使用钩子。
答案 0 :(得分:1)
您必须使用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"/>
答案 1 :(得分:0)
好的,因此,debounce
如果包裹在useCallback
中似乎可以使用
不知道为什么在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