最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

使用React封裝一個Tree樹形組件的實例代碼

 更新時間:2024年03月11日 10:58:06   作者:滑動變滾動的蝸牛  
這篇文章主要介紹了使用React封裝一個Tree樹形組件的實例,文中通過代碼示例講解的非常詳細,對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下

前言

為什么要造這樣一個輪子呢?

最近在學(xué)習(xí) next ,想用 next 重構(gòu)一下自己的博客,而在 自己博客 的編輯頁面中有使用到 antd 的一個樹形的結(jié)構(gòu)組件來展示文章的分類;

而我的 個人博客 (next版) 使用的是 next-ui ,但是里面并沒有 tree 組件,看了下最近很火的 shadcn 也沒有類似組件,我也不想為了 tree 又引入 antd 了,就想著自己封裝一個玩玩,權(quán)當(dāng)提升技術(shù)了(當(dāng)然了非 next 版)。順便還能為 我的組件庫 添加一員。

當(dāng)然我是對照 antd 作為模板開發(fā)的,但是他的 tree 是沒有單獨 check 的,當(dāng)時我的舊版博客中為了實現(xiàn)該需求我可沒少費工夫。

線上 Demo

源碼

實現(xiàn)思路

我這里主要是根據(jù) antdProps 選擇一部分,并按照自身需求來增減實現(xiàn)的。

下面我就講述整個 tree 樹形組件的核心部分吧,其他一些屬性就不細講了,感興趣可以直接看 源碼

Html 基本結(jié)構(gòu)

下面是整個組件的基本結(jié)構(gòu),renderTreeList 函數(shù)遞歸調(diào)用渲染 treechildren 節(jié)點。

類名 node-content 中的就是節(jié)點的內(nèi)容了,根據(jù)需求樣式自定義即可。

const Tree = forwardRef<TreeInstance, TreeProps>((props, ref) => {
  const { checkable, treeData, checkedKeys, defaultExpandAll, multiple, singleSelected, selectable = true, selectedKeys: propsSelectedKeys, onCheck, onSelect, onRightClick, ...ret } = props
  
  // ... 省略部分內(nèi)容,只展示核心結(jié)構(gòu)

  // 遞歸渲染 tree 的列表 
  const renderTreeList = (list?: TreeNode[]) => {
    // checkTree 的說明見下面
    if(!checkTree) return null
    return list?.map(item => {
      const checkItem = checkTree![item.key]
      return (
        <div key={item.key} className={`node`}>
          <div className={`node-content`}>
            // checkItem.show 用來判斷展開
            <div>↓</div>
            // checkItem.checked 用來處理是否 check
            <Checkbox />
            <div>{item.title}</div>
          </div>
          <div className='children'>
            {renderTreeList(item.children)}
          </div>
        </div>
      )
    })
  }

  return (
    <div className={`${classPrefix} ${ret.className ?? ''}`} style={ret.style}>
      {renderTreeList(treeData)}
    </div>
  )
})

實現(xiàn)交互的樹形結(jié)構(gòu) (checkTree)

生成一個用于實現(xiàn)交互效果的樹形結(jié)構(gòu) ( checkTree )

export type CheckTreeItem = {
  /** 父節(jié)點的 key 值 */
  parentKey?: string
  /** 子節(jié)點的 key 數(shù)組 */
  childKeys?: string[]
  /** 是否展開 */
  show: boolean
  /** 是否選中 */
  checked: boolean
  /** 是否有 checkbox */
  checkable?: boolean
  /** 禁用 checkbox */
  disableCheckbox?: boolean
  /** 禁止整個節(jié)點的選擇 */
  disabled?: boolean
}

export type CheckTree = Record<string, CheckTreeItem>
// ...
const [checkTree, setCheckTree] = useState<CheckTree>();

整體是一個只有一層結(jié)構(gòu)的對象,使用每一項數(shù)據(jù)中唯一的 key 值作為 checkTree 的 key,通過 parentKey 和 childKeys 來查找該節(jié)點的 父子兄弟節(jié)點。

例:

初始化樹形結(jié)構(gòu)

根據(jù) generateCheckTree 函數(shù)的遞歸調(diào)用,將傳入的 treeData 樹狀結(jié)構(gòu)數(shù)據(jù)轉(zhuǎn)變?yōu)榻M件需要的 checkTree。

