Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Vue.js 状態管理の選択肢 - そのVuex本当に必要ですか - / Vue.js Sta...
Search
ryo
March 17, 2021
Technology
7
4.6k
Vue.js 状態管理の選択肢 - そのVuex本当に必要ですか - / Vue.js State Management Options
iCARE Dev Meetup #19 2021/03/17
ryo
March 17, 2021
Tweet
Share
More Decks by ryo
See All by ryo
Raycast AI APIを使ってちょっと便利なAI拡張機能を作ってみた
kawamataryo
1
290
退屈なことはDevinにやらせよう〜〜Devin APIを使ったVisual Regression Testの自動追加〜
kawamataryo
5
2k
SaaS公式MCPサーバーをリリースして得た学び
kawamataryo
7
2.1k
Raycast AI APIを使ってちょっと便利な拡張機能を作ってみた / created-a-handy-extension-using-the-raycast-ai-api
kawamataryo
1
700
ts-morphのパフォーマンス改善Tips
kawamataryo
0
74
webpack to Rspack
kawamataryo
0
80
GitHub Actions と Datadog でコードベースの定点観測
kawamataryo
7
2k
個人開発駆動学習 / personal development driven learning
kawamataryo
1
260
GitHub Trending Bot, Sky Follower Bridge の紹介
kawamataryo
0
460
Other Decks in Technology
See All in Technology
NIKKEI Tech Talk #41: セキュア・バイ・デザインからクラウド管理を考える
sekido
PRO
0
130
LLM-Readyなデータ基盤を高速に構築するためのアジャイルデータモデリングの実例
kashira
0
260
Kiro Autonomous AgentとKiro Powers の紹介 / kiro-autonomous-agent-and-powers
tomoki10
0
530
MLflowで始めるプロンプト管理、評価、最適化
databricksjapan
1
250
Lessons from Migrating to OpenSearch: Shard Design, Log Ingestion, and UI Decisions
sansantech
PRO
1
140
シニアソフトウェアエンジニアになるためには
kworkdev
PRO
3
170
regrowth_tokyo_2025_securityagent
hiashisan
0
250
会社紹介資料 / Sansan Company Profile
sansan33
PRO
11
390k
ChatGPTで論⽂は読めるのか
spatial_ai_network
10
29k
Databricks向けJupyter Kernelでデータサイエンティストの開発環境をAI-Readyにする / Data+AI World Tour Tokyo After Party
genda
1
530
GitHub Copilotを使いこなす 実例に学ぶAIコーディング活用術
74th
3
3.4k
プロンプトやエージェントを自動的に作る方法
shibuiwilliam
12
10k
Featured
See All Featured
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
A designer walks into a library…
pauljervisheath
210
24k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Building an army of robots
kneath
306
46k
Scaling GitHub
holman
464
140k
The Art of Programming - Codeland 2020
erikaheidi
56
14k
Leading Effective Engineering Teams in the AI Era
addyosmani
8
1.3k
It's Worth the Effort
3n
187
29k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.3k
GraphQLとの向き合い方2022年版
quramy
50
14k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.2k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
1.1k
Transcript
Vue.js ঢ়ଶཧͷબࢶ ʙ ͦͷVuexຊʹඞཁͰ͔͢? ʙ @KawamataRyo 2021/03/17 iCARE Dev Meetup
#19
ࣗݾհ
@KawamataRyo LAPRAS גࣜձࣾ TypeScript, Vue, Firebase, Ruby ݩফ࢜🔥🚒
ࠓ͢͜ͱ
Vuex + α ͷঢ়ଶཧख๏ͷհ ͦΕͧΕͷ Pros/Cons ͷ·ͱΊ
ͳͥঢ়ଶཧΛςʔϚʹʁ🤔
Vuex ݏΘΕ͗͢͡Όͳ͍͔..?
Vuex 😭 🐵 🐶 😼 🦁 🐯 🐻 🐹 🦊
ͦΕͬͯຊʹVuexͷͳͷͩΖ͏͔ʁ
VuexΛ దʹར༻Ͱ͖ͯͳ͍ or దͳॴͰར༻Ͱ͖ͯͳ͍ ߹͋Δͷ͔ʁ
VuexΛ దʹར༻Ͱ͖ͯͳ͍ or దͳॴͰར༻Ͱ͖ͯͳ͍ ߹͋Δͷ͔ʁ
Vuex Ҏ֎ͷঢ়ଶཧΛΔ͜ͱͰ ঢ়ଶཧͷબࢶΛ͍͛ͨ
ঢ়ଶཧͷجຊ
ͦͦঢ়ଶཧͱʁ
ΞϓϦ͕࣋ͭ ঢ়ଶʢσʔλʣΛదʹѻ ͍ΞϓϦΛਖ਼͘͠ಈ࡞ͤ͞ Δํ๏
ͳͥঢ়ଶཧϥΠϒϥϦ͕ඞཁʁ
ίϯϙʔωϯτ͕૿͑Δͱ Props/EmitͰͷঢ়ଶͷ ͕ෳࡶʹ..
ೝূใͳͲෳίϯϙʔωϯτ Ͱڞ௨ͷσʔλɾϩδοΫΛ࣋ͪ ͍ͨ..
ෳͷίϯϙʔωϯτ͔Β ΞΫηεͰ͖Δάϩʔόϧͳ ঢ়ଶ͕ඞཁ => ঢ়ଶཧϥΠϒϥϦ
ঢ়ଶཧͷબࢶ
Vuex Pinia Vue Apollo Composition API Store
Vuex Pinia Vue Apollo Composition API Store
- Vueެࣜͷঢ়ଶཧϥΠϒϥϦ - FluxϥΠΫ - ୯Ұ Store vuejs / vuex
(v4)
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"); } } });
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Λૢ࡞
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ͷܕఆٛऔಘͷͨΊʣ
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");
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Λొ
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>
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Λऔಘ countnumberܕͱͯ͠ਪ
<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)
<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Λ࣮ޮ ܕิޮ͔ͳ͍
- ެࣜϥΠϒϥϦ - υΩϡϝϯτɾࢀߟใͷ๛͞ - DevToolsͷ࿈ܞ - ߏԽ͞ΕͨύλʔϯʢFluxʣ - SSRରԠ
Pros 😁
- TypeScriptͷܕ͚ͷରԠ - هड़ྔͷଟ͞ Cons 😵
Vuex Pinia Vue Apollo Composition API Store
- Composition APIઐ༻ - ܰྔʢ1kbະຬʣ - ෳStore posva / pinia
Store import { defineStore } from "pinia"; export const useCounterStore
= defineStore({ id: "counter", state: () => ({ count: 0 }), actions: { increment() { this.count++; }, decrement() { this.count--; } } });
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Λ࣋ͭ
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");
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ετΞΛొ
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>
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Λར༻
<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)
<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ͷ ܕิ͕ޮ͘
Demo https://vue-state-management-samples.vercel.app/pinia
- TypeScript શରԠ - DevToolsͷ࿈ܞʢݱঢ়ࢀরͷΈʣ - γϯϓϧͳίʔυ - SSRରԠ Pros
😁
- ใྔͷগͳ͞ - ਖ਼ࣜϦϦʔεલ - ։ൃͷܧଓੑ Cons 😵
Vuex Pinia Composition API Store vue-apollo
- Composition APIͰ ಠ࣮ࣗͨ͠Store Composition Store
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; };
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Λૢ࡞
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ͷܕʹઃఆ
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");
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Ͱͷొ
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>
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ܕͱͯ͠ਪ
<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)
<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) ୯७ͳؔݺͼग़͠ ͳͷͰܕิ͕ޮ͘
Demo https://vue-state-management-samples.vercel.app/original-store
- TypeScript શରԠ - γϯϓϧͳίʔυ - ґଘͳ͠ Pros 😁
- ΦϨΦϨStoreͷ - DevTools ࿈ܞ - vue-routerɺSSRͷରԠ Cons 😵
Vuex Pinia Vue Apollo Composition API Store
- Apollo CacheΛStoreʹ - Query / MutationͰͷૢ࡞ vuejs / vue-apollo
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 } `;
Queryͷఆٛ import gql from "graphql-tag"; export const COUNT_QUERY = gql`
query CountQuery { store @client { count } } `;
Queryͷఆٛ import gql from "graphql-tag"; export const COUNT_QUERY = gql`
query CountQuery { store @client { count } } `; @clientͱσΟϨΫςΟϒΛ͚ͭΔ͜ͱͰɺ αʔόʔʹ͍߹ΘͤͣɺΩϟογϡͰॲཧ͢Δ
import gql from "graphql-tag"; export const INCREMENT_MUTATION = gql` mutation
incrementMutation { increment @client } `; export const DECREMENT_MUTATION = gql` mutation decrementMutation { decrement @client } `; Mutationͷఆٛ
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; } } };
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Λॻ͖͍͑ͯΔ
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 } } });
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ͷॳظԽॲཧ
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");
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
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>
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Ͱऔಘ
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>
<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Ͱॻ͖͑
Demo https://vue-state-management-samples.vercel.app/vue-apollo
- StoreΛ࣋ͨͳ͍ - DevToolsʢApolloʣରԠ Pros 😁
- Vue ApolloɺGraphQLͷґଘ - ঢ়ଶͷѲ͕ͮ͠Β͍ - ։ൃͷܧଓੑ - ίʔυྔͷ૿Ճ Cons
😵
·ͱΊ
7VFY 1JOJB $PNQPTJUJPO"1* 4UPSF 7VF"QPMMP 4UPSFͷ ୯Ұ ෳ ෳ ʢΩϟγϡʣ
%FW5PPMTରԠ ˓ ˚ º ˚ʢ"QPMMPʣ 5ZQF4DSJQUରԠ ˚ ˓ ˓ ˚ ଟػೳੑ ʢ443FUDʣ ˓ ˚ º º ίʔυͷهड़ྔ ˚ ˓ ˓ º
ࠓͷVueͷঢ়ଶཧVuex͚ͩͰͳ͍ɻ ΞϓϦέʔγϣϯͷنॏࢹ͢Δʹ ߹Θͤͯదͳঢ়ଶཧख๏Λબ ͢Δ͜ͱ͕େ
ࢀߟ • 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/
͓·͚ Vuex 5 RFCͷ·ͱΊ 2021/03/14 ࣌ https://github.com/kiaking/rfcs/blob/vuex-5/active-rfcs/0000-vuex-5.md
Storeͷఆٛ Storeͷར༻ Mutationsͷഇࢭ dispatchΛհͣ͞ actionsΛݺΔ ᶃ FluxϥΠΫύλʔϯΛഇࢭ
Optional Store Composition Store ΄΅Composition APIͦͷ·· ᶄ 2छྨͷStoreఆٛํ๏
Store 1 Store 2 ωετ͞ΕͨStoreʢModuleʣ Λഇࢭͯ͠ɺComposition Ͱ Storeͷ૬ޓར༻Λߦ͏ ᶅωετ͞ΕͨStoreΛഇࢭ
Store Component Optional Storeͷ߹ ܕΞϊςʔγϣϯΛ͚ͭΔ ʢComposition StoreͰෆཁʣ ༻࣌ ܕิ͕શʹޮ͘ ᶆ
શͳTypeScriptαϙʔτ