Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

ࣗݾ঺հ

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

ࠓ೔࿩͢͜ͱ

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Vuex 😭 🐵 🐶 😼 🦁 🐯 🐻 🐹 🦊

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

ঢ়ଶ؅ཧͷجຊ

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

ঢ়ଶ؅ཧͷબ୒ࢶ

Slide 21

Slide 21 text

Vuex Pinia Vue Apollo Composition API Store

Slide 22

Slide 22 text

Vuex Pinia Vue Apollo Composition API Store

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

Store import { InjectionKey } from "vue"; import { createStore, Store } from "vuex"; export interface State { count: number; } export const key: InjectionKey> = Symbol(); export const store = createStore({ 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Λૢ࡞

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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");

Slide 28

Slide 28 text

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Λొ࿥

Slide 29

Slide 29 text

Components (read) 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 }; } });

Slide 30

Slide 30 text

Components (read) 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 }; } }); keyΛ౉ͯ͠storeΛऔಘ count͸numberܕͱͯ͠ਪ࿦

Slide 31

Slide 31 text

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 }; } }); Components (write)

Slide 32

Slide 32 text

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 }; } }); Components (write) dispatchͰactionsΛ࣮ޮ ܕิ׬͸ޮ͔ͳ͍

Slide 33

Slide 33 text

- ެࣜϥΠϒϥϦ - υΩϡϝϯτɾࢀߟ৘ใͷ๛෋͞ - DevToolsͷ࿈ܞ - ߏ଄Խ͞ΕͨύλʔϯʢFluxʣ - SSRରԠ Pros 😁

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Vuex Pinia Vue Apollo Composition API Store

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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Λ࣋ͭ

Slide 39

Slide 39 text

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");

Slide 40

Slide 40 text

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ετΞΛొ࿥

Slide 41

Slide 41 text

Components (read) 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 }; } });

Slide 42

Slide 42 text

Components (read) 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 }; } }); numberܕͱͯ͠ਪ࿦ exportͨ͠StoreΛར༻

Slide 43

Slide 43 text

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 }; } }); Components (write)

Slide 44

Slide 44 text

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 }; } }); Components (write) actionsͷ ܕิ׬͕ޮ͘

Slide 45

Slide 45 text

Demo https://vue-state-management-samples.vercel.app/pinia

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Vuex Pinia Composition API Store vue-apollo

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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>; export const STORE_KEY: InjectionKey = Symbol("Store"); export const useStore = () => { return inject(STORE_KEY) as Store; };

Slide 51

Slide 51 text

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>; export const STORE_KEY: InjectionKey = Symbol("Store"); export const useStore = () => { return inject(STORE_KEY) as Store; }; reactiveͰstateΛఆٛ ؔ਺ͰstateΛૢ࡞

Slide 52

Slide 52 text

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>; export const STORE_KEY: InjectionKey = Symbol("Store"); export const useStore = () => { return inject(STORE_KEY) as Store; }; provide/inject༻ͷ ؔ਺ɾΩʔ DeepReadolyͰ stateͷ௚઀ॻ͖׵͑Λېࢭ্ͨ͠Ͱ InjectionKeyͷܕʹઃఆ

Slide 53

Slide 53 text

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");

Slide 54

Slide 54 text

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Ͱͷొ࿥

Slide 55

Slide 55 text

Components (read) 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 }; } });

Slide 56

Slide 56 text

Components (read) 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 }; } }); injectͰͷstoreͷऔಘ numberܕͱͯ͠ਪ࿦

Slide 57

Slide 57 text

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 }; } }); Components (write)

Slide 58

Slide 58 text

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 }; } }); Components (write) ୯७ͳؔ਺ݺͼग़͠ ͳͷͰܕิ׬͕ޮ͘

Slide 59

Slide 59 text

Demo https://vue-state-management-samples.vercel.app/original-store

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

Vuex Pinia Vue Apollo Composition API Store

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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 } `;

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

import gql from "graphql-tag"; export const INCREMENT_MUTATION = gql` mutation incrementMutation { increment @client } `; export const DECREMENT_MUTATION = gql` mutation decrementMutation { decrement @client } `; Mutationͷఆٛ

Slide 68

Slide 68 text

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({ 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({ query: COUNT_QUERY }); data.store.count--; cache.writeQuery({ query: COUNT_QUERY, data }); return data.store.count; } } };

Slide 69

Slide 69 text

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({ 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({ query: COUNT_QUERY }); data.store.count--; cache.writeQuery({ query: COUNT_QUERY, data }); return data.store.count; } } }; queryͷcacheΛॻ͖׵͍͑ͯΔ

Slide 70

Slide 70 text

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 } } });

Slide 71

Slide 71 text

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ͷॳظԽॲཧ

Slide 72

Slide 72 text

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");

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

Components (read) 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 }; } });

Slide 75

Slide 75 text

Components (read) 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 }; } }); GraphQL QueryͰऔಘ

Slide 76

Slide 76 text

Components (write) 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 }; } });

Slide 77

Slide 77 text

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 }; } }); Components (write) MutationͰॻ͖׵͑

Slide 78

Slide 78 text

Demo https://vue-state-management-samples.vercel.app/vue-apollo

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

·ͱΊ

Slide 82

Slide 82 text

7VFY 1JOJB $PNQPTJUJPO"1* 4UPSF 7VF"QPMMP 4UPSFͷ਺ ୯Ұ ෳ਺ ෳ਺ ʢΩϟγϡʣ %FW5PPMTରԠ ˓ ˚ º ˚ʢ"QPMMPʣ 5ZQF4DSJQUରԠ ˚ ˓ ˓ ˚ ଟػೳੑ ʢ443FUDʣ ˓ ˚ º º ίʔυͷهड़ྔ ˚ ˓ ˓ º

Slide 83

Slide 83 text

ࠓͷVueͷঢ়ଶ؅ཧ͸Vuex͚ͩͰ͸ͳ͍ɻ ΞϓϦέʔγϣϯͷن໛΍ॏࢹ͢Δ఺ʹ ߹Θͤͯద੾ͳঢ়ଶ؅ཧख๏Λબ୒ ͢Δ͜ͱ͕େ੾

Slide 84

Slide 84 text

ࢀߟ • 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/

Slide 85

Slide 85 text

͓·͚ Vuex 5 RFCͷ·ͱΊ 2021/03/14 ࣌఺ https://github.com/kiaking/rfcs/blob/vuex-5/active-rfcs/0000-vuex-5.md

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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