Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Vue.js 状態管理の選択肢 - そのVuex本当に必要ですか - / Vue.js State Management Options

0a16f7b8629f2facca2a1e80a73c5423?s=47 ryo
March 17, 2021

Vue.js 状態管理の選択肢 - そのVuex本当に必要ですか - / Vue.js State Management Options

iCARE Dev Meetup #19 2021/03/17

0a16f7b8629f2facca2a1e80a73c5423?s=128

ryo

March 17, 2021
Tweet

Transcript

  1. Vue.js ঢ়ଶ؅ཧͷબ୒ࢶ ʙ ͦͷVuexຊ౰ʹඞཁͰ͔͢? ʙ @KawamataRyo 2021/03/17 iCARE Dev Meetup

    #19
  2. ࣗݾ঺հ

  3. @KawamataRyo LAPRAS גࣜձࣾ TypeScript, Vue, Firebase, Ruby ݩফ๷࢜🔥🚒

  4. ࠓ೔࿩͢͜ͱ

  5. Vuex + α ͷঢ়ଶ؅ཧख๏ͷ঺հ ͦΕͧΕͷ Pros/Cons ͷ·ͱΊ

  6. ͳͥঢ়ଶ؅ཧΛςʔϚʹʁ🤔

  7. Vuex ݏΘΕ͗͢͡Όͳ͍͔..?

  8. Vuex 😭 🐵 🐶 😼 🦁 🐯 🐻 🐹 🦊

  9. ͦΕͬͯຊ౰ʹVuexͷ໰୊ͳͷͩΖ͏͔ʁ

  10. VuexΛ ద੾ʹར༻Ͱ͖ͯͳ͍ or ద੾ͳ৔ॴͰར༻Ͱ͖ͯͳ͍ ৔߹΋͋Δͷ͔΋ʁ

  11. VuexΛ ద੾ʹར༻Ͱ͖ͯͳ͍ or ద੾ͳ৔ॴͰར༻Ͱ͖ͯͳ͍ ৔߹΋͋Δͷ͔΋ʁ

  12. Vuex Ҏ֎ͷঢ়ଶ؅ཧΛ஌Δ͜ͱͰ ঢ়ଶ؅ཧͷબ୒ࢶΛ޿͍͛ͨ

  13. ঢ়ଶ؅ཧͷجຊ

  14. ͦ΋ͦ΋ঢ়ଶ؅ཧͱ͸ʁ

  15. ΞϓϦ͕࣋ͭ ঢ়ଶʢσʔλʣΛద੾ʹѻ ͍ΞϓϦΛਖ਼͘͠ಈ࡞ͤ͞ Δํ๏

  16. ͳͥঢ়ଶ؅ཧϥΠϒϥϦ͕ඞཁʁ

  17. ίϯϙʔωϯτ͕૿͑Δͱ Props/EmitͰͷঢ়ଶͷ఻೻ ͕ෳࡶʹ..

  18. ೝূ৘ใͳͲෳ਺ίϯϙʔωϯτ Ͱڞ௨ͷσʔλɾϩδοΫΛ࣋ͪ ͍ͨ..

  19. ෳ਺ͷίϯϙʔωϯτ͔Β ΞΫηεͰ͖Δάϩʔόϧͳ ঢ়ଶ͕ඞཁ => ঢ়ଶ؅ཧϥΠϒϥϦ

  20. ঢ়ଶ؅ཧͷબ୒ࢶ

  21. Vuex Pinia Vue Apollo Composition API Store

  22. Vuex Pinia Vue Apollo Composition API Store

  23. - Vueެࣜͷঢ়ଶ؅ཧϥΠϒϥϦ - FluxϥΠΫ - ୯Ұ Store vuejs / vuex

    (v4)
  24. Store import { InjectionKey } from "vue"; import { createStore,

    Store } from "vuex"; export interface State { count: number; } export const key: InjectionKey<Store<State>> = Symbol(); export const store = createStore<State>({ state: { count: 0 }, mutations: { increment(state) { state.count++; }, decrement(state) { state.count--; } }, actions: { increment({ commit }) { commit("increment"); }, decrement({ commit }) { commit("decrement"); } } });
  25. Store import { InjectionKey } from "vue"; import { createStore,

    Store } from "vuex"; export interface State { count: number; } export const key: InjectionKey<Store<State>> = Symbol(); export const store = createStore<State>({ state: { count: 0 }, mutations: { increment(state) { state.count++; }, decrement(state) { state.count--; } }, actions: { increment({ commit }) { commit("increment"); }, decrement({ commit }) { commit("decrement"); } } }); createStoreͰstoreΛఆٛ mutations, actionsͰstateΛૢ࡞
  26. Store import { InjectionKey } from "vue"; import { createStore,

    Store } from "vuex"; export interface State { count: number; } export const key: InjectionKey<Store<State>> = Symbol(); export const store = createStore<State>({ state: { count: 0 }, mutations: { increment(state) { state.count++; }, decrement(state) { state.count--; } }, actions: { increment({ commit }) { commit("increment"); }, decrement({ commit }) { commit("decrement"); } } }); provide/inject༻ͷΩʔ ʢTSͷܕఆٛऔಘͷͨΊʣ
  27. Install import { createApp } from "vue"; import App from

    "./App.vue"; import { store, key } from "./stores/vuex/store"; const app = createApp(App); // vuex app.use(store, key); app.mount("#app");
  28. Install import { createApp } from "vue"; import App from

    "./App.vue"; import { store, key } from "./stores/vuex/store"; const app = createApp(App); // vuex app.use(store, key); app.mount("#app"); storeͱkeyΛొ࿥
  29. Components (read) <script lang="ts"> import { defineComponent, computed } from

    "vue"; import { useStore } from "vuex"; import { key } from "@/stores/vuex/store"; export default defineComponent({ name: "Counter", setup() { const store = useStore(key); const count = computed(() => store.state.count); return { count }; } }); </script>
  30. Components (read) <script lang="ts"> import { defineComponent, computed } from

    "vue"; import { useStore } from "vuex"; import { key } from "@/stores/vuex/store"; export default defineComponent({ name: "Counter", setup() { const store = useStore(key); const count = computed(() => store.state.count); return { count }; } }); </script> keyΛ౉ͯ͠storeΛऔಘ count͸numberܕͱͯ͠ਪ࿦
  31. <script lang="ts"> import { defineComponent } from "vue"; import {

    useStore } from "vuex"; import { key } from "@/stores/vuex/store"; export default defineComponent({ name: "IncrementButton", setup() { const store = useStore(key); const increment = () => store.dispatch("increment"); return { increment }; } }); </script> Components (write)
  32. <script lang="ts"> import { defineComponent } from "vue"; import {

    useStore } from "vuex"; import { key } from "@/stores/vuex/store"; export default defineComponent({ name: "IncrementButton", setup() { const store = useStore(key); const increment = () => store.dispatch("increment"); return { increment }; } }); </script> Components (write) dispatchͰactionsΛ࣮ޮ ܕิ׬͸ޮ͔ͳ͍
  33. - ެࣜϥΠϒϥϦ - υΩϡϝϯτɾࢀߟ৘ใͷ๛෋͞ - DevToolsͷ࿈ܞ - ߏ଄Խ͞ΕͨύλʔϯʢFluxʣ - SSRରԠ

    Pros 😁
  34. - TypeScriptͷܕ෇͚΁ͷରԠ - هड़ྔͷଟ͞ Cons 😵

  35. Vuex Pinia Vue Apollo Composition API Store

  36. - Composition APIઐ༻ - ܰྔʢ1kbະຬʣ - ෳ਺Store posva / pinia

  37. Store import { defineStore } from "pinia"; export const useCounterStore

    = defineStore({ id: "counter", state: () => ({ count: 0 }), actions: { increment() { this.count++; }, decrement() { this.count--; } } });
  38. Store import { defineStore } from "pinia"; export const useCounterStore

    = defineStore({ id: "counter", state: () => ({ count: 0 }), actions: { increment() { this.count++; }, decrement() { this.count--; } } }); mutations͸ͳ͘actionsͰ stateΛૢ࡞ storeݻ༗ͷ idΛ࣋ͭ
  39. Install import { createApp } from "vue"; import App from

    "./App.vue"; import { createPinia } from "pinia"; const app = createApp(App); // pinia app.use(createPinia()); app.mount("#app");
  40. Install import { createApp } from "vue"; import App from

    "./App.vue"; import { createPinia } from "pinia"; const app = createApp(App); // pinia app.use(createPinia()); app.mount("#app"); piniaͷrootετΞΛొ࿥
  41. Components (read) <script lang="ts"> import { defineComponent, computed } from

    "vue"; import { useCounterStore } from "@/stores/pinia/store"; export default defineComponent({ name: "Counter", setup() { const store = useCounterStore(); const count = computed(() => store.count); return { count }; } }); </script>
  42. Components (read) <script lang="ts"> import { defineComponent, computed } from

    "vue"; import { useCounterStore } from "@/stores/pinia/store"; export default defineComponent({ name: "Counter", setup() { const store = useCounterStore(); const count = computed(() => store.count); return { count }; } }); </script> numberܕͱͯ͠ਪ࿦ exportͨ͠StoreΛར༻
  43. <script lang="ts"> import { defineComponent } from "vue"; import {

    useCounterStore } from "@/stores/pinia/store"; export default defineComponent({ name: "IncrementButton", setup() { const store = useCounterStore(); const increment = () => store.increment(); return { increment }; } }); </script> Components (write)
  44. <script lang="ts"> import { defineComponent } from "vue"; import {

    useCounterStore } from "@/stores/pinia/store"; export default defineComponent({ name: "IncrementButton", setup() { const store = useCounterStore(); const increment = () => store.increment(); return { increment }; } }); </script> Components (write) actionsͷ ܕิ׬͕ޮ͘
  45. Demo https://vue-state-management-samples.vercel.app/pinia

  46. - TypeScript ׬શରԠ - DevToolsͷ࿈ܞʢݱঢ়ࢀরͷΈʣ - γϯϓϧͳίʔυ - SSRରԠ Pros

    😁
  47. - ৘ใྔͷগͳ͞ - ਖ਼ࣜϦϦʔεલ - ։ൃͷܧଓੑ Cons 😵

  48. Vuex Pinia Composition API Store vue-apollo

  49. - Composition APIͰ ಠ࣮ࣗ૷ͨ͠Store Composition Store

  50. Store import { reactive, provide, InjectionKey, toRefs } from "vue";

    import { DeepReadonly } from "utility-types"; export const createStore = () => { const state = reactive({ count: 0 }); const actions = { increment: () => { state.count++; }, decrement: () => { state.count--; } }; return { state: toRefs(state), actions }; }; type Store = ReturnType<DeepReadonly<typeof createStore>>; export const STORE_KEY: InjectionKey<Store> = Symbol("Store"); export const useStore = () => { return inject(STORE_KEY) as Store; };
  51. Store import { reactive, provide, InjectionKey, toRefs } from "vue";

    import { DeepReadonly } from "utility-types"; export const createStore = () => { const state = reactive({ count: 0 }); const actions = { increment: () => { state.count++; }, decrement: () => { state.count--; } }; return { state: toRefs(state), actions }; }; type Store = ReturnType<DeepReadonly<typeof createStore>>; export const STORE_KEY: InjectionKey<Store> = Symbol("Store"); export const useStore = () => { return inject(STORE_KEY) as Store; }; reactiveͰstateΛఆٛ ؔ਺ͰstateΛૢ࡞
  52. Store import { reactive, provide, InjectionKey, toRefs } from "vue";

    import { DeepReadonly } from "utility-types"; export const createStore = () => { const state = reactive({ count: 0 }); const actions = { increment: () => { state.count++; }, decrement: () => { state.count--; } }; return { state: toRefs(state), actions }; }; type Store = DeepReadonly<ReturnType<typeof createStore>>; export const STORE_KEY: InjectionKey<Store> = Symbol("Store"); export const useStore = () => { return inject(STORE_KEY) as Store; }; provide/inject༻ͷ ؔ਺ɾΩʔ DeepReadolyͰ stateͷ௚઀ॻ͖׵͑Λېࢭ্ͨ͠Ͱ InjectionKeyͷܕʹઃఆ
  53. Install import { createApp, provide } from "vue"; import App

    from "./App.vue"; import { STORE_KEY, createStore } from "@/stores/originalStore/store"; const app = createApp(App); // Original Store app.provide(STORE_KEY, createStore()); app.mount("#app");
  54. import { createApp, provide } from "vue"; import App from

    "./App.vue"; import { STORE_KEY, createStore } from "@/stores/originalStore/store"; const app = createApp(App); // Original Store app.provide(STORE_KEY, createStore()); app.mount("#app"); Install Storeͷ࡞੒ͱprovideͰͷొ࿥
  55. Components (read) <script lang="ts"> import { defineComponent, computed } from

    "vue"; import { useStore } from "@/stores/originalStore/store"; export default defineComponent({ name: "Counter", setup() { const { state } = useStore(); const count = computed(() => state.count.value); return { count }; } }); </script>
  56. Components (read) <script lang="ts"> import { defineComponent, computed } from

    "vue"; import { useStore } from "@/stores/originalStore/store"; export default defineComponent({ name: "Counter", setup() { const { state } = useStore(); const count = computed(() => state.count.value); return { count }; } }); </script> injectͰͷstoreͷऔಘ numberܕͱͯ͠ਪ࿦
  57. <script lang="ts"> import { defineComponent } from "vue"; import {

    useStore } from "@/stores/originalStore/store"; export default defineComponent({ name: "IncrementButton", setup() { const { actions } = useStore(); const increment = () => actions.increment(); return { increment }; } }); </script> Components (write)
  58. <script lang="ts"> import { defineComponent } from "vue"; import {

    useStore } from "@/stores/originalStore/store"; export default defineComponent({ name: "IncrementButton", setup() { const { actions } = useStore(); const increment = () => actions.increment(); return { increment }; } }); </script> Components (write) ୯७ͳؔ਺ݺͼग़͠ ͳͷͰܕิ׬͕ޮ͘
  59. Demo https://vue-state-management-samples.vercel.app/original-store

  60. - TypeScript ׬શରԠ - γϯϓϧͳίʔυ - ґଘͳ͠ Pros 😁

  61. - ΦϨΦϨStoreͷ໰୊ - DevTools ࿈ܞ - vue-routerɺSSRͷରԠ Cons 😵

  62. Vuex Pinia Vue Apollo Composition API Store

  63. - Apollo CacheΛStoreʹ - Query / MutationͰͷૢ࡞ vuejs / vue-apollo

  64. GraphQLͷεΩʔϚఆٛ import gql from "graphql-tag"; export const typeDefs = gql`

    extend type Store { count: Int! } extend type Mutation { increment: Int decrement: Int } extend type Query { CountQuery: Store } `;
  65. Queryͷఆٛ import gql from "graphql-tag"; export const COUNT_QUERY = gql`

    query CountQuery { store @client { count } } `;
  66. Queryͷఆٛ import gql from "graphql-tag"; export const COUNT_QUERY = gql`

    query CountQuery { store @client { count } } `; @clientͱσΟϨΫςΟϒΛ͚ͭΔ͜ͱͰɺ αʔόʔʹ໰͍߹ΘͤͣɺΩϟογϡͰॲཧ͢Δ
  67. import gql from "graphql-tag"; export const INCREMENT_MUTATION = gql` mutation

    incrementMutation { increment @client } `; export const DECREMENT_MUTATION = gql` mutation decrementMutation { decrement @client } `; Mutationͷఆٛ
  68. Resolvers import { InMemoryCache } from "apollo-cache-inmemory"; import { COUNT_QUERY

    } from "@/stores/apolloClient/queries"; export const resolvers = { Mutation: { increment: ( _: unknown, _arg: unknown, { cache }: { cache: InMemoryCache }) => { const data = cache.readQuery<any>({ query: COUNT_QUERY }); data.store.count++; cache.writeQuery({ query: COUNT_QUERY, data }); return data.store.count; }, decrement: ( _: unknown, _arg: unknown, { cache }: { cache: InMemoryCache }) => { const data = cache.readQuery<any>({ query: COUNT_QUERY }); data.store.count--; cache.writeQuery({ query: COUNT_QUERY, data }); return data.store.count; } } };
  69. Resolvers import { InMemoryCache } from "apollo-cache-inmemory"; import { COUNT_QUERY

    } from "@/stores/apolloClient/queries"; export const resolvers = { Mutation: { increment: ( _: unknown, _arg: unknown, { cache }: { cache: InMemoryCache }) => { const data = cache.readQuery<any>({ query: COUNT_QUERY }); data.store.count++; cache.writeQuery({ query: COUNT_QUERY, data }); return data.store.count; }, decrement: ( _: unknown, _arg: unknown, { cache }: { cache: InMemoryCache }) => { const data = cache.readQuery<any>({ query: COUNT_QUERY }); data.store.count--; cache.writeQuery({ query: COUNT_QUERY, data }); return data.store.count; } } }; queryͷcacheΛॻ͖׵͍͑ͯΔ
  70. Apollo Clientͷ࡞੒ import ApolloClient from "apollo-boost"; import { typeDefs }

    from "@/stores/apolloClient/typeDefs"; import { InMemoryCache } from "apollo-cache-inmemory"; import { resolvers } from "@/stores/apolloClient/resolvers"; const cache = new InMemoryCache(); export const apolloClient = new ApolloClient({ cache, typeDefs, resolvers }); // storeͷॳظԽ cache.writeData({ data: { store: { __typename: "Store", count: 0 } } });
  71. Apollo Clientͷ࡞੒ import ApolloClient from "apollo-boost"; import { typeDefs }

    from "@/stores/apolloClient/typeDefs"; import { InMemoryCache } from "apollo-cache-inmemory"; import { resolvers } from "@/stores/apolloClient/resolvers"; const cache = new InMemoryCache(); export const apolloClient = new ApolloClient({ cache, typeDefs, resolvers }); // storeͷॳظԽ cache.writeData({ data: { store: { __typename: "Store", count: 0 } } }); cacheͷॳظԽॲཧ
  72. Install import { createApp } from "vue"; import App from

    "./App.vue"; import { DefaultApolloClient } from "@vue/apollo-composable"; import { apolloClient } from "@/stores/apolloClient/apolloClient"; const app = createApp(App); // vue-apollo app.provide(DefaultApolloClient, apolloClient); app.mount("#app");
  73. Install import { createApp } from "vue"; import App from

    "./App.vue"; import { DefaultApolloClient } from "@vue/apollo-composable"; import { apolloClient } from "@/stores/apolloClient/apolloClient"; const app = createApp(App); // vue-apollo app.provide(DefaultApolloClient, apolloClient); app.mount("#app"); Apollo ClientΛprovide
  74. Components (read) <script lang="ts"> import { defineComponent } from "vue";

    import { useQuery, useResult } from "@vue/apollo-composable"; import { COUNT_QUERY } from "@/stores/apolloClient/queries"; export default defineComponent({ name: "Counter", setup() { const { result } = useQuery(COUNT_QUERY); const count = useResult(result, 0, data => data.store.count); return { count }; } }); </script>
  75. Components (read) <script lang="ts"> import { defineComponent } from "vue";

    import { useQuery, useResult } from "@vue/apollo-composable"; import { COUNT_QUERY } from "@/stores/apolloClient/queries"; export default defineComponent({ name: "Counter", setup() { const { result } = useQuery(COUNT_QUERY); const count = useResult(result, 0, data => data.store.count); return { count }; } }); </script> GraphQL QueryͰऔಘ
  76. Components (write) <script lang="ts"> import { defineComponent } from "vue";

    import { useMutation } from "@vue/apollo-composable"; import { INCREMENT_MUTATION } from "@/stores/apolloClient/mutations"; export default defineComponent({ name: "IncrementButton", setup() { const { mutate: increment } = useMutation(INCREMENT_MUTATION); return { increment }; } }); </script>
  77. <script lang="ts"> import { defineComponent } from "vue"; import {

    useMutation } from "@vue/apollo-composable"; import { INCREMENT_MUTATION } from "@/stores/apolloClient/mutations"; export default defineComponent({ name: "IncrementButton", setup() { const { mutate: increment } = useMutation(INCREMENT_MUTATION); return { increment }; } }); </script> Components (write) MutationͰॻ͖׵͑
  78. Demo https://vue-state-management-samples.vercel.app/vue-apollo

  79. - StoreΛ࣋ͨͳ͍ - DevToolsʢApolloʣରԠ Pros 😁

  80. - Vue ApolloɺGraphQL΁ͷґଘ - ঢ়ଶͷ೺Ѳ͕ͮ͠Β͍ - ։ൃͷܧଓੑ - ίʔυྔͷ૿Ճ Cons

    😵
  81. ·ͱΊ

  82. 7VFY 1JOJB $PNQPTJUJPO"1* 4UPSF 7VF"QPMMP 4UPSFͷ਺ ୯Ұ ෳ਺ ෳ਺ ʢΩϟγϡʣ

    %FW5PPMTରԠ ˓ ˚ º ˚ʢ"QPMMPʣ 5ZQF4DSJQUରԠ ˚ ˓ ˓ ˚ ଟػೳੑ ʢ443FUDʣ ˓ ˚ º º ίʔυͷهड़ྔ ˚ ˓ ˓ º
  83. ࠓͷVueͷঢ়ଶ؅ཧ͸Vuex͚ͩͰ͸ͳ͍ɻ ΞϓϦέʔγϣϯͷن໛΍ॏࢹ͢Δ఺ʹ ߹Θͤͯద੾ͳঢ়ଶ؅ཧख๏Λબ୒ ͢Δ͜ͱ͕େ੾

  84. ࢀߟ • you might not need Vuex: https://speakerdeck.com/ntepluhina/you-might-not-need-vuex • Do

    you really need Vuex: https://blog.logrocket.com/do-you-really-need-vuex • ΈΜͳͷVue.js: https://gihyo.jp/book/2021/978-4-297-11902-7 • Pinia vs Vuex: https://www.youtube.com/watch?v=ht_1NR7OFWc • intro to vuex: https://www.vuemastery.com/courses/mastering-vuex/intro-to-vuex/ • Pinia doc: https://pinia.esm.dev/ • Vuex4 doc: https://next.vuex.vuejs.org/
  85. ͓·͚ Vuex 5 RFCͷ·ͱΊ 2021/03/14 ࣌఺ https://github.com/kiaking/rfcs/blob/vuex-5/active-rfcs/0000-vuex-5.md

  86. Storeͷఆٛ Storeͷར༻ Mutationsͷഇࢭ dispatchΛհͣ͞ actionsΛ௚઀ݺ΂Δ ᶃ FluxϥΠΫύλʔϯΛഇࢭ

  87. Optional Store Composition Store ΄΅Composition APIͦͷ·· ᶄ 2छྨͷStoreఆٛํ๏

  88. Store 1 Store 2 ωετ͞ΕͨStoreʢModuleʣ Λഇࢭͯ͠ɺComposition Ͱ Storeͷ૬ޓར༻Λߦ͏ ᶅωετ͞ΕͨStoreΛഇࢭ

  89. Store Component Optional Storeͷ৔߹͸ ܕΞϊςʔγϣϯΛ͚ͭΔ ʢComposition StoreͰ͸ෆཁʣ ࢖༻࣌͸ ܕิ׬͕׬શʹޮ͘ ᶆ

    ׬શͳTypeScriptαϙʔτ