Vue的遞迴元件:渲染巢狀評論

語言: CN / TW / HK

大多數現代社交網路都包括一個功能,使用者可以通過對該特定評論的評論來回複評論。如果我們將其視覺化,我們的評論的資料會像下面的結構:

- Comment A
                - comment a1
                                - comment a12
                - comment a2
- Comment B
- Comment C

Comment A​ 有子評論 comment a1​ 和 comment a2​。反過來,comment a1​ 有子評論comment a12 ,它也可以有自己的子評論。

有了這種結構,我們可以讓一個註釋有無數層的子註釋。你可能已經熟悉了這種結構化資料的方法,也就是所謂的樹狀結構。不理解的可以想想電腦上的目錄,一個資料夾可以有子資料夾等等。

這節課,我們來看看在Vue中如何使用遞迴元件來管理樹狀結構的資料。在介紹 Vue 中遞迴元件之前,我們先回顧一下什麼是遞迴。

什麼是遞迴

遞迴簡單的說就是自己呼叫自己,考慮下面這個函式:

function sum_numbers(arr, n) {
  return sum_numbers(arr, n - 1) + arr[n - 1];
}

雖然有些缺陷的,但上面的函式可以被認為是遞迴函式,因為它在函式中呼叫了自己。然而,這個定義並不包括所有的內容。遞迴是一種解決問題的方法。它基於這樣一個前提:給定一個問題,如果我們知道其子問題的解決方案,我們就可以找到其解決方案。

例如,上面的 sum_numbers​ 函式可以找到一個給定陣列 arr = [1, 2, 3, 4, 5]​ 中所有數字的總和。在求和問題中,如果我們知道5​之前的所有數字之和,那麼我們可以將問題簡化為arr中的數字之和等於最後一個元素和最後一個元素之前所有數字之和。

在上面定義的sum_numbers​函式中,表示式 return sum_numbers(arr, n - 1) + arr[n - 1]; 所做的正是我們剛才描述的。

為了 描繪 sum_numbers 函式在輸入 [1, 2, 3, 4] 的情況下如何從頭到尾執行,請看下面的程式碼:

**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 --** 這裡有一個問題

這裡有一個問:;我們的遞迴函式試圖將一個空列表新增到一個數字中。事實上,更大的問題是,我們的遞迴函式會一直無限地呼叫自己。

為了確保我們的遞迴函式不會無限地呼叫自己,我們需要一個基本情況。你可以把基數看作是我們希望我們的函式停止自我呼叫的點。

在上面例子中,如果sum_numbers函式中只有一個數字,它就應該停止呼叫自己。如果陣列中只剩下一個數字,那麼就沒有什麼可以與之相加的了,在這種情況下,我們只需返回這個數字。

function sum_numbers(arr, n) {
  if(n <= 1){ //Base Case
    return arr[0];
  } else {
   return sum_numbers(arr, n - 1) + arr[n - 1];
  }
}

從根本上說,這就是遞迴的意義,但與Vue的遞迴元件有什麼聯絡?

Vue 遞迴元件

Vue中的元件是可重用的Vue例項。大多數時候,當我們在Vue中建立一個元件時,只是為了能在其他地方重用它。例如,一個電子商務網站,我們可以在多個頁面上顯示產品。也可以有一個Product Component​ ,可以在不同的頁面上呈現,而不是在每個需要的頁面上重複 Product Component 的程式碼。

如果一個Vue元件在自己的模板中引用自己,那麼它就被認為是遞迴的。遞迴元件與普通元件不同。除了在其他地方被重用之外,遞迴元件還在其模板中引用自己。

為什麼一個元件會引用自己?當你在其他元件中渲染一個元件時,客體元件是子體,而渲染它的元件是父體。

在 Product Component​ 的例子中,該元件可以將 ProductReview 作為其子元件。在這種情況下,我們對這些元件所代表的實體有兩個不同的元件是有意義的,因為產品和評論在各方面都是不同的。

但是,如果我們以 Comment​  和 Sub-comment​ 為例,那麼就不一樣了。這兩個組成部分代表的是同一件事。一個子評論也是一個評論。因此,我們為 Comment​ 和 Sub-comment 設定兩個不同的元件是沒有意義的,因為它們在結構上是一樣的。我們可以只有一個引用自己的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>

雖然,但上面的元件可以被認為是遞迴的,因為它引用了自己。和遞迴函式一樣,遞迴元件也必須有一個終止條件,而上面的程式碼中缺少這個終止條件。這裡需要注意的另一件重要的事情是,為了使一個元件能夠引用自己,必須定義 name 選項。

現在明白了什麼是Vue中的遞迴元件,接著,來看看如何使用它來構建一個巢狀的評論介面。

構建評論介面

設定Vue開發環境

首先,初始化一個新的Vue專案,在終端執行 vue create nested-comments命令:

vue create nested-comments

根據提示安裝後,會得到如下的目錄結構:

使用 vue serve 把專案跑起來。

用遞迴元件來渲染巢狀的評論

為了將 巢狀評論渲染到DOM,首先,刪除src/views和src/components​中的所有檔案。然後,建立 src/components/Comment.vue,script 內容如下:

<script>
export default {
  name: "recursive-comment",
  props: {
    comment: {
      type: String,
      required: true,
    },
    replies: {
      type: Array,
      default: () => [],
    },
  },
};
</script>

在上面的程式碼片斷中,將的元件命名為遞迴元件(recursive-component​)。記住,在Vue中,一個遞迴元件必須有一個宣告的 name​ 。此外,我們的元件希望在它被引用的任何地方都能將comment​  和 replies​ 的 props 傳遞給它。

接著,template 內容如下:

<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" 是終於遞迴的條件,一旦條件不成立,則停止遞迴。

接下來,在 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>

執行,效果如下所示:

總結

雖然我們舉的例子不是一個典型的評論元件,但我們的目標是探索如何利用Vue中遞迴元件的力量來渲染巢狀資料。

我們看到,我們可以通過建立一個在自己的模板中引用自己的元件來做到這一點。這種遞迴方法在渲染那些看似不同但結構相同的資料實體時特別有用。例如,以我們的 comments​  和 replies 為例。

乍一看,我們好像需要兩個元件,一個用於comments​ ,另一個用於 replies。但是,用遞迴的方法,我們能夠用一個元件來渲染這兩種內容。最重要的是,我們的元件會渲染所有的評論和回覆,直到它達到終止條件。