TreeViewコンポーネント(Vuetify)の「Async items」機能について

# 背景

Vuetifyの公式サイトを見ていて、treeviewコンポーネントに「Async items」という機能があるのを見つけたので試してみた。
https://vuetifyjs.com/ja/components/treeview/

簡単に説明すると、ツリーのノードを開いた瞬間に子要素のデータを取得しに行く機能。
まぁ、ツリーのコンポーネントなら普通は持ってるよねって感じの機能ですが、使ってみると色々見えてきたりするので。。。

あと、今回はローカルファイルからのデータ読み込みをやるので、ついでに、VS CodeのLive Serverを試してみようと思う(実はこっちが本当の目的だったりする)。

# ツリーコンポーネントへの「Async items」の適用

以前作成したWebComponents用ツリーコンポーネントのサンプルは、コード内にデータをべた書きしていたので、これを他からデータを取得してくるように変えてみる。

## 準備

以前作成したWebComponents用ツリーコンポーネントのコードを落としてくる。

```console
> git clone https://github.com/yusuke-ka/sample-vce-vuetify.git
```

インストール。

```console
> cd sample-vce-vuetify
> yarn install
```

元のコードは下のようになっていて、データ(items)は固定になっている。

```tree.vue
<template>
  <div id="app">
    <v-app id="inspire">
      <v-treeview selectable :items="items"></v-treeview>
    </v-app>
  </div>
</template>

<script>
...
export default {
  el: "#app",
  vuetify: new Vuetify({
    icons: {
      iconfont: "md",
    },
  }),
  data: () => ({
    items: [
      {
        id: 1,
        name: "Applications :",
        children: [
          { id: 2, name: "Calendar : app" },
          { id: 3, name: "Chrome : app" },
          { id: 4, name: "Webstorm : app" },
        ],
      },
      ....
    ],
  }),
};
</script>
```

**yarn build**でビルドすると、ツリーコンポーネントがWebComponents化されてbundle.jsが生成されて、他から呼び出せるようになる。

プロジェクト直下に下記のようなindex.htmlを置くと、ローカルでの動作確認ができる。

```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Custom Element By Vue + Vuetify</title>
    <script src="dist/bundle.js"></script>
  </head>
  <body>
    <vce-tree></vce-tree>
  </body>
</html>
```

こんな感じ↓

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643184/b4cbf1cf-08ee-6aa4-6cc7-a21b71547b1b.png)


これを、ノードを開くタイミングで子要素を取得してくるように変更してみる。

## コード修正

まず、*template*部分に*:load-children*を追加。メソッド(算出プロパティ)名は「fetchData」にしておく。
また、メソッド名が紛らわしいので、「:items="items"」は「:items="itemList"」に変更しておく。

```tree.vue
<template>
  <div id="app">
    <v-app id="inspire">
      <v-treeview
        selectable
        :items="itemList"
        :load-children="fetchData"
      ></v-treeview>
    </v-app>
  </div>
</template>
```

*script*部分は、*data*の*items*は空にしておき、*computed*で*itemList()*を定義する。
さらに、*methods*で*fetchData()*を定義。ここでデータを取得する。
普通はバックエンドのサーバーからデータを取得するんだけど、今回はローカルファイルを指定。
あと、外部からデータを取得しているのを分かりやすくするために、最初にpauseを定義して、意図的に待ち時間を入れている。

```tree.vue
<script>
...

const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

export default {
  ...
  data: () => ({
    items: [],
  }),
  computed: {
    itemList() {
      return [
        {
          name: "ItemList",
          children: this.items,
        },
      ];
    },
  },
  methods: {
    async fetchData(item) {
      await pause(1000); // 意図的に待ち時間を発生させる
      return fetch("./data.json")
        .then((res) => res.json())
        .then((json) => item.children.push(...json))
        .catch((err) => console.warn(err));
    },
  },
};
</script>

```

ローカルに置いたデータファイル(data.json)はこちら。

```data.json
[
  {
    "id": 1,
    "name": "ChildA :",
    "children": [
      {
        "id": 4,
        "name": "ChildA-1 :"
      }
    ]
  },
  {
    "id": 2,
    "name": "ChildB :",
    "children": []
  },
  {
    "id": 3,
    "name": "ChildC :"
  }
]
```

*vuetify*の*treeview*では、「children」を空にしてやると、そのノードを開いた際に「:load-children」に定義したメソッドが実行されるらしいので、上のデータでは「ChildB」だけその状態にしている。

## 動作確認

このままビルドして、index.htmlを再表示してみる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643184/c80db079-db00-8a14-f447-6a71aeea6ca4.png)

ローカルファイルを読み込んでいるので、最近のブラウザだとCORSのエラーが発生する。

ということで、予定通りVS CodeにLive Serverを入れて対応する。

## Live Serverの導入

VS CodeのExtensions(MARKETPLACE)で、「Live Server」で検索

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643184/4ec41cbf-e9bf-d982-bdae-68066af03df6.png)

Live ServerのExtensionが表示されるので、「Install」ボタンからインストールする。

導入完了。

実行も簡単。
index.htmlをVS Code上で開いた状態で、右下にある「Go Live」を押す。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643184/902421a7-96e8-4040-3b59-3d043cab08a9.png)

自動的にブラウザが立ち上がり、index.htmlが表示される。

## 動作確認(再)

初期表示では、「itemList()」の実行結果として、ルートノードが表示される。

ノードを開くと、fetchData(item)が実行されて、「data.json」に書いたデータが表示される。


![tree.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643184/0470a43b-9d0f-3d8f-aaf5-054ce08173b2.gif)


続いて取得したデータの子要素を開いてみる。

![tree.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643184/6c7e9c4b-9efa-8803-ecac-316432f915ac.gif)

ChildAの子要素「ChildA-1」は「data.json」に定義されているので、最初のデータ取得で一緒に取得され、即座に展開される。

ChildBの子要素は「data.json」には空で定義されているので、展開しようとした際にfetchData(item)が実行されて再度取得される。
(※本来は引数のitemで指定したノードの子要素を取りに行くべきだが、今回はitemを無視して「data.json」を読むので最初と全く同じデータが展開される。)

ChildCは「data.json」で「children」を指定していないので、そもそもノードが展開できない。

ということで、treeviewコンポーネントで動的に子要素を取得するように変更できました。

# さいごに

ローカルで色々書いていると、CORSのエラーにはよく遭遇する。
今までは、わざわざS3でホスティングして確認していたので結構面倒だったが、Live Serverを使うことでだいぶ楽になることが分かった。

*treeview*コンポーネントへの「Async items」導入については、「順次ロードさせるためには*children*を空にして返してやる必要がある」ってのが分かったことが収穫かな。(※まぁ公式サイトに書いてあるんですが。)