React: оптимизация (повышение производительности) формы с большим кол-вом Checkbox элементов
В продолжении темы React + Ant design: задержки при отображении и взаимодействии с большим (200+) кол-вом Checkbox
Поясните пожалуйста как в данном примере организовать использование хуков React типа useMemo
, useCallback
и метода memo
для снижения кол-ва отрисовки компонентов.
Задача:
- есть компонент (
ComponentListModal
), который работает с выбранными элементами - компонент (
ComponentListByAlphabet
), вызываемый из 1), который отрисовывает список сгруппированных checkbox - компонент (
ComponentListLetterBlock
), вызываемый из 2), который отрисовывает группу checkbox - компонент (
ComponentListLetterItem
), вызываемый из 3), который отрисовывает отдельный checkbox
Проблема: при изменении состояния отдельного чекбокса в 4) изменяется список выбранных элементов в 1) и происходит перерисовка всех!!! чекбоксов, хотя по идее надо бы перерисовать только один чекбокс.
Код (выкинул для экономии места непринципиальный для текущего вопроса функционал) выглядит примерно так:
// компонент: элемент группы списка
const ComponentListLetterItem = (props: IProps_ListLetterItem) => {
// отрисовать компонент
return (
<div className='lm-block-item'>
<Checkbox
checked = {props.checked}
onChange = {(e: CheckboxChangeEvent) => props.onCheck(props.item.id, e?.target?.checked)}
>
<div className="lm-item-name">{props.item.name}</div>
</Checkbox>
</div>
);
}
// компонент: группа списка
const ComponentListLetterBlock = (props: IProps_ListLetterBlock) => {
// отрисовать компонент
return (
<div className='lm-block'>
<h3 className="lm-block-letter">{props.group.id}</h3>
{
props.group.values.map((elem, index) => (
<ComponentListLetterItem
key = {index}
item = {elem}
checked = {props.selectedIDs.includes(elem.id)}
onCheck = {props.onCheck}
/>
))
}
</div>
);
}
// компонент: список, сгруппированный по алфавиту
const ComponentListByAlphabet = (props: IProps_ListAlphabet) => {
// группировка элементов по алфавиту
const groups = groupItemsByLetter(props.items);
// отрисовать компонент
return (
<div className='lm-panel'>
<div className='lm-list'>
{
groups.map((group, index) => (
<ComponentListLetterBlock
key = {index}
group = {group}
selectedIDs = {props.selectedIDs}
onCheck = {props.onCheckItem}
/>
))
}
</div>
</div>
);
}
// копмпонент: окно выбора элементов списка
const ComponentListModal = (props: IProps_ListModal) => {
// контроль состояний
const [selectedItems, setSelectedItems] = useState<string[]>([]); // список
// событие: выбран элемент
const handleCheckItem = (id: string, checked: boolean) => {
if (checked) {
// добавить идентификатор элемента в список выбранных элементов
setSelectedItems([...selectedItems.filter(elem => elem !== id), id]);
}
else {
// удалить идентификатор элемента из списка выбранных элементов
setSelectedItems(selectedItems.filter(elem => elem !== id));
}
}
// отрисовать компонент
return (
<div className="lm-content">
<ComponentListByAlphabet
items = {props.items}
selectedIDs = {selectedItems}
onCheckItem = {handleCheckItem}
/>
</div>
);
}
Подскажите как лучше всего было бы оптимизировать данный React + Ant Design + typescript код.
Вроде как просится memo
для ComponentListLetterItem
, но чтоб он сработал нужно завернуть в useCallback
функцию handleCheckItem
и вот тут недопонимание, потому что при таком решении в лоб в списке выбирается исключительно только 1 элемент, при выборе 2 элемента галка с первого снимается
Ответы (1 шт):
Поясните пожалуйста как в данном примере организовать использование хуков React типа useMemo, useCallback и метода memo для снижения кол-ва отрисовки компонентов.
Снижение количества рендеров, на моем примере, можно реализовать вот таким образом.
Я специально поставил console.log
в компонентах для демонстрации.
Сначала выведутся все группы и все чеки... Но потом будут ренделиться только компонент "выбраной" группы и компонент измененного чека.
Поскольку твой "пример" не рабочий и просто "набор" кусков... На нем показать что-то работающее не представляется возможным.
//
const Itm = React.memo(({obj, n, act}) => {
console.log(obj.id)
const fn = () => act(n, obj.id)
return <li>
<input type='checkbox' onChange={fn} checked={obj.v} />
</li>
})
//
const Grp = React.memo(({obj, act}) => {
console.log(obj.name)
return <li>
<p>{obj.name}</p>
<ol>
{obj?.arr.map(o => <Itm key={o.id} obj={o} n={obj.id} act={act} />)}
</ol>
</li>
})
//
const App = () => {
const {arr, act} = useData()
return <ul>
{arr.map(o => <Grp key={o.id} obj={o} act={act} />)}
</ul>
}
const domContainer = document.querySelector('#like_button_container');
const root = ReactDOM.createRoot(domContainer);
root.render(<App />);
//
function useData(){
const [arr, setArr] = React.useState([])
React.useEffect(_ => setArr(data(10, 30)), [])
const act = React.useCallback((n, m) => setArr(old => old.map(o => o.id === n
? {...o, arr: o.arr.map(o => o.id === m
? {...o, v: !o.v}
: o
)}
: o
)), [])
return {arr, act}
}
//
function data(n, m){
return Array.from({length: n}, (_, i) => {
return {
id: i,
name: 'Группа ' + (i + 1),
arr: Array.from({length: m}, (_, i) => {
return {
id: i,
v: false
}
})
}
})
}
ul,
ol {
list-style-type: none;
padding-left: 0;
}
ol {
display: flex;
}
<div id="like_button_container"></div>
<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>