2024-10-09|閱讀時間 ‧ 約 0 分鐘

EP32 - ex4. Tree View

可以想像Tree View會有很多階層~
相當適合來搭建組件父子關係~
但還是有點無法想像怎麼實作!一起快來看看怎麼回事~

註解說明這個組件是一個嵌套樹狀結構,並且它能夠遞歸渲染自己。用戶可以雙擊一個項目將其轉換為文件夾。

App.vue

<!--
A nested tree component that recursively renders itself.
You can double click on an item to turn it into a folder.
-->

<script setup>
import { ref } from 'vue'
import TreeItem from './TreeItem.vue'

const treeData = ref({
name: 'My Tree',
children: [
{ name: 'hello' },
{ name: 'world' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [{ name: 'hello' }, { name: 'world' }]
},
{ name: 'hello' },
{ name: 'world' },
{
name: 'child folder',
children: [{ name: 'hello' }, { name: 'world' }]
}
]
}
]
})
</script>

<template>
<ul>
<TreeItem class="item" :model="treeData"></TreeItem>
</ul>
</template>

<style>
.item {
cursor: pointer;
line-height: 1.5;
}
.bold {
font-weight: bold;
}
</style>

<script setup> 部分

import { ref } from 'vue' 
import TreeItem from './TreeItem.vue'
  • import { ref } from 'vue':從 Vue 中導入 ref 函數,這是一個響應式引用,用來創建可變的數據。
  • import TreeItem from './TreeItem.vue':導入名為 TreeItem 的子組件,這個組件負責渲染樹中的每個項目。
const treeData = ref({
name: 'My Tree',
children: [
{ name: 'hello' },
{ name: 'world' },
{
name: 'child folder',
children: [
{
name: 'child folder',
children: [{ name: 'hello' }, { name: 'world' }]
},
{ name: 'hello' },
{ name: 'world' },
{
name: 'child folder',
children: [{ name: 'hello' }, { name: 'world' }]
}
]
}
]
})

const treeData = ref({...}):定義了一個響應式的 treeData 變數,這個變數包含了一個樹狀結構的數據。

  • name: 'My Tree':樹的根節點名稱為 "My Tree"。
  • children: [...]:根節點的子項目是一個陣列,包含多個項目,每個項目都可以是一個葉子或一個具有子項的文件夾。
    • 項目包括名稱為 "hello" 和 "world" 的葉子項目,以及一個名為 "child folder" 的文件夾,該文件夾中又包含其他項目。

<template> 部分

  <ul>
<TreeItem class="item" :model="treeData"></TreeItem>
</ul>
  • <ul>:使用無序列表標籤來呈現樹狀結構。
  • <TreeItem class="item" :model="treeData"></TreeItem>:渲染 TreeItem 子組件,並將 treeData 作為屬性 model 傳遞給它。這樣 TreeItem 就能夠使用這些數據來顯示樹狀結構。

<style> 部分

<style>
.item {
cursor: pointer;
line-height: 1.5;
}
.bold {
font-weight: bold;
}
</style>
  • .item:為 TreeItem 的 CSS 類,設置游標為指針型,讓使用者知道這些項目是可點擊的。同時設置行高為 1.5,增強可讀性。
  • .bold:設置字體加粗的 CSS 類,用於突出顯示特定的項目。

TreeItem.vue

<script setup>
import { ref, computed } from 'vue'

const props = defineProps({
model: Object
})

const isOpen = ref(false)
const isFolder = computed(() => {
return props.model.children && props.model.children.length
})

function toggle() {
isOpen.value = !isOpen.value
}

function changeType() {
if (!isFolder.value) {
props.model.children = []
addChild()
isOpen.value = true
}
}

function addChild() {
props.model.children.push({ name: 'new stuff' })
}
</script>

<template>
<li>
<div
:class="{ bold: isFolder }"
@click="toggle"
@dblclick="changeType">
{{ model.name }}
<span v-if="isFolder">[{{ isOpen ? '-' : '+' }}]</span>
</div>
<ul v-show="isOpen" v-if="isFolder">
<!--
A component can recursively render itself using its
"name" option (inferred from filename if using SFC)
-->
<TreeItem
class="item"
v-for="model in model.children"
:model="model">
</TreeItem>
<li class="add" @click="addChild">+</li>
</ul>
</li>
</template>

這段 Vue.js 代碼定義了一個名為 TreeItem 的組件,該組件用於渲染樹狀結構中的每一個項目,並實現文件夾的打開與關閉功能。以下是逐行解釋:

<script setup> 部分

import { ref, computed } from 'vue'
  • import { ref, computed } from 'vue':從 Vue 中導入 refcomputed 函數。
    • ref 用於創建響應式變量。
    • computed 用於創建響應式計算屬性。
const props = defineProps({
model: Object
})
  • const props = defineProps({...}):定義組件的屬性,這裡的 model 是一個物件(Object),用於傳遞樹狀結構的數據。
const isOpen = ref(false)
  • const isOpen = ref(false):定義一個響應式變量 isOpen,初始值為 false,表示文件夾是否展開。
const isFolder = computed(() => {
return props.model.children && props.model.children.length
})
  • const isFolder = computed(() => {...}):定義一個計算屬性 isFolder,用於判斷當前項目是否為文件夾。如果 model 物件有 children 並且其長度大於零,則返回 true,否則返回 false
