天天看點

Vue元件遞歸——元件化開發的必備技能

作者:前端阿飛V

不知道大家有沒遇到過這樣的場景:渲染清單資料的時候,清單的子項還是清單。如果層級少尚且可以用幾個for循環搞定,但是層級多或者層級不确定就有點無從下手了。

其實這就是樹形結構資料,像常見的組織架構圖,檔案夾目錄,導航菜單等都屬于這種結構。很多元件庫都帶有樹形元件,但往往樣式不是我們想要的,改起來也非常的費勁。那麼,如何自己渲染這些資料呢?答案就是——元件遞歸!

效果展示

Vue元件遞歸——元件化開發的必備技能

以上就是使用元件遞歸,并加入簡單互動的展示效果。點選節點會在控制台輸出節點對應的資料,如果有子節點,則會展開或收起子節點。接下來我們就看看如何實作以上效果吧!

渲染完整資料

渲染資料這一步非常簡單,首先是把樹形結構封裝成一個清單元件,其次判斷每一項有沒有子節點,如果有子節點,再使用自身元件去渲染就可以了。

src/components/myTree.vue

<template>
  <div class="tree-item">
    <div v-for="item in treeData" :key="item.id">
      <div class="item-title">{{ item.name }}</div>
      <div v-if="item.children && item.children.length" class="item-childen">
        <my-tree :treeData="item.children"></my-tree>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'myTree',
  props: {
    treeData: {
      type: Array,
      default: () => []
    }
  }
}
</script>

<style lang="scss" scoped>
.tree-item {
  .item-title {
    padding: 4px 8px;
  }
  .item-childen {
    padding-left: 20px;
  }
}
</style>
           

src/App.vue

<template>
  <my-tree :tree-data="treeData"></my-tree>
</template>

<script>
const treeData = [
  { id: 1, name: '一級1' },
  {
    id: 2,
    name: '一級2',
    children: [
      { id: 3, name: '二級2-1' },
      { id: 4, name: '二級2-2' }
    ]
  },
  {
    id: 5,
    name: '一級3',
    children: [
      {
        id: 6,
        name: '二級3-1',
        children: [
          { id: 7, name: '三級3-1-1' },
          { id: 8, name: '三級3-1-2' }
        ]
      },
      { id: 9, name: '二級3-2' },
      { id: 10, name: '二級3-3' }
    ]
  }
]
import myTree from '@/components/myTree.vue'
export default {
  components: {
    myTree
  },
  data() {
    return {
      treeData: treeData
    }
  }
}
</script>
           

效果如下:

Vue元件遞歸——元件化開發的必備技能

擷取節點資料

接下來我們要做的是,點選節點時在控制台輸出對應的資料。首先我們使用 $emit,将一級節點的 item 傳遞出去,也就是子傳父的方法,相信大家都會。

其次是将内層節點的資料傳遞出去,同樣使用子傳父的方法,隻是我們需要給元件裡面的 my-tree 綁定@node-click="$emit('node-click', $event)",這樣每次子級每次都可以調用父級的 node-click 方法,父級又調用它的父級 node-click 方法,最終調的都是最外層的 node-click 方法,我們隻需要在這個過程中,把資料傳遞過去就可以了。這塊有點繞,相信大家多看幾遍應該可以看懂。修改如下:

src/components/myTree.vue

<div class="item-title" @click="itemNodeClick(item)">{{ item.name }}</div>
<div v-if="item.children && item.children.length" class="item-childen">
  <my-tree
    :treeData="item.children"
    @node-click="$emit('node-click', $event)"
  ></my-tree>
</div>
...
itemNodeClick(item) {
  this.$emit("node-click", item)
}
           

src/App.vue

<my-tree :tree-data="treeData" @node-click="nodeClick"></my-tree>
...
nodeClick(val) {
  console.log(val)
}
           

效果如下:

Vue元件遞歸——元件化開發的必備技能

動态展開收起

這一步的思路是給元件設定一個數組,數組中存放的是目前清單中需要展開的節點的id,當點選節點的時候添加或删除節點id,然後判斷每個節點的id在不在這個數組,在則顯示子節點,不在則隐藏子節點。

src/components/myTree.vue

