Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add useXXX helpers #1725

Open
kiaking opened this issue Apr 25, 2020 · 36 comments
Open

Add useXXX helpers #1725

kiaking opened this issue Apr 25, 2020 · 36 comments
Labels
4.x enhancement New feature or request

Comments

@kiaking
Copy link
Member

kiaking commented Apr 25, 2020

What problem does this feature solve?

Currently, we don't have mapXXX helpers equivalent feature when using Vuex 4 in composition api. It would be nice to have as both a convenience and for better typing support.

What does the proposed API look like?

To smoothly support migration from Vuex 3, at first, we should align with existing mapXXX helpers.

All of the following codes are meant to be used inside setup hook.

useState

const { count, countAlias, countPlusLocalState } = useState({
  // arrow functions can make the code very succinct!
  count: state => state.count,

  // passing the string value 'count' is same as `state => state.count`
  countAlias: 'count',

  // to access local state with `this`, a normal function must be used
  countPlusLocalState (state) {
    return state.count   this.localCount
  }
})

We should also support passing an array.

const { count } = useState([
  // map this.count to store.state.count
  'count'
])

useGetters

const { doneTodosCount, anotherGetter } = useGetters([
  'doneTodosCount',
  'anotherGetter'
])

Alias the name by passing an object.

const { doneCount } = useGetters({
  doneCount: 'doneTodosCount'
})

useActions

const { increment, incrementBy } = useActions([
  'increment', // map `increment()` to `store.dispatch('increment')`
  'incrementBy' // map `incrementBy(amount)` to `store.dispatch('incrementBy', amount)`
])

const { add } = useActions({
  add: 'increment' // map `add()` to `store.dispatch('increment')`
})

useMutations

const { increment, incrementBy } = useMutations([
  'increment', // map `increment()` to `store.commit('increment')`
  'incrementBy' // map `incrementBy(amount)` to `store.commit('incrementBy', amount)`
])

const { add } = useMutations({
  add: 'increment' // map `add()` to `store.commit('increment')`
})

Namespacing

All useXXX helpers should support passing namespace as the first argument.

const { a, b } = useState('some/nested/module', {
  a: state => state.a,
  b: state => state.b
})

const { foo, bar } = useActions('some/nested/module', [
  'foo',
  'bar'
])

And finally, useNamespacedHelpers.

const { useState, useActions } = useNamespacedHelpers('some/nested/module')

// look up in `some/nested/module`
const { a, b } = useState({
  a: state => state.a,
  b: state => state.b
})


// look up in `some/nested/module`
const { foo, bar } = useActions([
  'foo',
  'bar'
])

NOTE

There's an issue #1695 that proposes adding useModule helper that returns the whole module as an object. We could do the follow-up PR to tackle this idea as well.

@lmiller1990
Copy link
Member

An alternative would be to provide these in a separate library, and users could use to include that, too, if you want to keep core very small and simple.

I like the proposal in general - it mirrors the mapXXX helpers nicely.

@kiaking
Copy link
Member Author

kiaking commented Apr 30, 2020

An alternative would be to provide these in a separate library, and users could use to include that, too, if you want to keep core very small and simple.

Ah good point. Maybe something we should think about when designing Vuex 5. As of Vuex 4, mapXXX is already in the core and to align with Vuex 3, I think it makes more sense to have it in the core 👍

@libbGit
Copy link

libbGit commented Jun 2, 2020

in vuex 4.0.0-beta.2, I don't find useState, just mapState has been given, how do i use mapState it?

@libbGit
Copy link

libbGit commented Jun 2, 2020

An alternative would be to provide these in a separate library, and users could use to include that, too, if you want to keep core very small and simple.

Ah good point. Maybe something we should think about when designing Vuex 5. As of Vuex 4, mapXXX is already in the core and to align with Vuex 3, I think it makes more sense to have it in the core 👍

how do i use mapXXX?

in the setup function, i can't get the store state.

import { mapState, mapMutations, mapGetters, mapActions } from "vuex"; 
setup(props, context) { 
     let state = mapState("user",["name"]); 
     // state.name is a mappedState function, not a value
}

@kiaking
Copy link
Member Author

kiaking commented Jun 2, 2020

You can't use mapXXX helpers inside setup hook. And useXXX helpers are not there too. Please wait until this issue is being tackled! 🙇

@libbGit
Copy link

libbGit commented Jun 2, 2020

You can't use mapXXX helpers inside setup hook. And useXXX helpers are not there too. Please wait until this issue is being tackled! 🙇

okay, thx, Wishing good news will come soon.

@PatrykWalach
Copy link

PatrykWalach commented Sep 4, 2020

this is pretty easy to implement, mappedActions have to be bound with $store

const mappedActions = mapActions('user', ['login'])
const store = useStore()
const login = mappedActions.login.bind({ $store: store })

useActions can be created like a so

const useActions = (...args) => {
  const $store = useStore()

  return Object.fromEntries(
    Object.entries(mapActions(...args)).map(
      ([key, value]) => [
        key,
        value.bind({
          $store,
        }),
      ],
    ),
  )
}

and if you use typescript you have to cast all the overloaded types

const useActions = ((...args: [any, any]) => {
  const $store = inject('store')

  return Object.fromEntries(
    (Object.entries(mapActions(...args))).map(
      ([key, value]: [string, ActionMethod]) => [
        key,
        value.bind({
          $store,
        }),
      ],
    ),
  )
}) as Mapper<ActionMethod> &
  MapperWithNamespace<ActionMethod> &
  MapperForAction &
  MapperForActionWithNamespace

