#reactjs #react-hooks
#reactjs #реагирующие перехваты
Вопрос:
Я ищу мнения о моем подходе к созданию приложения для просмотра дерева реакции, использующего хуки.
Вот код, использующий useCallback, React.memo и useState. Пожалуйста, обратите внимание, что одновременно может быть открыт только один элемент 1-го уровня, на остальных уровнях может быть открыто сразу несколько элементов.
Branch.js:
import React, { useState, useCallback} from 'react'
import Leaf from './Leaf'
const Branch = ({ items }) => {
const [expanded, setExpanded] = useState([])
const clickHandler = useCallback(
({ categoryId, level }) => {
let result
if (level === 1) {
result = expanded.includes(categoryId) ? [] : [categoryId]
} else {
result = expanded.includes(categoryId) ? expanded.filter(item => item !== categoryId) : [ ...new Set([ categoryId, ...expanded])]
}
setExpanded(result)
},[expanded])
return (
<ul>
{items amp;amp; 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
Leaf.js:
import React from 'react'
import Branch from './Branch'
const Leaf = React.memo(({ name, categoryId, level, children, eventsCount, onClick, isOpen }) => {
const _onClick = () => {
onClick({ categoryId, level })
}
return (
<li className={!isOpen amp;amp; 'hidden'}>
<button onClick={_onClick}>
<span>{name}</span>
</button>
{children.length ? <Branch items={children}/> : ''}
</li>
)
})
export default Leaf
Я бы хотел, чтобы кто-нибудь проверил код на предмет производительности (т. Е. Количества ненужных повторных отображений), которые могут произойти. Мне интересно ваше мнение о моем использовании React.memo и обработчика событий click (useCallback).
Вызывает ли способ, которым я передаю вниз clickHandler
, а затем получаю и запускаю этот обработчик, или предотвращает дополнительные повторные рендеринги?
Ответ №1:
Это было бы более эффективно с функциональными обновлениями:
const clickHandler = useCallback(
({ categoryId, level }) => {
setExpanded(expanded => {
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
}
}, []
)
Таким образом, обработчик вообще не меняется.
Ответ №2:
Единственное серьезное ограничение производительности в вашем коде заключается в том, что при изменении expanded создается новый 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 resu<
})
},[])
return (
<ul>
{items amp;amp; 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;
Второе: когда логика обновления состояния становится сложной, использование метода обратного вызова для обновления состояния может запутать и затруднить отладку. В таких случаях лучше использовать 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 amp;amp; 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 amp;amp; 'hidden'}>
<button onClick={_onClick}>
<span>{name}</span>
</button>
{children.length ? <Branch items={children}/> : ''}
</li>
)
})
export default Leaf
export default Branch;