// ...
useEffect(() => {
  if(!treeData?.length) return
  const generateCheckTree = (list: TreeNode[], parentKey?: string) => {
    return list?.reduce((pre, cur) => {
      // checkedKeys 就是默認傳入 check 項,用于默認是否勾選
      const curChecked = Boolean(checkedKeys?.includes(cur.key));
      pre[cur.key] = {
        // 默認是否展開該樹形結(jié)構(gòu)
        show: !!defaultExpandAll, 
        checked: curChecked, 
        parentKey,
      }
      // 一些屬性的默認值
      if(cur.checkable) pre[cur.key].checkable = true
      if(cur.disableCheckbox) pre[cur.key].disableCheckbox = true
      if(cur.disabled) pre[cur.key].disabled = true
      // 有孩子節(jié)點就遞歸調(diào)用,生成數(shù)據(jù)
      if(cur.children?.length) {
        pre[cur.key].childKeys = cur.children.map(c => c.key)
        const treeChild = generateCheckTree(cur.children, cur.key)
        pre = {...pre, ...treeChild}
      }
      return pre
    }, {} as CheckTree)
  }
  const state = generateCheckTree(treeData)
  setCheckTree(state)
  // ...
}, [treeData])

大致就是如下圖所示,將 treeData 轉(zhuǎn)變?yōu)?nbsp;checkTree。

點擊 check 節(jié)點

對應(yīng)上面 html 結(jié)構(gòu)中的 CheckBox 位置, checkable 等屬性就是用來判斷是否展示禁用 CheckBox 的。

// ...
{(checkable && item.checkable !== false) && (
  <CheckBox 
    checked={checkItem.checked} 
    disabled={item.disabled || item.disableCheckbox}
    // 先忽略,用來判斷當(dāng)前是否有孩子節(jié)點被選中了,true 則代表需要展示 checkbox 的半選樣式
    indeterminate={getIsSomeChildCheck(checkItem, checkTree)}
    onChange={() => {
      if(item.disabled || item.disableCheckbox) return
      onNodeCheck(item.key)
    }} 
  />   
)}

先看 onChange 中觸發(fā)的回調(diào) onNodeCheck 函數(shù),該函數(shù)主要是將 checkItem 中對應(yīng)該項的 checked 取反一下。

/** 點擊選中節(jié)點 */
const onNodeCheck = (key: string) => {
  const checkItem = checkTree![key]
  const curChecked = !checkItem.checked
  checkItem.checked = curChecked;

  // 先忽略,用來判斷是否是單選的
  if(singleSelected) onSingleCheck(key, curChecked)
  else onCheckChildAndParent(key, curChecked)
  setCheckTree({...checkTree})

  // 先忽略,用來獲取當(dāng)前 check 的所有 key 值
  const keys = getCheckKeys(checkTree!)

  // check 觸發(fā)的組件回調(diào)
  onCheck?.(keys, {
    key, 
    // 這步判斷主要是單選時,選擇父節(jié)點時只會選中其子節(jié)點
    checked: keys.includes(key) ? curChecked : false,  
    parentKeys: getParentKeys(key, checkTree!),
    treeDataItem: getTreeDataItem(key, treeData),
  })
}

然后通過 onCheckChildAndParent 函數(shù),處理對應(yīng)的父子節(jié)點的選中狀態(tài)。

  • 子節(jié)點: checkAllChild 遞歸將當(dāng)前節(jié)點的 子節(jié)點 全選或全不選。

  • 父節(jié)點: checkAllParent 遞歸處理當(dāng)前節(jié)點的 父節(jié)點 的選中狀態(tài)。

  • 兄弟節(jié)點:只有在單選節(jié)點的時候需要,選擇同層節(jié)點,使 兄弟節(jié)點 取消選中

/** 處理父子節(jié)點的選中狀態(tài) */
const onCheckChildAndParent = (key: string, curChecked: boolean, cTree = checkTree!) => {
  const checkItem = cTree[key];

  // 全選/不選所有子節(jié)點
  (function checkAllChild(childKeys?: string[]) {
    childKeys?.forEach(childKey => {
      cTree[childKey].checked = curChecked
      checkAllChild(cTree[childKey].childKeys)
    })
  })(checkItem.childKeys);

  // 處理父節(jié)點的選中狀態(tài)
  (function checkAllParent(parentKey?: string) {
    if(!parentKey) return
    if(!curChecked) { // 取消所有父節(jié)點的選中
      cTree[parentKey].checked = false
      checkAllParent(cTree[parentKey].parentKey)
    } else { // 將所有子節(jié)點被全選的父節(jié)點也選中
      const isSiblingCheck = !!cTree[parentKey].childKeys?.every(childKey => cTree[childKey].checked)
      if(isSiblingCheck) { // 判斷兄弟節(jié)點是否也全被選中
        cTree[parentKey].checked = true
        checkAllParent(cTree[parentKey].parentKey)
      }
    }
  })(checkItem.parentKey);

  // 同層單選時,使兄弟節(jié)點取消選中
  if(singleSelected && curChecked) {
    const keys = cTree[key].parentKey ? cTree[cTree[key].parentKey!].childKeys : firstNodeKeys
    keys?.forEach(siblingKey => {
      if(siblingKey !== key) {
        cTree[siblingKey].checked = false
      }
    })
  }
}

