相关文章推荐
爱旅游的红烧肉  ·  OpenCV4 ...·  2 周前    · 
淡定的冰淇淋  ·  SyntaxError: missing ...·  2 周前    · 
想出国的大象  ·  MongoDB--Spring Data ...·  9 月前    · 
面冷心慈的茶叶  ·  cosmosSearch vector ...·  10 月前    · 
犯傻的上铺  ·  javascript - ...·  1 年前    · 
前端组件库 @ 华为

本文已参与「 掘力星计划 」,赢取创作大礼包,挑战创作激励金。

最近在与 村长 老师一起做 直播 ,给大家分享 vue devui 开源组件库的建设,前面两期以从 0 开始开发一个 tree 组件为栗子🌰,介绍了如何实现一个能渲染多层节点的 tree 组件。

实现能渲染一层节点的 tree 组件

实现能渲染多层节点并带展开图标的 tree 组件

最终实现的效果如下:

这只是实现了渲染的逻辑,tree 节点前面的减号图标是无法点击的,节点是无法收起的。

这次就将带大家一起实现点击图标展开/收起树节点的功能。

我们需要实现的最终效果如下:

增加open标识

之前传入 tree 组件的 data 大致结构是这样的:

label: '一级 1' , level: 1 , children: [{ label: '二级 1-1' , level: 2 ,

这样我并不知道哪些节点需要展开,哪些需要收起,所以第一步应该给需要展开的节点增加open字段。

比如我们希望让以下节点展开,其他都收起:

  • 二级 3-2
  • 改造后的数据结构如下:

    label: '一级 1' , level: 1 , children: [ ... ] label: '一级 2' , level: 1 , open: true , // 新增 children: [ ... ] label: '一级 3' , level: 1 , open: true , // 新增 children: [{ label: '二级 3-2' , level: 2 , open: true , // 新增 children: [ ... ] label: '一级 4' , level: 1 ,

    渲染展开/收起图标

    没有open字段的情况下,节点默认是全部展开的,节点前面的图标全部都是标识展开的减号图标。

    现在有了open字段,我们可以根据该字段渲染展开(减号) or 收起(加号)图标,因此我们需要改造下 renderNode 方法。

    const renderNode = (item) => {
      return (
          class="devui-tree-node"
          style={{ paddingLeft: `${24 * (item.level - 1)}px` }}
            item.children
              // Before
              // ? <IconOpen class="mr-xs" />
              // After
              ? item.open
                ? <IconOpen class="mr-xs" />
                : <IconClose class="mr-xs" />
              : <Indent />
          { item.label }
    

    基本渲染逻辑

  • 如果当前节点没有子节点,则直接渲染,节点无图标,根据当前层级显示相应数量的占位元素 Indent
  • 如果当前节点有子节点,open 属性不为 true,则直接渲染(不渲染子节点),前面的图标为 IconClose
  • 如果当前节点有子节点,open 属性为 true,则渲染当前节点+它的第一层子节点,前面的图标为 IconOpen
  • 如果子节点中又包含 open 为 true 的节点,则以此类推
  • 只渲染展开的节点

    为了方便渲染制定的节点,我们对之前的嵌套数据结构进行一些转换:

  • 将数据拍平
  • 过滤出 open 为 true 的节点数据
  • 转换的基本思路是:

  • 通过 reduce 方法进行递归,初始值为空数组[]
  • 然后判断 item 数据是否有 open 属性
  • 有的话将该数据+子数据都拼接起来
  • 没有的话就只将该数据进行拼接
  • // 获取需要展开的节点数据(无嵌套结构的一维数组)
    const openedTree = (tree) => {
      return tree.reduce((acc, item) => (
        item.open
          ? acc.concat(item, openedTree(item.children))
          : acc.concat(item)
      ), [])
    const openedData = openedTree(data)
    

    到这一步效果就已经有了,只是还不能交互。

    给节点绑定点击事件

    要实现点击图标展开/收起节点功能,就需要给节点图标绑定点击事件。

    const renderNode = (item) => {
      return (
          class="devui-tree-node"
          style={{ paddingLeft: `${24 * (item.level - 1)}px` }}
            item.children
              ? item.open
                ? <IconOpen class="mr-xs" onClick={() => toggle(item)} /> // 给节点绑定点击事件
                : <IconClose class="mr-xs" onClick={() => toggle(item)} /> // 给节点绑定点击事件
              : <Indent />
          { item.label }
    
    const toggle = (item) => {
      // 展开/收起逻辑
    

    处理展开/收起的逻辑

    展开/收起功能,本质上就是改变当前节点数据的 open 字段:

  • 如果当前 open 字段为 true,说明节点是展开的,点击图标时,应该将其设置为 false
  • 如果当前没有 open 字段或者 open 字段为 false,说明节点是收起的,点击图标时,应该将其设置为 true
  • const toggle = (item) => {
      item.open = !item.open // 改变当前节点的open字段
    

    这样我们的目标就完成了:

    实现能展开/收起的 tree

    不过目前代码都写在 tree 组件的 setup 方法里,加上之前的 renderNode 等方法,setup 方法已经有60+行代码,后续如果继续增加其他功能,setup 代码量会越来越大,也越来越不可读和难以维护,也就越容易出 bug。

    因此需要对它进行重构,使用 vue3 的 composition api,将节点展开/收起相关的变量和逻辑抽离到一个单独的use-toggle.ts文件中。

    composables/use-toggle.ts

    import { ref } from 'vue'
    export default function useToggle(data: unknown): any {
      const openedTree = (tree) => {
        return tree.reduce((acc, item) => (
          item.open
            ? acc.concat(item, openedTree(item.children))
            : acc.concat(item)
        ), [])
      const openedData = ref(openedTree(data)) // 响应式对象
      const toggle = (item) => {
        console.log('toggle', item, item.id, item.open);
        item.open = !item.open
        openedData.value = openedTree(data)
      return {
        openedData,
        toggle,
    

    tree.tsx 中只需要引入需要的变量和方法即可。

    import useToggle from './composables/use-toggle'
    setup(props) {
      // 其他逻辑
      // 从 useToggle 中引入需要的变量和方法
      const { openedData, toggle } = useToggle(data.value)
      // 其他逻辑
    

    本文主要讲述如何一步步给 tree 组件增加展开/收起功能,并使用vue3的组合式api对这个功能从setup 中抽离。

    欢迎参与devui开源项目

    我们 DevUI 团队有多个开源项目,现在都在招募contributor,欢迎大家一起参与开源中来!(感兴趣的小伙伴可以添加DevUI小助手的微信:devui-official,将你拉到我们的核心开发群)

  • Ng DevUI: github.com/DevCloudFE/…
  • Vue DevUI: gitee.com/devui/vue-d…
  • DevUI Admin github.com/DevCloudFE/…
  • DevUI官网:devui.design/