Help us understand the problem. What is going on with this article?

Vue.jsにおけるRender PropとScoped Slotsについて

More than 1 year has passed since last update.

こんにちは。転職によりReact畑からVue畑に乗り換えることになったフロントエンドエンジニアです。

Vueでも描画関数使ってればRender Propsも使えるじゃんと思ったところ、Vue.js 作者のEvan You曰く「Render PropパターンはVue.jsにおけるScoped Slotsと同じ」とのこと。

本当にそうなのか簡単な実装例を用意してみました。

Render Prop

まず、そもそものRender Propを使う目的としては
「コードの再利用性を高めるための実装パターンで、複数のコンポーネントに適用したい汎用的な振る舞いを抽出すること」であると捉えています。

少し前にRender Prop使えばHOCいらんくね?という記事が話題になりましたが、HOCみたいにあるコンポーネントに特定の振る舞いを追加するのが目的と考えるとイメージしやすいと思います。

パターンの詳細についてですが、実装を見るのが一番早いと思うので、下記にVue.jsでの実装例を紹介したいと思います。
今回は例として、ありがちな「選択された要素だけスタイルを変える」という振る舞いを実装してみます。

App.vue
//「選択された要素だけスタイルを変える」振る舞いを持つコンポーネントです。
//このコンポーネントでは `render` propに渡された関数を実行することによって描画します。
const Selected = {
  props: {
    render: {
      default: h => null
    }
  },
  data() {
    return {
      selectedVal: 0
    };
  },
  methods: {
    select(value) {
      this.selectedVal = value;
    }
  },
  render() {
    return this.$props.render({
      selectedVal: this.selectedVal,
      select: this.select
    });
  }
};

export default {
  functional: true,
  render: (h, { props }) => (
    <div>
      <Selected
        render={({ selectedVal, select }) => (
          <div>
            <input
              type="number"
              onChange={event => select(event.target.value)}
              value={selectedVal}
            />
            <ul>
              {props.items.map((item, i) => (
                /* ここで選択されたものだけスタイルを変えています */
                <li class={selectedVal == i ? "selected-item" : ""} onClick={event => select(i)}> {{ item }} </li>
              ))}
            </ul>
          </div>
        )}
      />
    </div>
  )
};

初見だと分かりづらいかもな、と思うのがこの Selected コンポーネントは自身のViewを持たないということです。render関数内でpropsとして渡されたrenderメソッドを呼び出しているだけで、自身はロジックしか持っていません。もはやこれを"コンポーネント"と呼ぶのが正しいのかすら微妙ですが、パターンとしてはそういう感じなので、今までのコンポーネントに対するメンタルモデルを少し変えてみましょう。

以上が実装例です。Selected コンポーネントのrender propをいじるだけで簡単に「選択された要素だけスタイルを変える」振る舞いを様々なコンポーネントに適用できるようになりました。

続いてScoped Slotを用いて同じ機能を実装してみたいと思います。

Scoped Slot

Slotとは何ぞやという話はドキュメントに譲るとして、Scoped Slotの何が普通のSlotと違うかと言いますと、その名の通り「Slotの配信先のコンポーネントのスコープのデータにアクセスすることができる」という点ですね。
こちらも実装して見たのでご覧ください!

Selected.vue
<template>
  <div>
    <slot name="selected" :selectedVal="selectedVal" :select="select">
      デフォルトの内容。
    </slot>
  </div>
</template>

<script>
export default {
  name: "Selected",
  data() {
    return { selectedVal: 0 };
  },
  methods: {
    select(value) {
      this.selectedVal = value;
    }
  }
}
</script>
App.vue
<template>
  <div id="app">
    <Selected>
      <ul
        slot="selected"
        slot-scope="{selectedVal, select}"> <!-- ここでSelectedコンポーネントのスコープにある変数・関数にアクセスしています -->
        <li
          v-for="(item, index) in props.items"
          :class="selectedVal == index ? 'selected-item' : ''"
          @click="select(index)"
        >
          {{ item }}
        </li>
      </ul>
    </Selected>
  </div>
</template>

<script>
  import Selected from './Selected';

  export default {
    name: 'hello',
    components:{
      Selected
    }
  }
</script>

Scoped Slotでも配信先のコンポーネントの変数・関数を扱えるため、それを用いて振る舞いだけを抽出することができることを確認しました。ラップされたコンポーネントのデータや関数にアクセスして振る舞いを追加するというのは、やってることはRender Propと同じだなと思います。

結論

実装例作ってみての結論ですが、どちらのパターンもEvan Youが言っている通り解いてる課題もできることも一緒だなと感じました。どっちが良いとかではなく書き方の違いでしかないので、描画関数を使っているか否かなど、携わっているVueプロジェクトのスタイルに応じて使い分ければ良いのではと思います。

それでは、本記事は以上となります。お読みいただきありがとうございました。

参考

seya
最近の趣味はGraphQLとFigmaです。
https://note.mu/seyanote
linc-well
Linc'well(リンクウェル)は2018年創業のヘルスケアスタートアップです。我々は、医療のIT化を通じて、人々と社会の健康に貢献します。
https://www.linc-well.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした