我正在寻找有关使用Hooks构建React树视图应用程序的方法的意见。
这是使用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 && 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 && 'hidden'}>
<button onClick={_onClick}>
<span>{name}</span>
</button>
{children.length ? <Branch items={children}/> : ''}
</li>
)
})
export default Leaf
我希望有人检查该代码的性能(例如,不必要的重新渲染次数)。我对您对我对React.memo的使用和点击事件处理程序(useCallback)的使用感兴趣。
我传给clickHandler
然后接收并触发该处理程序的方式会导致或阻止其他重新渲染吗?
答案 0 :(得分:2)
代码中唯一的主要性能限制是,如果扩展的更改发生变化,则会创建一个新的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;
第二:当更新状态的逻辑变得复杂时,使用回调方法进行状态更新可能会造成混乱并且难以调试。在这种情况下,最好使用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;
答案 1 :(得分:1)
使用functional updates会更有效:
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
}
}, []
)
因此处理程序完全不会更改。