2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

とりあえずVue.jsのComposition APIで書くためのまとめ

Last updated at Posted at 2020-06-02

自分含め、これまで Vue.js 2.x でコードを書いていた人に向けて書きます。
あまり深いところや細かいところまでは掘らずに、Vue.js 2.x で書いてきた基本的な記述をComposition APIで書くために、と言う視点で書いてみました。

目次

  • Composition API
  • Vue.js 2.xで慣れ親しんだ書き方からの移行
  • ロジックの抽出と再利用

Composition API

これまでのVue.jsでは data computed methods などのoptionごとにリアクティブなデータや関数などを書いてきたが(Options APIというらしい)、大きなコンポーネントになってくると必要な機能ごとにコードをまとめた方がいい。そのための関数ベースのAPI群。

Vue.jsのv3から利用可能予定。
v2でもプラグインとして利用可能。

現在はRFCとして仕様を揉んでいるところなのでここに記載の内容は変更される可能性があります。
https://vue-composition-api-rfc.netlify.app/

メリット

  • 自由なコードの構成
  • ロジックの抽出と再利用
  • 型推論の強化

これまでのVue.jsのOptions APIを上書きするのものではなく、上記のメリットを受けられるような大規模なプロジェクトで使うのが良い。
Options APIはそれ自体がコードの規約となるので小・中規模なプロジェクトでとても有用と思う。

setup()

setupは新しいOptionで、コンポーネント内でComposition API を使うときのエントリーポイントになります。
setup関数内にリアクティブなデータや算出プロパティ、メソッドなどを定義していきます。

<template>
 ...
</template>

<script>
import { computed, ref, reactive } from 'vue' // 必要なAPIはimportが必要
export default {
  setup() {
    // 
  }
}
</script>

<style>
 ...
</style>

setup関数内で使うcomputed, ref, reactive などの関数(後述)はimportしておく必要があります。

import { computed, ref, reactive } from 'vue'

templateで使うものは return { } が必要

templateで使う値や関数はsetup関数の最後でreturnしておく必要がある。
下の例でいうとreturnされた count などは、いままでのOptions APIで言うところの this.count のような形でぶら下ります。
いちいちこれを書く手間がある一方、templateでどの値/関数が使われているかがわかりやすいというメリットもあります。

<template>
  <div>
    {{ count }} * 2 = {{ double }}
    <button @click="increment">+</button>
  </div>
</template>

<script>
import { computed, ref, reactive } from 'vue'
export default {
  setup() {
    const count = ref(0)
    const double = computed(() => {
      return count.value * 2
    }(

    const increment = () => {
      count.value++
    }
    
    // template で出力するデータはここで渡す必要がある
    return { count, double, increment }
  }
}
</script>

Options APIからCompositio APIへの移行

これまで慣れ親しんだOptions APIでの書き方をComposition APIで書くときのまとめです。
datacomputedなどoptionごとに書いてます。

data

ref()、またはreactive()でリアクティブなデータを扱うことができます。

ref()

ref関数は数値や文字列、booleanなどのプリミティブ値を引数にとり、リアクティブなrefオブジェクトを返す
setup関数内でリアクティブな値を使う場合は.valueプロパティを参照します。

<template>
  <div>
    {{ count }}
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
    const count = ref(0) // リアクティブな値をもつオブジェクトを返す
    console.log(count.value) // 0
    
    count.value++
    console.log(count.value) // 1
    
    const increment = () => {
      count.value++
    }
    return { count } // return する場合はリアクティブなrefオブジェクトごと返す。 .value は不要
  }
}
</script>

reactive()

オブジェクトを引数にとり、リアクティブプロキシーを返します。Vue.js 2.x系のVue.observable()と等しいようです。
(参考:Proxy - MDN web docs)

The reactive conversion is "deep": it affects all nested properties. In the ES2015 Proxy based implementation, the returned proxy is not equal to the original object. It is recommended to work exclusively with the reactive proxy and avoid relying on the original object.

<template>
  <div>
    {{ state.count }}
    {{ state.code }}
  </div>
</template>