<div class="item-title" @click="nodeClick(item)">
  <span>{{ item.name }}</span>
  <span v-if="item.children && item.children.length">
    [{{ isOpen(item.id) ? '-' : '+' }}]
  </span>
</div>
<div
  v-if="item.children && item.children.length"
  v-show="isOpen(item.id)"
  class="item-childen"
>
  <my-tree
    :treeData="item.children"
    @node-click="$emit('node-click', $event)"
  ></my-tree>
</div>

...
data() {
  return {
    expandedKeys: [] // 目前清單需要展開的節點id組成的數組
  }
},
methods: {
  nodeClick(item) {
    this.$emit('node-click', item)
    if (item.children && item.children.length) {
      let index = this.expandedKeys.indexOf(item.id)
      if (index > -1) {
        // 如果目前節點id存在數組中,則删除
        this.expandedKeys.splice(index, 1)
      } else {
        // 如果目前節點id不存在數組中,則添加
        this.expandedKeys.push(item.id)
      }
    }
  },
  isOpen(id) {
    // 判斷節點id在不在數組中,在則顯示,不在則隐藏
    return this.expandedKeys.includes(id)
  }
}
           

效果如下:

Vue元件遞歸——元件化開發的必備技能

最後我們再添加一些樣式,就大功告成啦!

完整代碼

src/components/myTree.vue

<template>
  <div class="tree-item">
    <div v-for="item in treeData" :key="item.id">
      <div class="item-title" @click="nodeClick(item)">
        <span>{{ item.name }}</span>
        <span v-if="item.children && item.children.length">
          [{{ isOpen(item.id) ? '-' : '+' }}]
        </span>
      </div>
      <div
        v-if="item.children && item.children.length"
        v-show="isOpen(item.id)"
        class="item-childen"
      >
        <my-tree
          :treeData="item.children"
          @node-click="$emit('node-click', $event)"
        ></my-tree>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'myTree',
  props: {
    treeData: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      expandedKeys: [] // 目前展開的節點id組成的數組
    }
  },
  methods: {
    nodeClick(item) {
      this.$emit('node-click', item)
      if (item.children && item.children.length) {
        let index = this.expandedKeys.indexOf(item.id)
        if (index > -1) {
          // 如果目前節點id存在數組中,則删除
          this.expandedKeys.splice(index, 1)
        } else {
          // 如果目前節點id不存在數組中,則添加
          this.expandedKeys.push(item.id)
        }
      }
    },
    isOpen(id) {
      // 判斷節點id在不在數組中,在則顯示,不在則隐藏
      return this.expandedKeys.includes(id)
    }
  }
}
</script>

<style lang="scss" scoped>
.tree-item {
  cursor: pointer;
  .item-title {
    padding: 4px 8px;
    &:hover {
      background: #eee;
    }
  }
  .item-childen {
    padding-left: 20px;
  }
}
</style>
           

src/App.vue

<template>
  <my-tree :tree-data="treeData" @node-click="nodeClick"></my-tree>
</template>

<script>
const treeData = [
  { id: 1, name: '一級1' },
  {
    id: 2,
    name: '一級2',
    children: [
      { id: 3, name: '二級2-1' },
      { id: 4, name: '二級2-2' }
    ]
  },
  {
    id: 5,
    name: '一級3',
    children: [
      {
        id: 6,
        name: '二級3-1',
        children: [
          { id: 7, name: '三級3-1-1' },
          { id: 8, name: '三級3-1-2' }
        ]
      },
      { id: 9, name: '二級3-2' },
      { id: 10, name: '二級3-3' }
    ]
  }
]
import myTree from '@/components/myTree.vue'
export default {
  components: {
    myTree
  },
  data() {
    return {
      treeData: treeData
    }
  },
  methods: {
    nodeClick(val) {
      console.log(val)
    }
  }
}
</script>
           

效果如下:

Vue元件遞歸——元件化開發的必備技能

以上就是今天的分享!有興趣的小夥伴可以動手試一哈,把元件進一步封裝,或修改成自己想要的樣式。 Vue官方的樹形視圖

如果覺得有用,不要忘了點個贊呦!(~ ̄▽ ̄)↗

Demo位址:https://github.com/frontend-afei/component-recurse-demo

繼續閱讀