#reactjs #react-hooks
#reactjs #реагировать-перехваты
Вопрос:
У меня есть компонент React, который отображает умеренно большой список входных данных (более 100 элементов). На моем компьютере все нормально, но на моем телефоне наблюдается заметная задержка ввода. 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);
Комментарии:
1. Похоже, вы вызываете свою функцию вместо ее передачи.
onChange={handleChange(obj)}
должно бытьonChange={() => handleChange(obj)}
2. Также отсутствует ключ для
<MyInput />
3. @DCTID handleChange выполняет функцию события. Таким образом, при вызове будет возвращена функция для обработки функции события.
Ответ №1:
Проблема связана с:
function handleChange(obj) {
return (event) => setObjects(objs.map((o) => {
if (o === obj) return {...obj, value: event.target.value}
return o;
}));
}
При этом вы обновите objs
массив. Очевидно, что это нормально, но React не знает, что изменилось, поэтому запускает рендеринг для всех дочерних элементов.
Если ваш функциональный компонент выдает тот же результат с использованием тех же реквизитов, вы можете обернуть его в вызов 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 amp;amp; prevProps.obj.value === nextProps.obj.value);
Однако реагируйте.Memo выполняет только поверхностное сравнение при попытке выяснить, следует ли его отображать, поэтому мы можем передать пользовательскую функцию сравнения в качестве второго аргумента.
(prevProps, nextProps) => prevProps.obj.label === nextProps.obj.label amp;amp; 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, показывающий, было ли что-то повторно отправлено.
Есть ли более эффективный способ подойти к этому?
Вы сохраняете все свои значения в массиве, что означает, что вы не знаете, какой элемент необходимо обновить, без итерации по всему массиву, сравнивая, соответствует ли объект.
Если вы начали с объекта:
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}}));
}
}
https://codepen.io/anon/pen/LvBPEB?editors=0011 в качестве примера приведу вот что.
Комментарии:
1. Я только что закончил свои тесты, чтобы опубликовать почти тот же ответ. Хороший ответ!
2. Этот ответ потрясающий. Заметка, наконец, имеет смысл. Я все еще не могу заставить его работать. Функция сравнения не вызывается. Я попытаюсь получить CodePen, но следует отметить, что я все еще использую массив с функцией обновления, подобной этой:
setObjects(prev => prev.fill({...prev[i], value},i,i 1))
которая заменяет элемент с определенным индексом на себя после получения нового значения.3. Вот версия, которая не работает. codepen.io/anon/pen/YMmVaB?editors=0011 Настоящая фишка в том, что он работает в react 16.7.0-alpha, но я обновился до 16.8 и без кубиков. Означает ли это, что это ошибка в react?
4. updateObjInArr изменяет
prev
состояние. Таким образом, React считает, что состояние никогда не менялось, поскольку вы случайно обновляете предыдущее состояние в соответствии с новым состоянием. Измените его наarr.slice().fill(......
, и он будет работать