Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
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
退屈なことはDevinにやらせよう〜〜Devin APIを使ったVisual Regression Testの自動追加〜
kawamataryo
5
1.8k
SaaS公式MCPサーバーをリリースして得た学び
kawamataryo
7
2k
Raycast AI APIを使ってちょっと便利な拡張機能を作ってみた / created-a-handy-extension-using-the-raycast-ai-api
kawamataryo
0
620
ts-morphのパフォーマンス改善Tips
kawamataryo
0
63
webpack to Rspack
kawamataryo
0
69
GitHub Actions と Datadog でコードベースの定点観測
kawamataryo
7
2k
個人開発駆動学習 / personal development driven learning
kawamataryo
1
250
GitHub Trending Bot, Sky Follower Bridge の紹介
kawamataryo
0
450
[Minecraft × ChatGPT] マイクラで作りたいものを伝えると魔法のように作ってくれるコマンドを作る
kawamataryo
0
2.4k
Other Decks in Technology
See All in Technology
Okta Identity Governanceで実現する最小権限の原則 / Implementing the Principle of Least Privilege with Okta Identity Governance
tatsumin39
0
140
旅で応援する✈️ NEWTが目指すコミュニティ支援とあたらしい旅行 / New Travel: Supporting by NEWT on Your Journey
mii3king
0
110
Azureコストと向き合った、4年半のリアル / Four and a half years of dealing with Azure costs
aeonpeople
1
250
AI時代におけるデータの重要性 ~データマネジメントの第一歩~
ryoichi_ota
0
710
[2025年10月版] Databricks Data + AI Boot Camp
databricksjapan
1
230
コンパウンド組織のCRE #cre_meetup
layerx
PRO
0
130
Bill One 開発エンジニア 紹介資料
sansan33
PRO
4
14k
クラウドとリアルの融合により、製造業はどう変わるのか?〜クラスメソッドの製造業への取組と共に〜
hamadakoji
0
260
HonoとJSXを使って管理画面をサクッと型安全に作ろう
diggymo
0
150
Claude Codeを駆使した初めてのiOSアプリ開発 ~ゼロから3週間でグローバルハッカソンで入賞するまで~
oikon48
10
5.2k
Introduction to Sansan, inc / Sansan Global Development Center, Inc.
sansan33
PRO
0
2.8k
Wasmの気になる最新情報
askua
0
180
Featured
See All Featured
Thoughts on Productivity
jonyablonski
70
4.9k
GitHub's CSS Performance
jonrohan
1032
470k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
190
55k
Building Better People: How to give real-time feedback that sticks.
wjessup
369
20k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
9
990
Writing Fast Ruby
sferik
629
62k
Building a Modern Day E-commerce SEO Strategy
aleyda
44
7.8k
Build The Right Thing And Hit Your Dates
maggiecrowley
37
2.9k
Done Done
chrislema
185
16k
Documentation Writing (for coders)
carmenintech
75
5.1k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
9
920
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.5k
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αϙʔτ