the same probably goes for mapMutations

mappedGetters and mappedState in vue 2 work straight up, since this in computed is bound to the instance.

 const getters = mapGetters('updateManager', ['rejected', 'isUpdating'])
 const rejected = computed(getters.rejected)

But if it's necessary in vue 3, this can be bound as well.

 const getters = mapGetters('updateManager', ['rejected', 'isUpdating'])
 const rejected = computed(getters.rejected.bind({ $store: store }))

So useGetters would look like this:

const useActions = (...args) => {
  const $store = useStore()

  return Object.fromEntries(
    Object.entries(mapGetters(...args)).map(
      ([key, value]) => [
        key,
        computed(value.bind({
          $store,
        })),
      ],
    ),
  )
}

and this should be casted with as Mapper<ComputedRef<any>> & MapperWithNamespace<ComputedRef<any>>

All the hooks
import {
mapGetters,
mapState,
Mapper,
MapperWithNamespace,
MapperForState,
MapperForStateWithNamespace,
Computed,
MapperForActionWithNamespace,
MutationMethod,
mapMutations,
mapActions,
MapperForMutationWithNamespace,
MapperForMutation,
ActionMethod,
MapperForAction,
} from 'vuex'
import { ComputedRef, computed, inject } from '@vue/composition-api'

const createActionHook = (
mapFunction: Mapper<any> & MapperWithNamespace<any>,
) =>
((...args: [any, any]) => {
  const $store = inject('store')

  return Object.fromEntries(
    Object.entries(mapFunction(...args)).map(
      ([key, value]: [string, any]) => [
        key,
        value.bind({
          $store,
        }),
      ],
    ),
  )
}) as Mapper<any> & MapperWithNamespace<any>

export const useMutation = createActionHook(mapMutations) as Mapper<
MutationMethod
> &
MapperWithNamespace<MutationMethod> &
MapperForMutation &
MapperForMutationWithNamespace

export const useActions = createActionHook(mapActions) as Mapper<ActionMethod> &
MapperWithNamespace<ActionMethod> &
MapperForAction &
MapperForActionWithNamespace

const createComputedHook = (
mapFunction: Mapper<Computed> & MapperWithNamespace<Computed>,
) =>
((...args: [any, any]) => {
  const $store = inject('store')

  return Object.fromEntries(
    Object.entries(mapFunction(...args)).map(
      ([key, value]: [string, Computed]) => [
        key,
        computed(
          value.bind({
            $store,
          }),
        ),
      ],
    ),
  )
}) as Mapper<ComputedRef<any>> & MapperWithNamespace<ComputedRef<any>>

export const useGetters = createComputedHook(mapGetters) as Mapper<
ComputedRef<any>
> &
MapperWithNamespace<ComputedRef<any>>

export const useState = createComputedHook(mapState) as Mapper<
ComputedRef<any>
> &
MapperWithNamespace<ComputedRef<any>> &
MapperForState &
MapperForStateWithNamespace

@Stoom
Copy link

Stoom commented Sep 7, 2020

vuex-composition-helpers has a good implementation of this and support for typescript type interfaces

@shawnwildermuth
Copy link

The vuex-composition-helpers project only works with Vue 2 as it was said that this is what the API would look like in Vuex 4 but I haven't seen it working.

@Stoom
Copy link

Stoom commented Sep 9, 2020

I guess my point was the above examples is still missing type safety. This is Paramount to preventing bugs around the store. The library suggested was to demonstrate how you can have type safety... Better yet is a bird architecture that puts types first from the beginning so some crazy advanced typing isn't required.

@shawnwildermuth
Copy link

Wouldn't an API like this allow for typesafety:

setup() {
  const { isBusy, count } = useState({
    isBusy: s => s.state.isBusy,
    count: s => s.getters.itemCount  
  });
}

Maybe it's too verbose.

@Stoom
Copy link

Stoom commented Sep 9, 2020

The problem in the current API is vuex is wrapping things. So you define an action it takes in the action context and the payload. When you use it you only provide a payload and vuex magically fills in the action context. Same for getters and the state argument, and mutations and their arguments.

@shawnwildermuth
Copy link

Hmm....so is this an argument against Vuex in general?

@Stoom
Copy link

Stoom commented Sep 9, 2020

Kinda yeah, but I don't know a better way of supporting everything. Wishlist it would be nice to have fully typed commit and dispatch functions too.

Maybe having a more direct access to the module and make a module aware of all state, getters, mutations, and actions, as they would be used from a component. Then we could simply map the module and interact with it like any other class...

@shawnwildermuth
Copy link

If all we're getting is protection against accidental mutation, couldn't we do that with just ref/reactive? Thinking out loud. I really like the simplicity of Vuex and don't think that type safety as as big of an issue for me.

@Stoom
Copy link

Stoom commented Sep 9, 2020

Yeah I mean with "hooks" and refs you could mimic vuex. As for type safety, we've found numerous bugs in or code where one developer assumed the store was one way, or during a refactor changed the interface.

@shawnwildermuth
Copy link

Sure, I get that. I don't mean hooks. I don't quite get useStore except for library code (import store from "@/store" is fine for most cases I think).

@Stoom
Copy link

Stoom commented Sep 10, 2020

