Vueのレンダーレススロットパターンを調べて、解決に役立つ問題を見てみましょう。

Vue.js 2.3.0で導入されたスコープ付きスロットでは、コンポーネントの再利用性が大幅に向上しています。 たとえば、レンダリングのないコンポーネントパターンが出現し、再利用可能な動作とプラグ可能なプレゼンテーションを提供するという問題が解決されました。

ここでは、反対の問題を解決する方法を説明します。再利用可能なプレゼンテーションとプラグ可能な動作を提供します。

レンダーレスコンポーネント

このパターンは、複雑な動作を実装し、カスタマイズ可能な表示を持つコンポーネントに適用されます。

そうするために:

  1. コンポーネントはすべての動作を実装します
  2. スコープスロットはレンダリングを担当します
  3. フォールバックコンテンツにより、コンポーネントをそのまま使用できるようになります。

例を見てみましょう。Ajaxリクエストを実行し、結果を表示するためのスロットを持つコンポーネントです。 コンポーネントはAjaxリクエストとロード状態を処理し、デフォルトのスロットはプレゼンテーションを提供します。

ここに簡略化された実装があります:

<template>
  <div>
    <slot v-if="loading" name="loading">
        <div>Loading ...</div>
    </slot>
    <slot v-else v-bind={data}>
    </slot>
  </div>
</template>

<script>
export default {
  props: ["url"],
  data: () => ({
    loading: true,
    data: null
  }),
  async created() {
    this.data = await fetch(this.url);
    this.loading = false;
  }
};
</script>

使用法:

<lazy-loading url="https://server/api/data">
  <template #default="{ data }">
    <div>{{ data }}</div>
  </template>
</lazy-loading>

このパターンに関する元の投稿については、こちらを確認してください。

別の問題

問題が逆の場合はどうなりますか。コンポーネントの主な機能がそのプレゼンテーションであると想像してください。 一方、動作はカスタマイズ可能である必要があります。

次のように、SVGに基づいてツリーコンポーネントを作成していると想像してください。

tree component

SVGの表示と、ノードの撤回やクリック時のテキストの強調表示などの動作を提供する必要があります。

これらの動作をハードコーディングせず、コンポーネントのユーザーが自由にオーバーライドできるようにすると、問題が発生します。

これらの動作を公開する簡単な解決策は、コンポーネントにメソッドとイベントを追加することです。

最終的には次のようになります。

<script>
export default {
  mounted() {
    // pseudo code
    nodes.on('click',(node) => this.$emit('click', node));
  },
  methods: {
    expandNode(node) {
      //...
    },
    retractNode(node) {
      //...
    },
    highlightText(node) {
      //...
    },
  }
};
</script>

コンポーネントに動作を追加するには、コンポーネントのコンシューマーは、次のように、親コンポーネントでrefを使用する必要があります。

<template>
  <tree ref="tree" @click="onClick"></tree>
</template>

<script>
export default {
  methods: {
    onClick(node) {
      this.$refs.tree.retractNode(node);
    }
  }
};
</script>

このアプローチにはいくつかの欠点があります。

  1. デフォルトの動作を提供することはできなくなりました
  2. 動作は、複製する必要のあるクックブックになります
  3. 動作は再利用できません

レンダーレススロットがこれらの問題をどのように解決できるか見てみましょう。

レンダーレススロット

行動は基本的に、イベントへの反応を証明することで構成されます。 それでは、イベントとコンポーネントメソッドへのアクセスを受け取るスロットを作成しましょう。

<template>
  <div>
    <slot name="behavior" :on="on" :actions="actions">
    </slot>
  </div>
</template>

<script>
export default {
  methods: {
    expandNode(node) { },
    retractNode(node) { },
   //...
  },
  computed:{
    actions() {
      const {expandNode, retractNode} = this;
      return {expandNode, retractNode};
    },
    on() {
      return this.$on.bind(this);
    }
  }
};
</script>

on属性は、親コンポーネントの$onメソッドであるため、すべてのイベントをリッスンすることができます。

ビヘイビアの実装は、レンダーレスコンポーネントとして実行できます。 クリックで展開するコンポーネントを書いてみましょう。

export default {
  props: ['on','action']

  render: () => null,

  created() {
    this.on("click", (node) => {
      this.actions.expandNode(node);
    });
  }
};

使用法:

<tree>
  <template #behavior="{ on, actions }">
    <expand-on-click v-bind="{ on, actions }"/>
  </template>
</tree>

このソリューションの主な利点は次のとおりです。

  • フォールバックコンテンツを提供することにより、デフォルトの動作を提供する可能性:

たとえば、グラフコンポーネントを次のように宣言します。

<template>
  <div>
    <slot name="behavior" :on="on" :actions="actions">
      <expand-on-click v-bind="{ on, actions }"/>
    </slot>
  </div>
</template>
  • コンポーネントのユーザーが選択できる標準的な動作を実装する再利用可能なコンポーネントを作成する可能性

ホバー時のハイライトコンポーネントについて考えてみましょう。

export default {
  props: ['on','action']

  render: () => null,

  created() {
    this.on("hover", (node) => {
      this.actions.highlight(node);
    });
  }
};

標準の動作をオーバーライドする:

<tree>
  <template #behavior="{ on, actions }">
    <highlight-on-hover v-bind="{ on, actions }"/>
  </template>
</tree>
  • 動作スロットは構成可能です

2つの事前定義された動作を追加しましょう:

<tree>
  <template #behavior="{ on, actions }">
    <expand-on-click v-bind="{ on, actions }"/>
    <highlight-on-hover v-bind="{ on, actions }"/>
  </template>
</tree>
  • ソリューションの読みやすさ

動作としてのコンポーネントは自明です。

  • 拡張性

on属性は、すべてのコンポーネントイベントへのアクセスを提供します。 スロットでは、デフォルトで新しいイベントを利用できます。

まとめ

レンダーレススロットは、コンポーネント内のメソッドとイベントを公開するための興味深い代替手段を提供します。 それらは、より読みやすく再利用可能なコードを提供します。

このパターンを実装するツリーコンポーネントのコードは、githubで入手できます: Vue.D3.tree