可以想像Tree View會有很多階層~
相當適合來搭建組件父子關係~
但還是有點無法想像怎麼實作!一起快來看看怎麼回事~
註解說明這個組件是一個嵌套樹狀結構,並且它能夠遞歸渲染自己。用戶可以雙擊一個項目將其轉換為文件夾。
<!--
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: [...]
:根節點的子項目是一個陣列,包含多個項目,每個項目都可以是一個葉子或一個具有子項的文件夾。<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 類,用於突出顯示特定的項目。<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
::class="{ bold: isFolder }"
根據 isFolder
的值動態添加或移除 bold
class。如果 isFolder
為 true
,則元素會加上 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"
:這一行表示對當前 TreeItem
的 model.children
陣列進行遍歷,為每個子項目創建一個新的 TreeItem
實例。:model="model"
:這一行將當前子項目(model
)作為 model
屬性傳遞給新創建的 TreeItem
實例。這種設計使得每個 TreeItem
都能夠渲染自己的子項目,從而形成一個多層次的樹形結構。比如,如果 model.children
中有子項目,這些子項目會被渲染為新的 TreeItem
,每個子項目都可以再次包含自己的子項目,以此類推,形成樹形結構。
這樣的遞回渲染非常適合顯示如文件夾、目錄或任何層級結構的數據,並且在 Vue.js 中實現相對簡單,通過組件的重用來達到目的。