Possibly things like Nuxt where you don't have direct access to the store? #blackmagic 🤢

@PatrykWalach
Copy link

I think the biggest issues with type safety are:

  1. typescript not having optional type arguments
    It's impossible to just pass type of state
useState<UserState>('user', ['name', 'id'])

The second type has to be passed as well

useState<UserState, 'name' | 'id'>('user', ['name', 'id'])
useState<UserState, { name(state: UserState): string, id: string }>('user', { name: state => state.name, id: 'id' })

The only way around this is to create a thunk

const { useState: useUserState } = createNamespacedHooks<UserState, UserGetters>('user')

useUserState(['name', 'id'])

I create the ./helpers file where I store all hooks to all modules to avoid creating them in multiple components

  1. dispatch and commit
    They can be easily typed with a pattern used in redux
const createAction = <P>(type: string) => {
  const action = (payload: P) => ({ type, payload})
  action.toString = () => type
  return action
}


const fetchUserData = createAction<{ userId: number }>('fetchUserData ')

const actions = {
  [fetchUserData](_, { userId }: { userId: string }){
  ...
  }
}

dispatch(fetchUserData({ userId: 2 }))

it's also possible to create a function that would type the payload inside of action tree

const actions = createActions((builder) => builder
  .addAction(fetchUserData, (context, { userId }) => {
    ...
  })
)
  1. getters not being typed
    I worked around this like that
interface UserGetters {
  isLoggedIn: boolean
}

type UserGetterTree = CreateGetterTree<UserState, RootState, RootGetters, UserGetters>

const getters: UserGetterTree = {
  isLoggedIn: (state) => !!state.name
}

then I can use UserGetters to type the hooks

@Stoom
Copy link

Stoom commented Sep 10, 2020

This example shows how it's difficult for a developer to keep type safety, vs just having a simple solution without a lot of boilerplate. Even in commit/dispatch example there's a typing error where userId was typed as a number in the crate, but then a string when declaring the actual function.

const​ ​createAction​ ​=​ ​<P>(type​: ​string)​ ​=>​ ​{​
  ​const​ ​action​ ​=​ ​(payload​: ​P)​ ​=>​ ​({​ type​,​ payload​})​
  ​action.toString​ ​=​ ​()​ ​=>​ ​type​
  ​return​ ​action​
​}​


​const​ ​fetchUserData​ ​=​ ​createAction<{​ ​userId​: ​number​ ​}>('fetchUserData ')​

​const​ ​actions​ ​=​ ​{​
  ​[fetchUserData](_,​ ​{​ userId ​}​: ​{​ ​userId​: ​string​ ​}){​
  ...
  ​}​
​}​

​dispatch(fetchUserData({​ ​userId​: ​2​ ​}))

@Stoom
Copy link

Stoom commented Sep 10, 2020

Maybe the typing discussion should be moved to a different issue?

@petervmeijgaard
Copy link

petervmeijgaard commented Oct 12, 2020

It's been more than a month already. Is there an update on this ticket? I'd really love to have first-party support for these useX-hooks. It'll clean up my project quite some bit.

@ux-engineer
Copy link

I've been following this issue, and as there have been discussion about type-safety with Vuex 4, I'd like to add my summary of some problem points along with an example repo of how to type Vuex 4 store... (Feel free to mark this as off-topic, if so.)

@xiaoluoboding
Copy link

Recently, I implement the all things in this issue talk about, and I'm consider about to contribute in vuex, then i found that, the useXXX helpers proposal already exist nearly eight months.

checkout https://github.com/vueblocks/vue-use-utilities#vuex

@vueblocks/vue-use-vuex - Use Vuex With Composition API Easily. It build on top of vue-demi & @vue/compostion-api. It works both for Vue 2 & 3, TypeScript Supported too.

useVuex

  • useState - same as mapState
  • useGetters - same as mapGetters
  • useMutations - same as mapMutations
  • useActions - same as mapActions

useStore

  • useStore - same as Vuex 4.x composition api useStore

Usage

useState

import { useVuex } from '@vueblocks/vue-use-vuex'

export default {
  // ...
  setup () {
    // Use the useState as you would use mapState
    const { useState } = useVuex()

    return {
      // mix this into the outer object with the object spread operator
      ...useState({
        // arrow functions can make the code very succinct!
        count: state => state.count,

        // passing the string value 'count' is same as `state => state.count`
        countAlias: 'count',

        // to access local state with `this`, a normal function must be used
        countPlusLocalState (state) {
          return state.count + this.localCount
        }
      }),
      ...mapState([
        // map count<ComputedRef> to store.state.count
        'count'
      ])
    }
  }
}

useGetters

import { useVuex } from '@vueblocks/vue-use-vuex'

export default {
  // ...
  setup () {
    // Use the useGetters as you would use mapGetters
    const { useGetters } = useVuex()

    return {
      // mix the getters into outer object with the object spread operator
      ...useGetters([
        'doneTodosCount',
        'anotherGetter',
        // ...
      ]),
      // if you want to map a getter to a different name, use an object:
      ...mapGetters({
        // map `doneCount<ComputedRef>` to `this.$store.getters.doneTodosCount`
        doneCount: 'doneTodosCount'
      })
    }
  }
}

useMutations

import { useVuex } from '@vueblocks/vue-use-vuex'

