Vue 3 Composition API 書き方メモ。 (2020/03/22 一部修正)
参考
環境準備 今回は、vite で初期環境を用意。
1 npm init @vitejs/app app --template vue
限りなく最小構成 <span>0</span>
と出るだけ。 この状態のnum
は、リアクティブではない。
1 2 3 4 5 6 7 8 9 10 11 12 13 <template > <span > {{num}}</span > </template > <script lang ="ts" > import { defineComponent } from "vue" ; export default defineComponent ({ setup : () => { return { num : 0 }; }, }); </script >
props と context を受け取る 表示は、以下のようになる。
1 2 3 4 <div > <span > 0</span > <span > ABC</span > </div >
props と context を受け取るときは、以下のようになる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <template > <div > <span > {{num}}</span > <span > {{param1}}</span > </div > </template > <script lang ="ts" > import { defineComponent } from "vue" ; export default defineComponent ({ props : { param1 : { type : String , default : "ABC" , }, param2 : { type : Object , default : () => ({}), }, }, setup : (props, context ) => { return { num : 0 }; }, }); </script >
ref と reactive リアクティブなオブジェクトの作成には、ref
reactive
toRef
readonly
を使う。 (他に、shallowReactive
shallowReadonly
などあるが、当面使わなそう。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <template > <div > <div > {{ val1 }}</div > <div > {{ val2 }}</div > <div > {{ val3.p1 }}</div > <div > {{ val4 }}</div > <div > {{ val5 }}</div > <div > {{ val6.p1.value }}</div > <div > {{ val7.p1 }}</div > </div > </template > <script lang ="ts" > import { defineComponent, reactive, readonly, ref, toRef, toRefs } from "vue" ; export default defineComponent ({ setup : () => { let val1 = 0 ; let val2 = ref (0 ); let val3 = reactive ({ p1 : 0 }); let tmp4 = reactive ({ p1 : 0 }); let val4 = tmp4.p1 ; let tmp5 = reactive ({ p1 : 0 }); let val5 = toRef (tmp5, "p1" ); let tmp6 = reactive ({ p1 : 0 }); let val6 = toRefs (tmp6); let tmp7 = reactive ({ p1 : 0 }); const val7 = readonly (tmp7); setInterval (() => { val1++; val2.value ++; val3.p1 ++; val4++; val5.value ++; val6.p1 .value ++; tmp7.p1 ++; }, 1000 ); return { val1, val2, val3, val4, val5, val6, val7 }; }, }); </script >
shallowRef
triggerRef
がちょっと面白い。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <template > <div > <div > {{ val1.p1 }}</div > </div > </template > <script lang ="ts" > import { defineComponent, shallowRef, triggerRef } from "vue" ; export default defineComponent ({ setup : () => { const val1 = shallowRef ({ p1 : 0 }); setInterval (() => { val1.value .p1 ++; }, 1000 ); setInterval (() => { triggerRef (val1); }, 5000 ); return { val1 }; }, }); </script >
computed よく使うcomputed
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template > <div > <div > {{ val1 }}</div > <div > {{ val2 }}</div > </div > </template > <script lang ="ts" > import { computed, defineComponent, reactive, ref } from "vue" ; export default defineComponent ({ setup : () => { const tmp1 = ref (0 ); const val1 = computed (() => tmp1.value * 10 ); const tmp2 = reactive ({ p1 : 0 }); const val2 = computed (() => tmp2.p1 * 100 ); setInterval (() => { tmp1.value ++; tmp2.p1 ++; }, 1000 ); return { val1, val2 }; }, }); </script >
watch と watchEffect watch
の方が使いがちの気がする。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 <template > <div > <div > {{ val1 }}</div > <div > {{ val2.p1 }}</div > <div > {{ val3 }}</div > <div > {{ val4.p1 }}</div > </div > </template > <script lang ="ts" > import { defineComponent, reactive, ref, watch, watchEffect } from "vue" ; export default defineComponent ({ setup : () => { const val1 = ref (0 ); watchEffect (() => console .log (`val1:${val1.value} ` )); const val2 = reactive ({ p1 : 0 }); watchEffect (() => console .log (`val2.p1:${val2.p1} ` )); const val3 = ref (0 ); watch (val3, () => console .log (`val3:${val3.value} ` )); const val4 = reactive ({ p1 : 0 }); watch (val4, () => console .log (`val4.p1:${val4.p1} ` )); setInterval (() => { val1.value ++; val2.p1 ++; val3.value ++; val4.p1 ++; }, 1000 ); return { val1, val2, val3, val4 }; }, }); </script >
Computed and watch | Vue.js に watchEffect と watch の比較 が記載されている。
Perform the side effect lazily; Be more specific about what state should trigger the watcher to re-run; Access both the previous and current value of the watched state
watch は、Lazy に実行
watchEffect より watch の方が明確に監視対象を指定できる
監視していた値の前後の値を参照できる
ライフサイクルフック 以前まで書いていた mounted
などは、onMounted
などのon~~
に変わった。setup()
の中で呼び出す。
1 2 3 4 5 6 7 8 9 10 11 <template > </template > <script lang ="ts" > import { defineComponent, onMounted } from "vue" ; export default defineComponent ({ setup : () => { onMounted (() => console .log ("Execute onMounted" )); }, }); </script >
Provide と Inject 公式の composition API のところを見ると、provide したコンポーネントでも inject して使えそうな書き方をしているように見える。 がしかし、Provide / inject | Vue.js を見ると、ちゃんと親子関係の説明をしていた。
正直これだと規模によるだろうが、ほとんど root コンポーネントで provide するんじゃないかとという感想。
シンプルに確認 親コンポーネントは以下のようになる。
src/App.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <template > <div > <Cmp1 /> <Cmp2 /> </div > </template > <script lang ="ts" > import { defineComponent, provide, ref } from "vue" ; import Cmp1 from "./components/Cmp1.vue" ; import Cmp2 from "./components/Cmp2.vue" ; export default defineComponent ({ components : { Cmp1 , Cmp2 , }, setup : () => { provide ("key" , ref (0 )); }, }); </script >
子コンポーネント[更新側]は以下のようになる。
src/components/Com1.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template > <div > {{val}}</div > </template > <script lang ="ts" > import { defineComponent, inject } from "vue" ; export default defineComponent ({ setup : () => { let val = inject ("key" ); let count = 0 ; setInterval (() => { val.value ++; }, 1000 ); return { val }; }, }); </script >
子コンポーネント[表示側]は以下のようになる。
src/components/Com2.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template > <div > {{val}}</div > </template > <script lang ="ts" > import { defineComponent, inject } from "vue" ; export default defineComponent ({ setup : () => { const val = inject ("key" ); return { val }; }, }); </script >
ルートコンポーネントで provide 最上位のコンポーネントで provide する。
main.ts 1 2 3 4 5 6 7 import { createApp, ref } from "vue" ;import App from "./App.vue" ;const app = createApp (App );app.provide ("key" , ref (0 )); app.mount ("#app" );
provide する対象をちゃんと切り出してみる サンプルの内容も準拠して、切り出してみます。
provide されるストア。
src/store.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { InjectionKey , reactive, provide, toRefs } from "vue" ;export interface StateDataInterface { val1 : number ; val2 : number ; } export const storeKey : InjectionKey <StateDataInterface > = Symbol ( "storeDataKey" ); export const storeData = ( ) => { return reactive ({ val1 : 0 , val2 : 0 }); };
ストアを provide する。
main.ts 1 2 3 4 5 6 7 import { createApp, ref } from "vue" ;import App from "./App.vue" ;import { storeKey, storeData, StateDataInterface } from "./store" ;const app = createApp (App );app.provide <StateDataInterface >(storeKey, storeData ()); app.mount ("#app" );
inject する側は、以下のようにする。
子コンポーネント[更新側]
src/components/Com1.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <template > <div > {{val ? val.val1 : "" }}</div > </template > <script lang ="ts" > import { defineComponent, inject } from "vue" ; import { storeKey, StateDataInterface } from "./../store" ; export default defineComponent ({ setup : () => { const val = inject<StateDataInterface >(storeKey); setInterval (() => { if (!val) return ; val.val1 ++; }, 1000 ); return { val : val }; }, }); </script >
子コンポーネント[表示側]
src/components/Com2.vue 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template > <div > {{val}}</div > </template > <script lang ="ts" > import { defineComponent, inject, toRefs } from "vue" ; import { storeKey, StateDataInterface } from "./../store" ; export default defineComponent ({ setup : () => { let { val1 } = toRefs ( inject<StateDataInterface >(storeKey, { val1 : 1 , val2 : 2 }) ); return { val : val1 }; }, }); </script >
Vue Composition API 一通り基本的書き方一旦確認できたので良かったかなと。 ref と reactive 周りが、1 つ下のオブジェクトを別の変数に取り出したりしただけで意図しないことが起きるのは注意ですね。
ではでは。