子節(jié)點的展開實現(xiàn)

html 結(jié)構(gòu)和 css 簡單樣式如下,通過 show 屬性給 children 節(jié)點賦高度,由于定義了 transition 屬性,所以當(dāng)高度變化時,就會觸發(fā)節(jié)點的 展開/收縮 動畫。

<div 
  className={`node-children`} 
  // height: fit-content; 無法觸發(fā)過渡效果,需要準(zhǔn)確的值
  // 也可通過 maxHeight 設(shè)置一個很大的值來解決,但值過大又會使過度效果難看,所以這里需要獲取一個準(zhǔn)確的高度
  style={{maxHeight: checkItem.show ? `${getTreeChildHeight(item.children!)}px` : 0}}
>
  {renderTreeList(item.children)}
</div>
.node-children {
  padding-left: 24px;
  overflow-y: hidden;
  transition: max-height 0.3s ease;
}

這里有一個點要注意,就是無法直接給子節(jié)點定義一個由內(nèi)容撐開的高度 height: fit-content;,這樣會使 transition 無法正常觸發(fā)。當(dāng)然可以通過給一個比較大的 maxHeight 來設(shè)置最大高度,這樣 transition 就會以 maxHeight 的高度實現(xiàn)動畫效果,但是這樣當(dāng)子節(jié)點總高度和 maxHeight 出入過大時就會使動畫效果很不好看。

所以我這里最終通過 getTreeChildHeight 函數(shù)來準(zhǔn)確計算孩子節(jié)點的總高度了。

首先等待 checkTree 完成構(gòu)建以及樹形結(jié)構(gòu)渲染完成,然后準(zhǔn)確獲取每個節(jié)點的高度,因為每個節(jié)點的 title 都是 ReactNode ,所以需要都獲取一遍他們的高度。

/** 標(biāo)題的最小高度 */
const TITLE_MIN_HEIGHT = 24;
/** 每個標(biāo)題的下邊距 */
const TITLE_MB = 6;

// 等待樹形結(jié)構(gòu)渲染完畢,獲取 title 的高度
useEffect(() => {
  if(!checkTree || !isTreeRender.current) return
  const info: TitleNodeInfo = {};
  for(let key in checkTree) {
    // 每個標(biāo)題渲染的內(nèi)容,都要根據(jù) key 給一個唯一的類名。
    const titleNode = document.querySelector(`.node-title-${key}`)
    if(titleNode) {
      info[key] = {height: Math.max(titleNode.clientHeight, TITLE_MIN_HEIGHT) + TITLE_MB} 
    }
  }
  setTitleNodeInfo(info)
  isTreeRender.current = false
}, [checkTree])

此時每個節(jié)點的 children 節(jié)點的高度,就能通過 getTreeChildHeight 函數(shù)遞歸計算得出了。

const getTreeChildHeight = (list: TreeNode[]) => {
  return list?.reduce((pre, cur) => {
    pre += (titleNodeInfo[cur.key]?.height ?? (TITLE_MIN_HEIGHT + TITLE_MB))
    if(checkTree![cur.key].show && cur.children?.length) {
      pre += getTreeChildHeight(cur.children)
    }
    return pre
  }, 0) ?? 0
}

ref 方法

然后我在組件里面實現(xiàn)了一些用于獲取 treeData 數(shù)據(jù)的一些方法,簡單來說都是一些遞歸調(diào)用等方法。

屬性名描述類型
getCheckTree獲取當(dāng)前選中的樹形結(jié)構(gòu)() => CheckTree | undefined
getParentKeys根據(jù) key 值獲取其父節(jié)點,從 key 節(jié)點的最親關(guān)系開始排列(key: string) => string[] | undefined
getSiblingKeys根據(jù) key 值獲取其兄弟節(jié)點,會包括自身節(jié)點(key: string) => string[] | undefined
getChildKeys根據(jù) key 值獲取其子節(jié)點(key: string) => string[] | undefined
getCheckKeys獲取當(dāng)前 check 中的所有 key() => string[]
getTreeDataItem獲取當(dāng)前 treeData 中的節(jié)點數(shù)據(jù)(key: string) => TreeNode | undefined

最終實現(xiàn)的 Props

其他屬性的功能實現(xiàn)我就不一一敘述了,感興趣可以直接看 源碼