export default {
  // ...
  setup () {
    // Use the useMutations as you would use mapMutations
    const { useMutations } = useVuex()

    return {
      ...useMutations([
        'increment', // map `increment()` to `this.$store.commit('increment')`

        // `mapMutations` also supports payloads:
        'incrementBy' // map `incrementBy(amount)` to `this.$store.commit('incrementBy', amount)`
      ]),
      ...useMutations({
        add: 'increment' // map `add()` to `this.$store.commit('increment')`
      })
    }
  }
}

useActions

import { useVuex } from '@vueblocks/vue-use-vuex'

export default {
  // ...
  setup () {
    // Use the useActions as you would use mapActions
    const { useActions } = useVuex()

    return {
      ...useActions([
        'increment', // map `increment()` to `this.$store.dispatch('increment')`

        // `mapActions` also supports payloads:
        'incrementBy' // map `incrementBy(amount)` to `this.$store.dispatch('incrementBy', amount)`
      ]),
      ...useActions({
        add: 'increment' // map `add()` to `this.$store.dispatch('increment')`
      })
    }
  }
}

namespacing

also support

// Get namespaced component binding helpers in useVuex
import { useVuex } from '@vueblocks/vue-use-vuex'

export default {
  setup () {
    const { mapState, mapActions } = useVuex('some/nested/module')

    return {
      // look up in `some/nested/module`
      ...mapState({
        a: state => state.a,
        b: state => state.b
      })
      // look up in `some/nested/module`
      ...mapActions([
        'foo',
        'bar'
      ])
    }
  }
}

It seems familiar right? Yeah, You could think of @vueblocks/vue-use-vuex as a wrapper of Vuex Helpers

Read Docs

But, I'm didn't think too much about type safety, and i am still learning TypeScript. If you're interested it, Please help me improve it.

PRs Welcome in @vueblocks/vue-use-utilities

@Alanscut
Copy link
Contributor

I find the proposal has been stalled for a long time.Is it still under development?Hope this proposal can be realised soon.

@kiaking kiaking removed the proposal label Jan 5, 2021
@kiaking kiaking added the enhancement New feature or request label Jan 5, 2021
@hi-reeve
Copy link

hi-reeve commented Jan 6, 2021

i hope this will be implemented very soon

