【问题标题】:React tree view app utilising Hooks (i.e. useCallback)使用 Hooks 的 React 树视图应用程序(即 useCallback)
【发布时间】:2019-03-23 15:37:03
【问题描述】:

我正在寻找有关我使用 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 然后接收并触发该处理程序的方式是否会导致或阻止额外的重新渲染?

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案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
          }
        }, []
      )
    

    所以处理程序根本不会改变。

    【讨论】:

      【解决方案2】:

      您的代码中唯一的主要性能限制是,如果展开更改,则会创建一个新的 clickHandler 回调,这将导致所有 Leaf 组件记忆中断,从而重新渲染所有组件,而不是仅呈现 @987654323 的特定组件@prop 变了

      因此提高性能的解决方案包括尽可能避免重新创建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;
      

      【讨论】:

        猜你喜欢
        • 2022-06-17
        • 1970-01-01
        • 1970-01-01
        • 2021-04-15
        • 1970-01-01
        • 2020-02-15
        • 2020-11-01
        • 2020-04-26
        • 2021-07-21
        相关资源
        最近更新 更多