使用Hooks反应树视图应用程序(即useCallback)

时间:2019-03-23 15:37:03

标签: reactjs react-hooks

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

2 个答案:

答案 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
      }
    }, []
  )

因此处理程序完全不会更改。