屬性名描述類型默認值
checkable是否有選擇框booleanfalse
checkedKeys(受控)選中復(fù)選框的樹節(jié)點的key,當(dāng)不在數(shù)組中的父節(jié)點需要被選中時,對應(yīng)節(jié)點也將選中,觸發(fā) onCheck 回調(diào),使該值保持正確string[]null
defaultExpandAll默認展開所有樹節(jié)點booleanfalse
multiple支持點選多個節(jié)點(節(jié)點本身)booleanfalse
singleSelected是否只能單選一個節(jié)點booleanfalse
selectable是否可選中booleantrue
selectedKeys(受控)設(shè)置選中的樹節(jié)點,多選需設(shè)置 multiple 為 truestring[]"-"
treeData樹形結(jié)構(gòu)的數(shù)據(jù)TreeNode[]--
onCheck點擊復(fù)選框觸發(fā)(checkedKeys: string[], params?: OnCheckParams) => void--
onSelect點擊樹節(jié)點觸發(fā)(selectKeys: string[], params: OnSelectParams) => void--
onRightClick點擊右鍵觸發(fā)(params: onRightClickParams) => void--
className類名string--
stylestyle樣式{}--
childrenchildren節(jié)點ReactNode--
ref-TreeInstance--

以上就是使用React封裝一個Tree樹形組件的實例代碼的詳細內(nèi)容,更多關(guān)于React封裝Tree組件的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React中的setState使用細節(jié)和原理解析(最新推薦)

    React中的setState使用細節(jié)和原理解析(最新推薦)

    這篇文章主要介紹了React中的setState使用細節(jié)和原理解析(最新推薦),前面我們有使用過setState的基本使用, 接下來我們對setState使用進行詳細的介紹,需要的朋友可以參考下
    2022-12-12
  • 編寫React組件項目實踐分析

    編寫React組件項目實踐分析

    本文通過實例給大家分享了編寫React組件項目實踐的全過程,對此有興趣的朋友可以參考下。
    2018-03-03
  • React運行機制超詳細講解

    React運行機制超詳細講解

    React 作為當(dāng)下最為流行的前端開發(fā)框架之一,使用它可以快速構(gòu)建大型 Web 應(yīng)用,加上其出色的性能表現(xiàn),使得眾多互聯(lián)網(wǎng)公司對它格外地青睞,這篇文章主要介紹了React運行機制
    2022-11-11
  • react如何解決Hooks閉包陷阱解決方案

    react如何解決Hooks閉包陷阱解決方案

    在React中使用Hooks時,開發(fā)者可能會遇到一些常見的坑點,這篇文章主要介紹了react如何解決Hooks閉包陷阱的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2026-05-05
  • react中hook介紹以及使用教程

    react中hook介紹以及使用教程

    這篇文章主要給大家介紹了關(guān)于react中hook及使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • React使用Electron開發(fā)桌面端的詳細流程步驟

    React使用Electron開發(fā)桌面端的詳細流程步驟

    React是一個流行的JavaScript庫,用于構(gòu)建Web應(yīng)用程序,結(jié)合Electron框架,可以輕松地將React應(yīng)用程序打包為桌面應(yīng)用程序,本文詳細介紹了使用React和Electron開發(fā)桌面應(yīng)用程序的步驟,需要的朋友可以參考下
    2023-06-06
  • 詳解React setState數(shù)據(jù)更新機制

    詳解React setState數(shù)據(jù)更新機制

    這篇文章主要介紹了React setState數(shù)據(jù)更新機制的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用React框架,感興趣的朋友可以了解下
    2021-04-04
  • ReactHooks批量更新state及獲取路由參數(shù)示例解析

    ReactHooks批量更新state及獲取路由參數(shù)示例解析

    這篇文章主要介紹了React Hooks如何實現(xiàn)批量更新state以及獲取路由參數(shù)的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-10-10
  • React優(yōu)雅的封裝SvgIcon組件示例

    React優(yōu)雅的封裝SvgIcon組件示例

    這篇文章主要為大家介紹了React優(yōu)雅的封裝SvgIcon組件示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-02-02
  • React Native之ListView實現(xiàn)九宮格效果的示例

    React Native之ListView實現(xiàn)九宮格效果的示例

    本篇文章主要介紹了React Native之ListView實現(xiàn)九宮格效果的示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-08-08

最新評論

贵德县| 加查县| 独山县| 丰县| 丽水市| 县级市| 厦门市| 常熟市| 磴口县| 大同市| 中西区| 乡城县| 鹿邑县| 淮阳县| 锦州市| 凤城市| 独山县| 枣庄市| 海晏县| 砚山县| 新沂市| 南华县| 松江区| 天等县| 邯郸市| 苗栗市| 高安市| 亚东县| 长宁区| 金堂县| 西乌| 黄龙县| 田林县| 藁城市| 崇左市| 丰城市| 保靖县| 敦煌市| 齐河县| 都安| 长岭县|