使用 useState 钩子有效地反应更新数组中的对象
React efficiently update object in array with useState hook
我有一个 React 组件,它呈现一个相当大的输入列表(100 多个项目)。它在我的电脑上呈现正常,但在我的 phone 上有明显的输入延迟。 React DevTools 显示整个父对象在每次按键时都会重新呈现。
有没有更有效的方法来解决这个问题?
https://codepen.io/anon/pen/YMvoyy?editors=0011
function MyInput({obj, onChange}) {
return (
<div>
<label>
{obj.label}
<input type="text" value={obj.value} onChange={onChange} />
</label>
</div>
);
}
// Passed in from a parent component
const startingObjects =
new Array(100).fill(null).map((_, i) => ({label: i, value: 'value'}));
function App() {
const [objs, setObjects] = React.useState(startingObjects);
function handleChange(obj) {
return (event) => setObjects(objs.map((o) => {
if (o === obj) return {...obj, value: event.target.value}
return o;
}));
}
return (
<div>
{objs.map((obj) => <MyInput obj={obj} onChange={handleChange(obj)} />)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
问题与:
function handleChange(obj) {
return (event) => setObjects(objs.map((o) => {
if (o === obj) return {...obj, value: event.target.value}
return o;
}));
}
在此,您将更新 objs
数组。这显然没问题,但 React 不知道发生了什么变化,因此触发了所有 Children 上的 Render。
如果您的函数组件在给定相同道具的情况下呈现相同的结果,您可以将其包装在对 React.memo 的调用中以提高性能。
https://reactjs.org/docs/react-api.html#reactmemo
const MyInput = React.memo(({obj, onChange}) => {
console.log(`Rerendered: ${obj.label}`);
return <div style={{display: 'flex'}}>
<label>{obj.label}</label>
<input type="text" value={obj.value} onChange={onChange} />
</div>;
}, (prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label && prevProps.obj.value === nextProps.obj.value);
但是,React.Memo 仅在尝试确定是否应呈现时进行浅比较,因此我们可以将自定义比较函数作为第二个参数传递。
(prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label && prevProps.obj.value === nextProps.obj.value);
基本上来说,如果 obj
属性上的标签和值与前一个 obj
属性上的属性相同,则不要重新渲染。
最后,setObjects
很像setState,也是异步的,不会立即反映和更新。因此,为了避免 objs
不正确并使用旧值的风险,您可以将其更改为回调,如下所示:
function handleChange(obj) {
return (event) => {
const value = event.target.value;
setObjects(prevObjs => (prevObjs.map((o) => {
if (o === obj) return {...obj, value }
return o;
})))
};
}
https://codepen.io/anon/pen/QPBLwy?editors=0011 拥有所有这些,以及 console.logs,显示是否重新渲染。
Is there a more efficient way to approach this?
您将所有值存储在一个数组中,这意味着您不知道需要更新哪个元素而不遍历整个数组,比较对象是否匹配。
如果您从对象开始:
const startingObjects =
new Array(100).fill(null).reduce((objects, _, index) => ({...objects, [index]: {value: 'value', label: index}}), {})
经过一些修改后,您的句柄函数将变为
function handleChange(obj, index) {
return (event) => {
const value = event.target.value;
setObjects(prevObjs => ({...prevObjs, [index]: {...obj, value}}));
}
}
我有一个 React 组件,它呈现一个相当大的输入列表(100 多个项目)。它在我的电脑上呈现正常,但在我的 phone 上有明显的输入延迟。 React DevTools 显示整个父对象在每次按键时都会重新呈现。
有没有更有效的方法来解决这个问题?
https://codepen.io/anon/pen/YMvoyy?editors=0011
function MyInput({obj, onChange}) {
return (
<div>
<label>
{obj.label}
<input type="text" value={obj.value} onChange={onChange} />
</label>
</div>
);
}
// Passed in from a parent component
const startingObjects =
new Array(100).fill(null).map((_, i) => ({label: i, value: 'value'}));
function App() {
const [objs, setObjects] = React.useState(startingObjects);
function handleChange(obj) {
return (event) => setObjects(objs.map((o) => {
if (o === obj) return {...obj, value: event.target.value}
return o;
}));
}
return (
<div>
{objs.map((obj) => <MyInput obj={obj} onChange={handleChange(obj)} />)}
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
问题与:
function handleChange(obj) {
return (event) => setObjects(objs.map((o) => {
if (o === obj) return {...obj, value: event.target.value}
return o;
}));
}
在此,您将更新 objs
数组。这显然没问题,但 React 不知道发生了什么变化,因此触发了所有 Children 上的 Render。
如果您的函数组件在给定相同道具的情况下呈现相同的结果,您可以将其包装在对 React.memo 的调用中以提高性能。
https://reactjs.org/docs/react-api.html#reactmemo
const MyInput = React.memo(({obj, onChange}) => {
console.log(`Rerendered: ${obj.label}`);
return <div style={{display: 'flex'}}>
<label>{obj.label}</label>
<input type="text" value={obj.value} onChange={onChange} />
</div>;
}, (prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label && prevProps.obj.value === nextProps.obj.value);
但是,React.Memo 仅在尝试确定是否应呈现时进行浅比较,因此我们可以将自定义比较函数作为第二个参数传递。
(prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label && prevProps.obj.value === nextProps.obj.value);
基本上来说,如果 obj
属性上的标签和值与前一个 obj
属性上的属性相同,则不要重新渲染。
最后,setObjects
很像setState,也是异步的,不会立即反映和更新。因此,为了避免 objs
不正确并使用旧值的风险,您可以将其更改为回调,如下所示:
function handleChange(obj) {
return (event) => {
const value = event.target.value;
setObjects(prevObjs => (prevObjs.map((o) => {
if (o === obj) return {...obj, value }
return o;
})))
};
}
https://codepen.io/anon/pen/QPBLwy?editors=0011 拥有所有这些,以及 console.logs,显示是否重新渲染。
Is there a more efficient way to approach this?
您将所有值存储在一个数组中,这意味着您不知道需要更新哪个元素而不遍历整个数组,比较对象是否匹配。
如果您从对象开始:
const startingObjects =
new Array(100).fill(null).reduce((objects, _, index) => ({...objects, [index]: {value: 'value', label: index}}), {})
经过一些修改后,您的句柄函数将变为
function handleChange(obj, index) {
return (event) => {
const value = event.target.value;
setObjects(prevObjs => ({...prevObjs, [index]: {...obj, value}}));
}
}