可以想像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 中導入ref和computed函數。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),則:- 將
model的children設置為空陣列,準備為其添加子項。 - 調用
addChild()函數添加一個新的子項。 - 將
isOpen設置為true,展開該項目。
- 將
function addChild() {
props.model.children.push({ name: 'new stuff' })
}
function addChild() {...}:定義一個函數addChild,該函數向model的children陣列中推入一個新的物件,這個物件的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>標籤的顯示狀態。當isOpen為true時,<ul>會顯示;當isOpen為false時,會隱藏,但仍然保留在 DOM 中。
v-if:
- 功能: 根據條件渲染或不渲染元素。
- 使用情境: 在此範例中,
v-if="isFolder"用於判斷當前項目是否為文件夾。只有在isFolder為true時,<ul>標籤才會被渲染到 DOM 中。如果isFolder為false,則不會渲染該<ul>標籤。
v-if(在 span 標籤中):
- 功能: 根據條件渲染或不渲染元素。
- 使用情境:
v-if="isFolder"判斷當前項目是否為文件夾,只有在isFolder為true時,該<span>標籤才會被渲染,顯示+或-符號。
v-for:
- 功能: 用於遍歷數組或對象,並為每個項目生成元素。
- 使用情境: 在此範例中,
v-for="model in model.children"遍歷model.children陣列中的每個子項目,並為每個子項目生成一個<TreeItem>元素。
:class:
- 功能: 動態綁定元素的 CSS class。
- 使用情境: 在此範例中,
:class="{ bold: isFolder }"根據isFolder的值動態添加或移除boldclass。如果isFolder為true,則元素會加上boldclass。
@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":這一行表示對當前TreeItem的model.children陣列進行遍歷,為每個子項目創建一個新的TreeItem實例。:model="model":這一行將當前子項目(model)作為model屬性傳遞給新創建的TreeItem實例。
這種設計使得每個 TreeItem 都能夠渲染自己的子項目,從而形成一個多層次的樹形結構。比如,如果 model.children 中有子項目,這些子項目會被渲染為新的 TreeItem,每個子項目都可以再次包含自己的子項目,以此類推,形成樹形結構。
這樣的遞回渲染非常適合顯示如文件夾、目錄或任何層級結構的數據,並且在 Vue.js 中實現相對簡單,通過組件的重用來達到目的。