<script>
import { reactive } from 'vue'
export default {
  setup() {
    const state = reactive({
      count: 1,
      code: 'ready'
    }
    
    state.count++ // 2 refオブジェクトと違い.valueを参照しない

    return { state }
  }
}
</script>

computed

算出プロパティはcomputed関数を使います。
引数にgetter関数をとり、イミュータブルなrefオブジェクトを返します。
templateで使う場合はもちろんreturnする必要があります。

import { ref, computed } from 'vue'
export default {
  setup() {
    const count = ref(2) 
    count.value++ // 3
 
    const double = computed(() => {
      return count.value * 2
    })
    console.log(double.value) // 6
    double.value++ // error

    return { double }
  }
}

methods

Composition APIのなかでmethodsは単純なJavaScriptの関数として宣言します。
template内で利用する場合はリアクティブな値と同様、setup関数の最後にreturnしてあげる必要があります。

<template>
  <div>
    Count is {{ count }} 
    <button @click="increment">+</button>
  </div>
</template>

<script>
import { ref } from 'vue'
export default {
  setup() {
  const count = ref(1)
  
    // 単純なJSの関数として宣言する
    const increment = () => {
      count.value++
    }

    return { count, increment }
  }
}
</script>

props

これまで同様、setup関数の外で指定。setup(props)のように引数に指定し使う。setupに渡されたpropsオブジェクトはリアクティブであり、

<template>
  <div>ID : { id }</div>
</template>
<script>
export default {
  props: {
    id: string
  },
  setup(props) {
    // setup関数内では props.id で扱うことができる
    
    return { props }
  }
}
</script>

ただしpropsオブジェクトからプロパティだけを取り出すとリアクティビティは失われます。

export default {
  props: {
    id: Number
  },
  setup({ id }) {
    watchEffect(() => {
    console.log(`id is: ` + id) // リアクティブではなくなる。
    })
  }
}

created(), mounted(), updated()

RFCのリファレンスには以下のような対応表があります。
https://vue-composition-api-rfc.netlify.app/api.html#lifecycle-hooks

  • beforeCreate -> setup()を使う
  • created -> setup()を使う
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

created()

setup関数はpropの初期値が解決された後、beforeCreatedフックとcreatedフックの前に呼ばれます。
そのためsetup自体をbeforeCreated, createdとして扱うのがいいようです。

なお、setupはいままでのcreatedよりも前に解決されるため、

  • setup関数内からはこれまでの data() のプロパティやmethodsの関数にアクセスできない
  • 逆にsetupでreturn された値/関数はcreated() やmethodsの中で this.xxxの表記でアクセスできる。

となります。

mounted(), updated()

created, beforeCreated以外のライフサイクルフック、例えばmounted, updatedなんかは
onXxxxの形でimportした上でsetup関数内でonMounted() onUpdated() のように使います。

import { onMounted, onUpdated } from 'vue'
export default {
  setup() {
    onMounted(() => {
      console.log('mounted.')
    }
    onUpdated(() => {
      console.log('updated.')
    })
  }
}

そのほかのライフサイクルフック

beforeMount(), beforeUpdate(), beforeDestroy(), destroyed(), errorCaptured()などのそのほかのLife Cycle Hookも同様に関数をimportしてsetup内で使います。

import { onBeforeMount, onBeforeUpdate, onBeforeUnmount, onUnmounted, onErrorCaptured   } from 'vue'
export default {
  setup() {
    onBeforeMount(() => { ... }) // = beforeMount()
    onBeforeUpdate(() => { ... }) // = beforeUpdate()
    onBeforeUnmount(() => { ... }) // = beforeDestroy()
    onUnmounted(() => { ... }) // = destroyed()
    onErrorCaptured(() => { ... }) // = errorCaptured()
  }
}

emit, attrs, slots

emit, attrs, slotsは、setup関数の第二引数に渡されるcontextオブジェクトを通してアクセス可能です。


export default {
  setup(props, context) {
    context.attrs
    context.slots
    context.emit
  }
}

this.$refs

公式のDocにあったものではないですが、同様なことはこんな感じでできそう。

<template>
  <h1 ref="hogeTitle">タイトル</h1>
</template>
<script>

import { ref, onMounted } from 'vue'
export default {
  setup() {
    // 空のリアクティブなオブジェクトを作成
    const hogeTitle = ref(null)
    console.log(hogeTitle.value) // 当然 null。 setup() はcreatedの前、templateが解決される前に実行されるので

    onMounted(() => {
      // templateが解決され、このタイミングでアクセスできる
      console.log(hogeTitle.value) // <h1>タイトル</h1>
    })
    return { hogeTitle } // これが必要
  }
}
</script>

ロジックの抽出と再利用

Composition APIを使うメリットの一つにロジックの抽出とその再利用があります。

いくつかのコンポーネント間で利用されるような機能のロジックや、膨大で複雑なコードの機能を切り出して再利用することができ、見通しがよく効率的なコードが書けます。
ロジックを抽出して再利用する際にも、Composition APIのコード構成に対する非常に高い柔軟性が活きてきます。

以下の例はクリックで増えるカウントに対する機能を抽出しています。

// count.vue 
import { ref, computed, onMounted } from 'vue'

export function count() {
  const count = ref(0)
  
  const update = (e) => {
    count.value++
  }

  const displayCount = computed(() => {
    return count.value >= 10 ? '10+' : count.value
  })
  
  onMounted(() => {
    window.addEventListener('click', update)
  })

  return { cont, displayCount }
}

抽出したcount関数を利用します

import { count } from './count'

export default {
  setup() {
    const { count, displayCount } = count()
    
    return { count, displayCount }
  }
}

終わりに

Compositio APIの基本的な書き方、そしてこれまでのVue.jsに慣れ親しんだ人からの目線での書き方をまとめてみました。

Composition APIにはここで紹介していないAPIがまだあります。
冒頭のメリットでも触れたように、自由なコード構成、型サポートの強化、ロジックの抽出など、特に大規模なプロジェクトでは非常に可能性を感じさせる新機能となることでしょう。

一方、RFCである現在(2020/06)はAPIの追加や破壊的変更がされる可能性があります。
そのため業務での利用はまだ難しいですが、
来るべきVue.js 3.0の正式リリースに備え今から概要を掴んでいくのは良いかなと思います。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?