~Webエンジニアブログ~

【Vue.js】provide/injectの使い方と使いどころ

# provide/injectとは

Vue.jsの基本であるデータバインディング。 通常は親から子に、子から孫にv-bindとpropsを使ってバケツリレーのようにせっせとデータを運びます。

ですが、ビジネスロジックと全く関連のない、例えばサイトのテーマカラーのような情報をバケツリレーするのは気持ちが悪い。 かといってどこでデータが変えられるかわからないVuexを使うのも間違ってる気がする。 今回は、そんな時に活かせるかもしれない、 provide/inject 構文の書き方をご紹介します。

# provideの書き方

<script>
export default {
  name: 'ParentComponent',

  provide () {
    return {
      hoge: () => this.fuga,
    }
  },

  computed: { 
    fuga: () => 'Hello world.',   
  },
}
</script>

provideはその名の通り、データを「提供する」構文です。 親→子の方向にデータを提供するのはv-bindと同じなんですが、provideは親→孫でも、親→ひ孫でも、バケツリレーをせずにデータを直接届けることができます。

注意点

provideは通常、リアクティブではありません。 そのため、この例ではhogeの値にファクトリ関数を使用することでリアクティブな動作を実現しています。

# injectの書き方

provideがv-bindだとすれば、injectはpropsのような対応関係になります。 バインディングした値を受け取るための構文です。

<template>
  <!-- Hello world. -->
  <h6>{{ hoge }}</h6>
</template>

<script>
export default {
  name: 'GrandchildComponent',

  inject: ['hoge'],
}
</script>

# 使いどころはどんな場所?

Vue公式でもプラグイン・ライブラリ向けの機能を謳っているように、 いわば飛び道具のような構文なので、あまり多用すると可読性が悪化するため、使いどころを間違えると大惨事となります。

使いどころとしては、主なビジネスロジックとは関係のない機能を隠蔽したい時に限るかなと思います。 例えば僕は、リサイズイベントに応じて画面幅を再計算する resize.js というミックスインを作成しました。

resize.js 全文はこちら
// resize.js
let resizeEnd = null

export default {
  provide () {
    return {
      $breakpoint: () => this.$breakpoint
    }
  },
  data: () => ({
    windowSize: 0,
    breakpoints: {
      xs: 600,
      sm: 960,
      md: 1264,
      lg: 1904,
    },
  }),

  computed: {
    $breakpoint () {
      const xs = this.windowSize <= this.breakpoints.xs
      const sm = this.breakpoints.xs <= this.windowSize && this.windowSize <= this.breakpoints.sm
      const md = this.breakpoints.sm <= this.windowSize && this.windowSize <= this.breakpoints.md
      const lg = this.breakpoints.md <= this.windowSize && this.windowSize <= this.breakpoints.lg
      const xl = this.windowSize > this.breakpoints.lg

      return {
        xs, sm, md, lg, xl,
        lessXs: xs,
        lessSm: this.windowSize <= this.breakpoints.sm,
        lessMd: this.windowSize <= this.breakpoints.md,
        lessLg: this.windowSize <= this.breakpoints.lg,

        overXs: this.windowSize >= this.breakpoints.xs,
        overSm: this.windowSize >= this.breakpoints.sm,
        overMd: this.windowSize >= this.breakpoints.md,
        overLg: this.windowSize >= this.breakpoints.lg,

        windowSize: this.windowSize,
      }
    },
  },

  mounted () {
    // 画面サイズをセット
    this.windowSize = window.innerWidth

    window.addEventListener('resize', () => {
      // リサイズが完了していなければ発火しない
      clearTimeout(resizeEnd)

      // リサイズ後に幅を設定
      resizeEnd = setTimeout(this.handleResize, 100)
    })
  },

  destroyed () {
    window.removeEventListener('resize', this.handleResize)
  },

  methods: {
    handleResize () {
      this.windowSize = window.innerWidth
    },
  },
}

これをroot付近のコンポーネントに読み込ませておき、必要なところでだけピンポイントでinjectすることで、ビジネスロジックとは関係ない変数でコードが汚染されるのを防ぐことができました!

# 終わりに

Vueの構文の中でも比較的扱いの難しいテクニックですので、使いどころは慎重に選んでいきたいですね。

参考になれば幸いです!

この記事をシェア