Vue的遞歸組件:渲染嵌套評(píng)論
大多數(shù)現(xiàn)代社交網(wǎng)絡(luò)都包括一個(gè)功能,用戶可以通過(guò)對(duì)該特定評(píng)論的評(píng)論來(lái)回復(fù)評(píng)論。如果我們將其可視化,我們的評(píng)論的數(shù)據(jù)會(huì)像下面的結(jié)構(gòu):
- Comment A
- comment a1
- comment a12
- comment a2
- Comment B
- Comment C
Comment A? 有子評(píng)論 comment a1? 和 comment a2?。反過(guò)來(lái),comment a1? 有子評(píng)論comment a12 ,它也可以有自己的子評(píng)論。
有了這種結(jié)構(gòu),我們可以讓一個(gè)注釋有無(wú)數(shù)層的子注釋。你可能已經(jīng)熟悉了這種結(jié)構(gòu)化數(shù)據(jù)的方法,也就是所謂的樹狀結(jié)構(gòu)。不理解的可以想想電腦上的目錄,一個(gè)文件夾可以有子文件夾等等。
這節(jié)課,我們來(lái)看看在Vue中如何使用遞歸組件來(lái)管理樹狀結(jié)構(gòu)的數(shù)據(jù)。在介紹 Vue 中遞歸組件之前,我們先回顧一下什么是遞歸。
什么是遞歸
遞歸簡(jiǎn)單的說(shuō)就是自己調(diào)用自己,考慮下面這個(gè)函數(shù):
function sum_numbers(arr, n) {
return sum_numbers(arr, n - 1) + arr[n - 1];
}
雖然有些缺陷的,但上面的函數(shù)可以被認(rèn)為是遞歸函數(shù),因?yàn)樗诤瘮?shù)中調(diào)用了自己。然而,這個(gè)定義并不包括所有的內(nèi)容。遞歸是一種解決問(wèn)題的方法。它基于這樣一個(gè)前提:給定一個(gè)問(wèn)題,如果我們知道其子問(wèn)題的解決方案,我們就可以找到其解決方案。
例如,上面的 sum_numbers? 函數(shù)可以找到一個(gè)給定數(shù)組 arr = [1, 2, 3, 4, 5]? 中所有數(shù)字的總和。在求和問(wèn)題中,如果我們知道5?之前的所有數(shù)字之和,那么我們可以將問(wèn)題簡(jiǎn)化為arr中的數(shù)字之和等于最后一個(gè)元素和最后一個(gè)元素之前所有數(shù)字之和。
在上面定義的sum_numbers?函數(shù)中,表達(dá)式 return sum_numbers(arr, n - 1) + arr[n - 1]; 所做的正是我們剛才描述的。
為了 描繪 sum_numbers 函數(shù)在輸入 [1, 2, 3, 4] 的情況下如何從頭到尾執(zhí)行,請(qǐng)看下面的代碼:
**sum_numbers([1, 2, 3, 4], 4)
|
calls
|**
**sum_numbers([1, 2, 3], 3) + 4
|
calls
|
sum_numbers([1, 2], 2) + 3
|
calls
|
sum_numbers([1], 1) + 2
|
calls
|
sum_numbers([], 0) + 1 --** 這里有一個(gè)問(wèn)題
這里有一個(gè)問(wèn):;我們的遞歸函數(shù)試圖將一個(gè)空列表添加到一個(gè)數(shù)字中。事實(shí)上,更大的問(wèn)題是,我們的遞歸函數(shù)會(huì)一直無(wú)限地調(diào)用自己。
為了確保我們的遞歸函數(shù)不會(huì)無(wú)限地調(diào)用自己,我們需要一個(gè)基本情況。你可以把基數(shù)看作是我們希望我們的函數(shù)停止自我調(diào)用的點(diǎn)。
在上面例子中,如果sum_numbers函數(shù)中只有一個(gè)數(shù)字,它就應(yīng)該停止調(diào)用自己。如果數(shù)組中只剩下一個(gè)數(shù)字,那么就沒(méi)有什么可以與之相加的了,在這種情況下,我們只需返回這個(gè)數(shù)字。
function sum_numbers(arr, n) {
if(n <= 1){ //Base Case
return arr[0];
} else {
return sum_numbers(arr, n - 1) + arr[n - 1];
}
}
從根本上說(shuō),這就是遞歸的意義,但與Vue的遞歸組件有什么聯(lián)系?
Vue 遞歸組件
Vue中的組件是可重用的Vue實(shí)例。大多數(shù)時(shí)候,當(dāng)我們?cè)赩ue中創(chuàng)建一個(gè)組件時(shí),只是為了能在其他地方重用它。例如,一個(gè)電子商務(wù)網(wǎng)站,我們可以在多個(gè)頁(yè)面上顯示產(chǎn)品。也可以有一個(gè)Product Component? ,可以在不同的頁(yè)面上呈現(xiàn),而不是在每個(gè)需要的頁(yè)面上重復(fù) Product Component 的代碼。
如果一個(gè)Vue組件在自己的模板中引用自己,那么它就被認(rèn)為是遞歸的。遞歸組件與普通組件不同。除了在其他地方被重用之外,遞歸組件還在其模板中引用自己。
為什么一個(gè)組件會(huì)引用自己?當(dāng)你在其他組件中渲染一個(gè)組件時(shí),客體組件是子體,而渲染它的組件是父體。
在 Product Component? 的例子中,該組件可以將 ProductReview 作為其子組件。在這種情況下,我們對(duì)這些組件所代表的實(shí)體有兩個(gè)不同的組件是有意義的,因?yàn)楫a(chǎn)品和評(píng)論在各方面都是不同的。
但是,如果我們以 Comment? 和 Sub-comment? 為例,那么就不一樣了。這兩個(gè)組成部分代表的是同一件事。一個(gè)子評(píng)論也是一個(gè)評(píng)論。因此,我們?yōu)?nbsp;Comment? 和 Sub-comment 設(shè)置兩個(gè)不同的組件是沒(méi)有意義的,因?yàn)樗鼈冊(cè)诮Y(jié)構(gòu)上是一樣的。我們可以只有一個(gè)引用自己的Comment 組件。還是太抽象了?看下面的片段:
<template>
<li class="comment">
<span>{{ comment.comment }}</span>
<comment v-for="reply in comment.replies" :comment="reply"></comment>
</li>
</template>
<script>
export default {
name: "comment",
props: {
comment: Object
}
};
</script>
雖然,但上面的組件可以被認(rèn)為是遞歸的,因?yàn)樗昧俗约骸:瓦f歸函數(shù)一樣,遞歸組件也必須有一個(gè)終止條件,而上面的代碼中缺少這個(gè)終止條件。這里需要注意的另一件重要的事情是,為了使一個(gè)組件能夠引用自己,必須定義 name 選項(xiàng)。
現(xiàn)在明白了什么是Vue中的遞歸組件,接著,來(lái)看看如何使用它來(lái)構(gòu)建一個(gè)嵌套的評(píng)論界面。
構(gòu)建評(píng)論界面
設(shè)置Vue開發(fā)環(huán)境
首先,初始化一個(gè)新的Vue項(xiàng)目,在終端運(yùn)行 vue create nested-comments命令:
vue create nested-comments
根據(jù)提示安裝后,會(huì)得到如下的目錄結(jié)構(gòu):
使用 vue serve 把項(xiàng)目跑起來(lái)。
用遞歸組件來(lái)渲染嵌套的評(píng)論
為了將 嵌套評(píng)論渲染到DOM,首先,刪除src/views和src/components?中的所有文件。然后,創(chuàng)建 src/components/Comment.vue,script 內(nèi)容如下:
<script>
export default {
name: "recursive-comment",
props: {
comment: {
type: String,
required: true,
},
replies: {
type: Array,
default: () => [],
},
},
};
</script>
在上面的代碼片斷中,將的組件命名為遞歸組件(recursive-component?)。記住,在Vue中,一個(gè)遞歸組件必須有一個(gè)聲明的 name? 。此外,我們的組件希望在它被引用的任何地方都能將comment? 和 replies? 的 props 傳遞給它。
接著,template 內(nèi)容如下:
<template>
<li>
<span class="comment">{{ comment }}</span>
<ul class="replies" v-if="replies.length">
<div v-for="(item, index) in replies" :key="index">
<recursive-comment
v-bind="{
comment: item.comment,
replies: item.replies,
}"
/>
</div>
</ul>
</li>
</template>
recursive-comment? 組件在自己的模板中引用自己。v-if="replies.length" 是終于遞歸的條件,一旦條件不成立,則停止遞歸。
接下來(lái),在 App.vue 引用一下就行啦:
<template>
<ul v-for="(item, index) in comments" :key="index" class="comments">
<Comment
v-bind="{
comment: item.comment,
replies: item.replies,
}"
/>
</ul>
</template>
<script>
import Comment from "@/components/Comment";
export default {
data: () => ({
comments: [
{
comment: "First comment",
replies: [
{
comment: "sub-comment 1 for comment 1",
replies: [
{
comment: "sub-sub-comment 1",
replies: [
{
comment: "sub-sub-sub-comment 1",
},
{ comment: "sub-sub-sub-comment 2" },
],
},
{ comment: "sub-sub-comment 2" },
],
},
{ comment: "sub-comment 2 for comment 1" },
],
},
{
comment: "Second comment",
replies: [
{
comment: "sub-comment 1 for comment 2",
replies: [
{ comment: "sub-sub-comment 1" },
{ comment: "sub-sub-comment 2" },
],
},
{ comment: "sub-comment 2 for comment 2" },
],
},
],
}),
components: {
Comment,
},
};
</script>
<style>
.comments ul {
padding-left: 16px;
margin: 6px 0;
}
</style>
運(yùn)行,效果如下所示:
總結(jié)
雖然我們舉的例子不是一個(gè)典型的評(píng)論組件,但我們的目標(biāo)是探索如何利用Vue中遞歸組件的力量來(lái)渲染嵌套數(shù)據(jù)。
我們看到,我們可以通過(guò)創(chuàng)建一個(gè)在自己的模板中引用自己的組件來(lái)做到這一點(diǎn)。這種遞歸方法在渲染那些看似不同但結(jié)構(gòu)相同的數(shù)據(jù)實(shí)體時(shí)特別有用。例如,以我們的 comments? 和 replies 為例。
乍一看,我們好像需要兩個(gè)組件,一個(gè)用于comments? ,另一個(gè)用于 replies。但是,用遞歸的方法,我們能夠用一個(gè)組件來(lái)渲染這兩種內(nèi)容。最重要的是,我們的組件會(huì)渲染所有的評(píng)論和回復(fù),直到它達(dá)到終止條件。