kawamataryo added a commit to kawamataryo/zenn-articles that referenced this issue Feb 11, 2021
diff --git a/articles/vuex4-with-typescript-and-composition-api.md b/articles/vuex4-with-typescript-and-composition-api.md
new file mode 100644
index 0000000..5c5eb77
--- /dev/null
+++ b/articles/vuex4-with-typescript-and-composition-api.md
@@ -0,0 +1,29 @@
+---
+title: "Vuex4 から始める Vuex 〜TSとComposition API を添えて〜"
+emoji: "4️⃣"
+type: "tech" # tech: 技術記事 / idea: アイデア
+topics: ["vue", "vuex", "typescript"]
+published: false
+---
+
+## 疑問
+
+- getters は Composition API でどう使うの?
+  - mapGetters は CompositionAPI で使えない。
+- Composition API で書いた場合と Optional API で書いた場合の比較
+- mutations, actions 違いは何?
+  - dispatch と commit は?
+  - mutations の実行は commit
+  - actions の実行は dispatch
+
+# 参考
+
+- [vuex/examples/composition at 4.0 · vuejs/vuex](https://github.com/vuejs/vuex/tree/4.0/examples/composition)
+
+useXXX を作りたいための Issue
+
+- [Add useXXX helpers · Issue #1725 · vuejs/vuex](vuejs/vuex#1725)
+
+Vuex で compositionAPI を使うためのライブラリ
+
+- [greenpress/vuex-composition-helpers: A util package to use Vuex with Composition API easily.](https://github.com/greenpress/vuex-composition-helpers#typescript-mappings)
kawamataryo added a commit to kawamataryo/zenn-articles that referenced this issue Feb 24, 2021
diff --git a/articles/intoroduce-vuex4-with-composition-api.md b/articles/intoroduce-vuex4-with-composition-api.md
new file mode 100644
index 0000000..4a963b7
--- /dev/null
+++ b/articles/intoroduce-vuex4-with-composition-api.md
@@ -0,0 +1,432 @@
+---
+title: "Vuex4 を Composition API + TypeScript で入門する"
+emoji: "️️🔋"
+type: "tech"
+topics: ["vuex", "vue", "typescript"]
+published: true
+---
+
+今月初めにリリースされた Vuex4 を Composition API + TypeScript で試してみたのでそのメモです。
+この記事は以下バージョンにて検証しています。
+- [vuejs/vue](https://github.com/vuejs/vue) 3.0.5
+- [vuejs/vuex](https://github.com/vuejs/vuex) 4.0.0
+
+# Vuexとは?
+Vuex は、Vue.js 公式の状態管理ライブラリです。
+Vue アプリケーション内に、どの階層のコンポーネントからでもデータを取得・更新できる単一のデータストアを作ることができます。Vuex を使うことで複数のコンポーネントで使う共有データの Props/Emit による過剰なバケツリレーが不要になります。
+
+また、複雑になりがちな状態管理において以下の図のように特定のルールで制約を与えることでデータの流れを一元化して、コードの構造と保守性を向上させることができます。
+
+![](https://i.gyazo.com/57159fbe2e8c50e475808076c325832f.png)
+([What is Vuex?](https://next.vuex.vuejs.org/))
+
+# 使い方
+Vue CLI で Vue3 + TypeScript の環境構築が行われている前提で、簡単な Todo アプリの状態管理を例に Vuex の一連の流れをみていきます。
+
+## インストール + 初期設定
+
+まず Vuex を依存に追加します。
+
+:::message
+Vuex4 は 2021/02/24 時点で vuex ではなく vuex@next というパッケージ指定なので注意してください。
+:::
+
+```
+yarn add vuex@next --save
+```
+
+つぎに Vuex のストアの構造を定義する`store.ts`を作成します。
+
+```
+mkdir src/store
+touch store/store.ts
+```
+
+```ts:src/store/store.ts
+import { InjectionKey } from 'vue';
+import { createStore, Store, useStore as baseUseStore } from "vuex";
+
+// stateの型定義
+type State = {};
+
+// storeをprovide/injectするためのキー
+export const key: InjectionKey<Store<State>> = Symbol();
+
+// store本体
+export const store = createStore<State>({});
+
+// useStoreを使う時にキーの指定を省略するためのラッパー関数
+export const useStore = () => {
+  return baseUseStore(key);
+}
+```
+
+`useStore`のラッパー関数はなくても良いのですが、毎回コンポーネントでストアを取得するために`useStore(key)`と key を指定するのが面倒なので定義しています。コンポーネントからは`store.ts`の`userStore()`を使うようにします([参考](https://next.vuex.vuejs.org/guide/typescript-support.html#simplifying-usestore-usage))。
+
+そしてストアを`main.ts`でプラグインとして設定します。
+
+```ts:main.ts
+import { createApp } from "vue";
+import App from "./App.vue";
+import { key, store } from "./store/store";
+
+const app = createApp(App);
+
+app.use(store, key);
+
+app.mount("#app");
+```
+
+これで準備は完了です。
+
+## State
+state は Vuex で管理する状態そのものです。state はリアクティブなデータとなり、state の変更は Vue.js の変更検知の対象となります。
+
+### ストアでの定義
+
+まず `store.ts`で state の型定義を追加します。
+
+```ts:store.ts
+// ...
+type TodoItem = {
+  id: number;
+  title: string;
+  content: string;
+  completed: boolean;
+};
+
+type State = {
+  todoItems: TodoItem[];
+};
+// ...
+```
+
+そしてストアの実装である`createStore`の引数オブジェクトに`state`プロパティを追加して初期値を設定します。
+
+```ts:store.ts
+// ...
+export const store = createStore<State>({
+   state: {
+     todoItems: [
+      {
+         id: 1,
+        title: "foo",
+        content: "bar",
+        completed: false
+      }
+    ]
+  }
+});
+// ...
+```
+
+### コンポーネントでの利用
+
+state の利用は`store.ts`に定義した`useStore`を使います。ラッパー関数にて`provide/inject`のキーは設定しているので、この場でキーの指定は不要です。
+
+```vue:src/store/store.ts
+<template>
+  <div>
+    <div v-for="item in todoItems" :key="item.id">
+      <p>{{ item.title }}</p>
+      <p>{{ item.content }}</p>
+      <p>{{ item.completed ? "✅" : "-" }}</p>
+      <hr />
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, computed } from "@vue/runtime-core";
+import { useStore } from "@/store/store"; // store/store.tsのものを利用
+
+export default defineComponent({
+  setup() {
+    const store = useStore();
+    const todoItems = computed(() => store.state.todoItems);
+
+    return {
+      todoItems
+    };
+  }
+});
+</script>
+```
+
+`store.sate`で取得できる値は`store.ts`で型定義した`type State`の型が設定されています。 なので state は型安全に使えます。
+
+```ts
+// 例
+// idはnumberと推論される
+const id = store.state.todoItems[0].id
+```
+
+## Mutations
+mutations は state を変更する関数です。
+Vuex で state を変更する際は直接 state を書き換えるのではなく、必ず mutation を経由して行うのが Vuex のルールです。
+
+### ストアでの定義
+
+まず、mutation の関数名を管理する`mutationType.ts`を作成します。
+関数名を定数や Enum で一元管理しておくと、後述する commit の際に定数で指定できるので便利です。
+
+```ts:src/store/mutationType.ts
+export const ADD_TODO_ITEM = "ADD_TODO_ITEM";
+```
+
+次に`store.ts`の createStore の引数のオブジェクトに mutations をプロパティを追加します。
+mutation の関数の第 1 引数には state、第 2 引数には後述する commit の際の引数を受け取れます。その引数を使って関数の内部で state を更新します。
+
+
+```ts:src/store/store.ts
+// ...
+import * as MutationTypes from "./mutationTypes";
+
+// ...
+export const store = createStore<State>({
+  // ...
+  mutations: {
+    [MutationTypes.ADD_TODO_ITEM](state, todoItem: TodoItem) {
+      state.todoItems.push(todoItem);
+    }
+  }
+});
+```
+
+### コンポーネントでの利用
+コンポーネント側での mutations の実効は`store.commit`を使います。
+以下 TodoItem を追加する例です。
+
+```vue:src/components/TodoItemForm.vue
+<template>
+  <form>
+    <label for="title">
+      title
+      <input type="text" id="title" v-model="form.title" />
+    </label>
+    <label for="content">
+      content
+      <input type="text" id="content" v-model="form.content" />
+    </label>
+    <input type="submit" value="submit" @click.prevent="onSubmit" />
+  </form>
+</template>
+
+<script lang="ts">
+import { defineComponent, reactive } from "@vue/runtime-core";
+import { useStore } from "@/store/store";
+import * as MutationTypes from "@/store/mutationTypes";
+
+export default defineComponent({
+  setup() {
+    const form = reactive({
+      title: "",
+      content: ""
+    });
+
+    const clearForm = () => {
+      form.title = "";
+      form.content = "";
+    };
+
+    const store = useStore();
+
+    const onSubmit = () => {
+      store.commit(MutationTypes.ADD_TODO_ITEM, {
+        id: Math.floor(Math.random() * 100000), // 仮でランダムなIDを設定
+        content: form.content,
+        title: form.title
+      });
+      clearForm();
+    };
+
+    return {
+      onSubmit,
+      form
+    };
+  }
+});
+</script>
+```
+
+state の変更は、`onSubmit`の`store.commit`部分で行っています。
+第 1 引数に mutation の関数名の文字列(ここでは mutationTypes の定数を利用)を指定して、第 2 引数で対象の mutation に渡す引数を指定しています。
+
+```ts
+const onSubmit = async () => {
+  await store.commit(MutationTypes.ADD_TODO_ITEM, {
+    id: Math.floor(Math.random() * 100000), // 仮でランダムなIDを設定
+    content: form.content,
+    title: form.title
+  });
+  clearForm();
+};
+```
+
+注意する点として、mutation は同期的に実効されます。非同期処理を mutation の関数内で行はないようにしてください。
+※ mutation 内部で非同期処理を書くことは出来ますが、store.commit が Promise を返さないので非同期処理を待つことが出来ない。
+
+:::message
+コンポーネントから mutation を実効せず、必ず後述の actions を介して mutation を実効という思想もあります。記述は増えますがそちらのほうがストアのデータの流れはシンプルになるので良さそうな気がします。
+:::
+
+## Actions
+action は同期・非同期を問わない state に関するあらゆる操作を定義するものです。
+一般的には非同期処理を含む state の変更のトリガーに用いられます。
+
+### ストアでの定義
+TodoItems の初期値を API 経由で取得する例です。
+action も mutation と同様に定数で管理するためにまず`actionTypes.ts`を追加します。
+
+```ts:src/store/actinonTypes.ts
+export const INITIALIZE_TODO_ITEMS = "INITIALIZE_TODO_ITEMS";
+```
+
+store の変更は mutation 経由して行うので`mutationTypes.ts`にも定数を追加します。
+
+```ts:src/store/mutationTypes.ts
+// ...
+export const INITIALIZE_TODO_ITEMS = "INITIALIZE_TODO_ITEMS";
+```
+
+次に`store.ts`の createStore の引数のオブジェクトに actions プロパティを追加します。
+actions の関数の第 1 引数には`state`, `commit`, `getters`を含むオブジェクトが受け取れます。actions の関数で async/await を使うことで非同期処理をハンドルできます。
+
+```ts:src/store/store.ts
+// ...
+import * as ActionTypes from "./actionTypes";
+
+// ...
+export const store = createStore<State>({
+  // ...
+  mutations: {
+    // ...
+    [MutationTypes.INITIALIZE_TODO_ITEMS](store, todoItems: TodoItem[]) {
+      store.todoItems = todoItems;
+    }
+  },
+  actions: {
+    async [ActionTypes.INITIALIZE_TODO_ITEMS]({ commit }) {
+      const todoItems = await fetchAllTodoItems(); // TodoItemsを取得するAPIコール
+      commit(ActionTypes.INITIALIZE_TODO_ITEMS, todoItems);
+    }
+  }
+});
+```
+
+今回は todoItem の一覧を取得する`fetchAllTodoItems`を実効してその戻値を mutation を介して TodoItems の初期化に使っています。
+
+
+### コンポーネントでの利用
+
+コンポーネントから action を利用する場合は`store.dispatch`を使います。
+以下はコンポーネントがマウントされるタイミングで dispatch を呼ぶ例です。dispatch は Promise を返すので async/await で処理を待つことができます。
+
+```vue:src/components/TodoItems.vue
+<!-- ... -->
+
+<script lang="ts">
+import { defineComponent, computed } from "@vue/runtime-core";
+import { useStore } from "@/store/store"; // store/store.tsのものを利用
+import * as ActionTypes from "@/store/actionTypes"
+
+export default defineComponent({
+  async setup() {
+    const store = useStore();
+    const todoItems = computed(() => store.state.todoItems);
+
+    onMounted(async () => {
+      await store.dispatch(ActionTypes.INITIALIZE_TODO_ITEMS)
+    })
+
+    return {
+      todoItems
+    };
+  }
+});
+</script>
+```
+
+ここでは dispatch に引数を渡していませんが、引数を受け取る Actions の場合は第 2 引数で Actions に渡す引数を指定出来ます。
+
+## Getters
+getter は state からの算出データを定義するものです。state に何らかの処理をした値を複数の場所で利用する場合は、 getter を定義しておくと便利です。
+
+
+### ストアでの定義
+`store.ts`の createStore の引数のオブジェクトに getters プロパティを追加して、完了済みの TodoItems を取得する getter を定義します。
+
+```ts:src/store/store
+export const store = createStore<State>({
+  // ...
+  getters: {
+    completedTodoItems: store => {
+      return store.todoItems.filter(todo => todo.completed);
+    }
+  }
+}
+```
+
+:::message
+Vue 3.0 の場合は Vue2 系と違い getter の結果がキャッシュされないという不具合があるようです。これは[こちらのPR](vuejs/vuex#1883
+Vue 3.1 で解消される見込みとのことです。
+:::
+
+### コンポーネントでの利用
+
+コンポーネントでは`store.getters`で利用出来ます。
+ただ、state と違い`store.getters`の戻値は any なので型推論が効きません。
+
+```vue:src/components/completedTodoItems.vue
+<!-- ... -->
+<script lang="ts">
+import { defineComponent, computed } from "@vue/runtime-core";
+import { useStore } from "@/store/store"; // store/store.tsのものを利用
+
+export default defineComponent({
+  setup() {
+    const store = useStore();
+    const todoItems = computed(() => store.getters.completedTodoItems); // todoItemsはComputedRef<any>と推論される
+
+    return {
+      todoItems
+    };
+  }
+});
+</script>
+```
+
+# その他考慮点
+使ってみてわかった Vuex4 採用に関する考慮点を書きます。
+
+## TypeScriptの対応がまだ不完全
+
+Vuex の課題として長らく上がっているのが TypeScript への対応だと思います。
+commit、dispatch、getters をタイプセーフに実効することが出来ず、型補完を効かせるには [vuex-type-helper](https://github.com/ktsn/vuex-type-helper) 等のプラグインを別途使う必要がありました。
+
+TypeScript 4.1 で template literal types が入ったことですし Vuex4 で何らかの改善があるのかなと期待してたところですが、まだ完全な対応は難しいようです。
+Vuex4 でも Vuex3 と同様、commit、dispatch、getters で型補完は効きません。別 Plugin を利用するか、独自で型定義を設定して何らかの対応をする必要があるようです。
+
+(独自で型定義する例)
+https://dev.to/shubhadip/vue-3-vuex-4-modules-typescript-2i2o
+
+## mapHelpersがComposition APIでは使えない
+Vue4 + Composition API の場合の現状の問題点が、複数のストア操作を一括で定義できる mapHelpers(`mapState`、 `mapGetters`、`mapActions`、`mapMutations`)が使えないことです。
+※ Vuex4 でも Option API を使う場合は mapHelpers を使えます。
+
+Vuex を使っている場合はほぼ mapHelpers を使っていると思うので、Vuex4 + Composition API の書き換えの障害になると思われます。
+
+一応以下の Issue が上がっています。早く実装されると良いですね。
+
+vuejs/vuex#1725
+
+# 終わりに
+
+以上、Vuex4 + Composition API + TypeScript の簡単な紹介でした。
+TypeScript の対応や mapHelpers など今すぐの採用は迷うところですが、引き続き動向を追っていきたいです。
+その他 Vuex の Moudule 機能が中々ややこしいのでまた別でまとめようと思っています。
+
+# 参考
+
+- [Vuex公式ドキュメント](https://next.vuex.vuejs.org/)
+- [みんなのVue.js:書籍案内|技術評論社](https://gihyo.jp/book/2021/978-4-297-11902-7)
\ No newline at end of file
kawamataryo added a commit to kawamataryo/zenn-articles that referenced this issue Mar 3, 2021
diff --git a/.cache/kvs-node-localstorage/proofdict-lastUpdated b/.cache/kvs-node-localstorage/proofdict-lastUpdated
index 3c67890..bcb6d24 100644
--- a/.cache/kvs-node-localstorage/proofdict-lastUpdated
+++ b/.cache/kvs-node-localstorage/proofdict-lastUpdated
@@ -1 +1 @@
-1614153630272
\ No newline at end of file
+1614772112314
\ No newline at end of file
diff --git a/articles/mysteriously-increasing-storage.md b/articles/mysteriously-increasing-storage.md
deleted file mode 100644
index b5235dc..0000000
--- a/articles/mysteriously-increasing-storage.md
+++ /dev/null
@@ -1,72 +0,0 @@
----
-title: "Cloud Functions for Firebaseの利用で、異様にGCPのStorageが消費されると思ったら.."
-emoji: "📛"
-type: "tech"
-topics: ["firebase", "gcp", "cloudFunctions", "CloudStorage"]
-published: true
----
-
-知っている人には当たり前の知識かもですが、地味に不安だったことなのでメモ。
-
-# 起こったこと
-
-Firebase でアプリを作っていたら、なぜか Storage のリソースが異様に消費されてることに気がつきました。
-
-![](https://storage.googleapis.com/zenn-user-upload/zbsz812u2ebw83a10yf82baba0qo)
-
-このプロジェクト自体は Cloud Functions for Firebase をメインに使っていて、Storage 自体にはほぼファイルはありませんでした。
-
-しかし、上記画像の通り異様にアクセスされています。
-さらに GCP で Storage をみると見覚えのないファイルが大量に!
-
-![](https://storage.googleapis.com/zenn-user-upload/cm10fr7pw6wdzuphs03y2thb579s)
-
-なぜ...🤔
-
-# 原因
-
-原因は Cloud Functions for Firebase のデプロイ時に自動保存されるソースコードでした。
-
-Cloud Functions for Firebase のデプロイは以下手順で行われるようです。
-
-1. 関数のソースコードを含むアーカイブを Cloud Storage のバケットにアップロード
-2. アップロードされた ソースコードを使って Cloud Build でコンテナイメージを構築
-3. 構築されたイメージを Container Registry にプッシュ
-4. Functions のコール時に Container Registry からコンテナを作成して実行
-
-なので、ローカルから `firebase deploy --only functions` をするたびに Cloud Storage にバケットが作成されていたのですね。
-
-
-※ 上記内容は所属しているコミュニティ([エンジニアと人生](https://community.camp-fire.jp/projects/view/280040))の Slack で教えてもらったことです。疑問を分報に書いたら即 [@moga](https://twitter.com/_mogaming) 氏 からコメントもらえました。本当に感謝🙏
-
-# 対処
-
-このままだと Storage のリソースが消費され、無駄な費用がかかることもあるので適宜削除しましょう。
-Cloud Storage のライフサイクルで削除を実行するのがオススメです。
-
-以下手順で設定を行いました。
-
-(1)GCP で Storage コンソールを開く
-
-![](https://storage.googleapis.com/zenn-user-upload/mzrg1prfy9dipshf9u2iym2x9qb9)
-
-(2)対象のバケットを選択しライフサイクルのタブを開く
-
-![](https://storage.googleapis.com/zenn-user-upload/ymk2vb7hjh3tflx0tm1g38c62mca)
-
-
-(3)ルールを追加を選択し、「オブジェクトを削除する」を選ぶ
-
-![](https://storage.googleapis.com/zenn-user-upload/5ljzznfubqrb6rqeq4x9x2f0jedo)
-
-(4)条件を「年齢」値を「1 日」にする
-
-![](https://storage.googleapis.com/zenn-user-upload/hbz90zsl09xpstkcylfbs3aq94bn)
-
-これでバケットに追加されてから 24 時間経過後に自動的に削除されます。
-解決!
-
-※ バケットを削除しても Cloud Functions for Firebase の実行自体は問題なく動作します(起動用の実態は Container Registry にあるので)。
-
-# 参考
-- [Cloud Functions のデプロイ  |  Google Cloud Functions に関するドキュメント](https://cloud.google.com/functions/docs/deploying)
diff --git a/articles/vuex4-with-typescript-and-composition-api.md b/articles/vuex4-with-typescript-and-composition-api.md
deleted file mode 100644
index 1c4a9ab..0000000
--- a/articles/vuex4-with-typescript-and-composition-api.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: "Vuex4 から始める Vuex 〜TSとComposition API を添えて〜"
-emoji: "4️⃣"
-type: "tech" # tech: 技術記事 / idea: アイデア
-topics: ["vue", "vuex", "typescript"]
-published: false
----
-
-## 疑問
-
-- getters は Composition API でどう使うの?
-  - mapGetters は CompositionAPI で使えない。
-- Composition API で書いた場合と Optional API で書いた場合の比較
-- mutations, actions 違いは何?
-  - dispatch と commit は?
-  - mutations の実行は commit
-  - actions の実行は dispatch
-
-# 参考
-
-- [vuex/examples/composition at 4.0 · vuejs/vuex](https://github.com/vuejs/vuex/tree/4.0/examples/composition)
-
-useXXX を作りたいための Issue
-
-- [Add useXXX helpers · Issue #1725 · vuejs/vuex](vuejs/vuex#1725)
-
-Vuex で compositionAPI を使うためのライブラリ
-
-- [greenpress/vuex-composition-helpers: A util package to use Vuex with Composition API easily.](https://github.com/greenpress/vuex-composition-helpers#typescript-mappings)
-
-- [How to use Vuex mappers inside Vue Composition API? | by David Meir-Levy | Everything Full Stack | Medium](https://medium.com/everything-full-stack/how-to-use-vuex-mappers-inside-vue-composition-api-dbce8a39c06c)
\ No newline at end of file
@Fanna1119
Copy link

Any update on this?

@hi-reeve
Copy link

there is another package like this, maybe you can check this package

https://github.com/vueblocks/vue-use-utilities

@ux-engineer
Copy link

I think they are focused on Vuex version 5 RFC, which is totally new syntax for Vuex, similar to Composition API:

https://github.com/kiaking/rfcs/blob/vuex-5/active-rfcs/0000-vuex-5.md

vuejs/rfcs#270

@towertop
Copy link

I implemented similar useXXX helpers during an experimental migration for my team's project. It is a single ts source file that you can copy and use. But the code pattern is just my personal opinion and maybe not quite helpful.

Plz check out the code and demo from https://github.com/towertop/vuex4-typed-method .

@yulafezmesi
Copy link

One of the big deficiencies for vuex, any update on this?

@jaitaiwan
Copy link

There's already a PR handling this, just needs to be merged by the looks of things

@vnues
Copy link

vnues commented Sep 29, 2021

https://github.com/greenpress/vuex-composition-helpers

this can be satisfied

@asasugar
Copy link

https://github.com/asasugar/vuex-composition-maphooks 【modified to vuex helpers】

@michaelnwani
Copy link

A note for travelers whom somehow find their way here: the community has moved on to Pinia as the official Vue store: https://vuejs.org/guide/scaling-up/state-management.html#pinia

@asasugar
Copy link

asasugar commented Apr 8, 2022

https://github.com/asasugar/vuex-composition-maphooks 【modified to vuex helpers】

I have switched to Pinia

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.x enhancement New feature or request
Projects
None yet
Development

No branches or pull requests