Единственное серьезное ограничение производительности в вашем коде заключается в том, что при расширении изменения создается новый clickHandler
обратный вызов, который приведет к прерыванию запоминания всех компонентов Leaf
, что приводит к повторной визуализации всех компонентов, а не только того конкретного компонента, чья isOpen
пропел изменился
Таким образом, решение для повышения производительности заключается в том, чтобы максимально избегать повторного вызова clickHandler
обратного вызова.Существует два способа решения вышеуказанных проблем
Первый : Первое решение включает использование метода обратного вызова для setState и использование useCallback
только при начальном рендеринге
const Branch = ({ items }) => {
const [expanded, setExpanded] = useState([])
const clickHandler = useCallback(
({ categoryId, level }) => {
setExpanded(prevExpanded => {
let result
if (level === 1) {
result = expanded.includes(categoryId) ? [] : [categoryId]
} else {
result = expanded.includes(categoryId) ? expanded.filter(item => item !== categoryId) : [ ...new Set([ categoryId, ...expanded])]
}
return result;
})
},[])
return (
<ul>
{items && items.map(item => {
const { categoryId, categoryName, level, eventsCount, children } = item
return (
<Leaf
key={categoryId}
categoryId={categoryId}
name={categoryName}
level={level}
eventsCount={eventsCount}
children={children}
isOpen={expanded.includes(categoryId)}
onClick={clickHandler}
/>
)})}
</ul>
)
}
export default Branch;
Second : Когда логика для обновления состояния становится сложной, использование метода обратного вызова для обновления состояния может привести к путанице и затруднению отладки.В таких случаях лучше использовать useReducer
вместо useState
и использовать действие dispatch
для установки состояния
const initialState = [];
const reducer = (state, action) => {
switch(action) {
case 'UPDATE_EXPANDED': {
const { level, categoryId } = action;
if (level === 1) {
return state.includes(categoryId) ? [] : [categoryId]
} else {
return state.includes(categoryId) ? state.filter(item => item !== categoryId) : [ ...new Set([ categoryId, ...state])]
}
}
default: return state;
}
}
const Branch = ({ items }) => {
const [expanded, dispatch] = useReducer(reducer, initialState);
return (
<ul>
{items && items.map(item => {
const { categoryId, categoryName, level, eventsCount, children } = item
return (
<Leaf
key={categoryId}
categoryId={categoryId}
name={categoryName}
level={level}
eventsCount={eventsCount}
children={children}
isOpen={expanded.includes(categoryId)}
onClick={dispatch}
/>
)})}
</ul>
)
}
const Leaf = React.memo(({ name, categoryId, level, children, eventsCount, onClick, isOpen }) => {
const _onClick = () => {
onClick({ type: 'UPDATE_EXPANDED', categoryId, level });
}
return (
<li className={!isOpen && 'hidden'}>
<button onClick={_onClick}>
<span>{name}</span>
</button>
{children.length ? <Branch items={children}/> : ''}
</li>
)
})
export default Leaf
export default Branch;