EP32 - ex4. Tree View

閱讀時間約 18 分鐘
可以想像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 中實現相對簡單,通過組件的重用來達到目的。








avatar-img
2會員
70內容數
分享生活趣事~
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
卡關的人生 的其他內容
在 Vue 3 中使用計算屬性來實現表格的排序和篩選功能。通過創建一個可重用的表格組件(DemoGrid),可以輕鬆管理和顯示外部數據。
這段代碼示範了如何從 GitHub 的 API 抓取最新的 Vue.js 提交數據並顯示在網頁上。這是後端 API 串接的一個範例,展示了如何動態地獲取和顯示數據。
看官方文件好累,終於要來看實用範例摟!這篇要做一個Markdown編輯器,真的假的?!
使用 defineAsyncComponent 函數可實現此功能,它接受一個返回 Promise 的加載函數。在大型應用中,組件可以按需加載,並且可與 ES 模塊的動態導入結合使用。還可以使用高級選項處理加載和錯誤狀態,例如設置加載組件和超時設定。
在 Vue 中,當需要將數據從父組件傳遞到深層嵌套的子組件時,使用 props 會導致屬性過度傳遞 (Prop Drilling),這樣即使某些中間組件不需要這些數據,也必須聲明並傳遞它們。這種情況會使代碼難以維護。Provide 和 Inject 是解決這個問題的工具。
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
在 Vue 3 中使用計算屬性來實現表格的排序和篩選功能。通過創建一個可重用的表格組件(DemoGrid),可以輕鬆管理和顯示外部數據。
這段代碼示範了如何從 GitHub 的 API 抓取最新的 Vue.js 提交數據並顯示在網頁上。這是後端 API 串接的一個範例,展示了如何動態地獲取和顯示數據。
看官方文件好累,終於要來看實用範例摟!這篇要做一個Markdown編輯器,真的假的?!
使用 defineAsyncComponent 函數可實現此功能,它接受一個返回 Promise 的加載函數。在大型應用中,組件可以按需加載,並且可與 ES 模塊的動態導入結合使用。還可以使用高級選項處理加載和錯誤狀態,例如設置加載組件和超時設定。
在 Vue 中,當需要將數據從父組件傳遞到深層嵌套的子組件時,使用 props 會導致屬性過度傳遞 (Prop Drilling),這樣即使某些中間組件不需要這些數據,也必須聲明並傳遞它們。這種情況會使代碼難以維護。Provide 和 Inject 是解決這個問題的工具。
通過使用插槽,組件可以變得更加靈活和可重用,例如 <FancyButton> 可以讓父組件提供按鈕的內部內容,而 <BaseLayout> 則可以使用命名插槽來指定不同區域的內容。插槽還可以設置預設內容,當父組件未提供插槽內容時,會渲染預設內容。
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
CSS 盒模型是理解和設計網頁佈局的核心概念。它包括元素的內容、填充、邊框和外邊距。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
VIPER(View Interactor Presenter Entities Router) View 負責顯示資料。 Interactor 負責管理model。 Presenter 負責處理View的業務邏輯。 Entities 負責data model。
MVVM(Model View ViewModel),特點是View跟ViewModel之間做資料綁定。 Model 負責儲存應用程式的資料。 View 負責顯示資料。 ViewModel 負責處理View和Model之間的狀態關係。
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
上一篇文章提到有些介面不應被繼承,但物件導向的子類別只能繼承父類別的介面,因而產生一些不合適的介面實作。trait/typeclass則沒有這種繼承機制,這似乎使需要繼承的特性無法直接使用。然而函數式導向比起繼承,更適合使用組合,根本不需要使用繼承疊加特性。 資料類型的定義往往跟怎麼建構模型相
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
11/20日NVDA即將公布最新一期的財報, 今天Sell Side的分析師, 開始調高目標價, 市場的股價也開始反應, 未來一週NVDA將重新回到美股市場的焦點, 今天我們要分析NVDA Sell Side怎麼看待這次NVDA的財報預測, 以及實際上Buy Side的倉位及操作, 從
Thumbnail
Hi 大家好,我是Ethan😊 相近大家都知道保濕是皮膚保養中最基本,也是最重要的一步。無論是在畫室裡長時間對著畫布,還是在旅途中面對各種氣候變化,保持皮膚的水分平衡對我來說至關重要。保濕化妝水不僅能迅速為皮膚補水,還能提升後續保養品的吸收效率。 曾經,我的保養程序簡單到只包括清潔和隨意上乳液
Thumbnail
CSS 盒模型是理解和設計網頁佈局的核心概念。它包括元素的內容、填充、邊框和外邊距。
Thumbnail
各位使用 Vue.js 開發的小夥伴們,你們都怎麼實作父子層組件資料的雙向綁定呢?如果你還在寫 prop + emit 的話,不妨進來看看吧。
Thumbnail
切換頁面卡卡有很多種原因,這裡舉的例子只針對元件太大的情境。 除了想辦法拆分外,還有一個方法就是利用 Vue 的 Async Component。
Thumbnail
※ 視圖模板 視圖模板(View Templates) 是在 MVC 架構中負責展示數據的 HTML 文件,包含模板語法,用於在渲染時插入實際數據。它們的主要目的是分離數據與展示邏輯,讓代碼更加模塊化和易於維護。 視圖模板設計和使用的核心理念,就是「重複的事情不要重複做、效益最大化、有效利用資源
VIPER(View Interactor Presenter Entities Router) View 負責顯示資料。 Interactor 負責管理model。 Presenter 負責處理View的業務邏輯。 Entities 負責data model。
MVVM(Model View ViewModel),特點是View跟ViewModel之間做資料綁定。 Model 負責儲存應用程式的資料。 View 負責顯示資料。 ViewModel 負責處理View和Model之間的狀態關係。
Thumbnail
平常我們在 html 上常看到的例如 v-for、v-model 等等... 也是VUE已經幫我們定義好的指令,而這次我們可以依這自己的需求來建立。 此功能屬於較進階的功能,因此實戰中會比較少見,市面上還是有不少完善的套件能達到同樣效果,建議可以先往這方面察找
Thumbnail
我們在實作中,難免會遇到在不同組件中,卻有需求相同的資料格式,因此 mixins 可以達到我們的需求,除了 data 以外也包含了 methods 可以共用,舉例來說,學生資料可能會在,班級跟社團內被使用,當我們要撰寫元件時,就可以省略多餘的 data 定義。
上一篇文章提到有些介面不應被繼承,但物件導向的子類別只能繼承父類別的介面,因而產生一些不合適的介面實作。trait/typeclass則沒有這種繼承機制,這似乎使需要繼承的特性無法直接使用。然而函數式導向比起繼承,更適合使用組合,根本不需要使用繼承疊加特性。 資料類型的定義往往跟怎麼建構模型相