function toggle() {
isOpen.value = !isOpen.value
}
  • function toggle() {...}:定義一個函數 toggle,用來切換 isOpen 的值。當用戶點擊項目時,這個函數會被調用,從而展開或收起文件夾。
function changeType() {
if (!isFolder.value) {
props.model.children = []
addChild()
isOpen.value = true
}
}
  • function changeType() {...}:定義一個函數 changeType,用於改變項目的類型。如果當前項目不是文件夾(!isFolder.value),則:
    • modelchildren 設置為空陣列,準備為其添加子項。
    • 調用 addChild() 函數添加一個新的子項。
    • isOpen 設置為 true,展開該項目。
function addChild() {
props.model.children.push({ name: 'new stuff' })
}
  • function addChild() {...}:定義一個函數 addChild,該函數向 modelchildren 陣列中推入一個新的物件,這個物件的 name 屬性設置為 'new stuff',用於表示新添加的項目。

<template> 部分

<template>
<li> <!-- 列表項,代表樹狀結構中的一個項目 -->
<div
:class="{ bold: isFolder }" <!-- 根據 isFolder 計算 class,若為文件夾則加上 bold -->
@click="toggle" <!-- 單擊事件,呼叫 toggle 函數 -->
@dblclick="changeType"> <!-- 雙擊事件,呼叫 changeType 函數 -->
{{ model.name }} <!-- 顯示當前項目的名稱 -->
<span v-if="isFolder"> <!-- 若是文件夾,顯示以下內容 -->
[{{ isOpen ? '-' : '+' }}] <!-- 根據 isOpen 狀態顯示 '-''+' -->
</span> <!-- 結束 span 標籤 -->
</div>
<ul v-show="isOpen" v-if="isFolder"> <!-- 若 isOpen 為 true 且是文件夾,顯示子項目 -->
<TreeItem
class="item" <!-- 給子項目加上 class -->
v-for="model in model.children" <!-- 遍歷子項目 -->
:model="model"> <!-- 傳遞每個子項目作為 model -->
</TreeItem> <!-- 結束 TreeItem 標籤 -->
<li class="add" @click="addChild">+</li> <!-- 顯示加號,點擊時呼叫 addChild 函數 -->
</ul> <!-- 結束 ul 標籤 -->
</li> <!-- 結束 li 標籤 -->
</template>

v-show:

  • 功能: 根據條件顯示或隱藏元素。
  • 使用情境: 在此範例中,v-show="isOpen" 用於控制 <ul> 標籤的顯示狀態。當 isOpentrue 時,<ul> 會顯示;當 isOpenfalse 時,會隱藏,但仍然保留在 DOM 中。

v-if:

  • 功能: 根據條件渲染或不渲染元素。
  • 使用情境: 在此範例中,v-if="isFolder" 用於判斷當前項目是否為文件夾。只有在 isFoldertrue 時,<ul> 標籤才會被渲染到 DOM 中。如果 isFolderfalse,則不會渲染該 <ul> 標籤。

v-if(在 span 標籤中):

    • 功能: 根據條件渲染或不渲染元素。
    • 使用情境: v-if="isFolder" 判斷當前項目是否為文件夾,只有在 isFoldertrue 時,該 <span> 標籤才會被渲染,顯示 +- 符號。

v-for:

  • 功能: 用於遍歷數組或對象,並為每個項目生成元素。
  • 使用情境: 在此範例中,v-for="model in model.children" 遍歷 model.children 陣列中的每個子項目,並為每個子項目生成一個 <TreeItem> 元素。

:class:

  • 功能: 動態綁定元素的 CSS class。
  • 使用情境: 在此範例中,:class="{ bold: isFolder }" 根據 isFolder 的值動態添加或移除 bold class。如果 isFoldertrue,則元素會加上 bold class。

@click@dblclick:

  • 功能: 綁定單擊和雙擊事件。
  • 使用情境:
    • @click="toggle":當用戶單擊 div 時,會呼叫 toggle 函數,切換 isOpen 的值。
    • @dblclick="changeType":當用戶雙擊 div 時,會呼叫 changeType 函數,根據當前項目的類型來進行相應的處理。
<TreeItem> 標籤在這個範例中確實是指遞回(recursive)渲染。這意味著 TreeItem 組件會在其自身的模板中調用自己,以便顯示樹形結構的每一層。這是一種常見的模式,用於處理樹形結構或類似的層級數據結構。

如何實現遞回:

在這個範例中,<TreeItem> 的使用方式如下:

<TreeItem
class="item"
v-for="model in model.children"
:model="model">
</TreeItem>
  • v-for="model in model.children":這一行表示對當前 TreeItemmodel.children 陣列進行遍歷,為每個子項目創建一個新的 TreeItem 實例。
  • :model="model":這一行將當前子項目(model)作為 model 屬性傳遞給新創建的 TreeItem 實例。

這種設計使得每個 TreeItem 都能夠渲染自己的子項目,從而形成一個多層次的樹形結構。比如,如果 model.children 中有子項目,這些子項目會被渲染為新的 TreeItem,每個子項目都可以再次包含自己的子項目,以此類推,形成樹形結構。

這樣的遞回渲染非常適合顯示如文件夾、目錄或任何層級結構的數據,並且在 Vue.js 中實現相對簡單,通過組件的重用來達到目的。








分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.