-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
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
Comments
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. |
Ah good point. Maybe something we should think about when designing Vuex 5. As of Vuex 4, |
in vuex 4.0.0-beta.2, I don't find useState, just mapState has been given, how do i use mapState it? |
how do i use mapXXX? in the setup function, i can't get the store state.
|
You can't use |
okay, thx, Wishing good news will come soon. |
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 mappedGetters and mappedState in vue 2 work straight up, since 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 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 All the hooksimport {
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 |
vuex-composition-helpers has a good implementation of this and support for typescript type interfaces |
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. |
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. |
Wouldn't an API like this allow for typesafety:
Maybe it's too verbose. |
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. |
Hmm....so is this an argument against Vuex in general? |
Kinda yeah, but I don't know a better way of supporting everything. Wishlist it would be nice to have fully typed 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... |
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. |
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. |
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). |
Possibly things like Nuxt where you don't have direct access to the store? #blackmagic 🤢 |
I think the biggest issues with type safety are:
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
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 }) => {
...
})
)
interface UserGetters {
isLoggedIn: boolean
}
type UserGetterTree = CreateGetterTree<UserState, RootState, RootGetters, UserGetters>
const getters: UserGetterTree = {
isLoggedIn: (state) => !!state.name
} then I can use |
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 })) |
Maybe the typing discussion should be moved to a different issue? |
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 |
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.) |
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 checkout https://github.com/vueblocks/vue-use-utilities#vuex
useVuex
useStore
UsageuseStateimport { 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'
])
}
}
} useGettersimport { 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'
})
}
}
} useMutationsimport { 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')`
})
}
}
} useActionsimport { 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')`
})
}
}
} namespacingalso 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 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 |
I find the proposal has been stalled for a long time.Is it still under development?Hope this proposal can be realised soon. |
i hope this will be implemented very soon |
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)
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
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
Any update on this? |
there is another package like this, maybe you can check this package |
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 |
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 . |
One of the big deficiencies for vuex, any update on this? |
There's already a PR handling this, just needs to be merged by the looks of things |
https://github.com/greenpress/vuex-composition-helpers this can be satisfied |
https://github.com/asasugar/vuex-composition-maphooks 【modified to vuex helpers】 |
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 |
I have switched to Pinia |
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
We should also support passing an array.
useGetters
Alias the name by passing an object.
useActions
useMutations
Namespacing
All
useXXX
helpers should support passingnamespace
as the first argument.And finally,
useNamespacedHelpers
.NOTE
There's an issue #1695 that proposes adding
useModule
helper that returns the wholemodule
as an object. We could do the follow-up PR to tackle this idea as well.The text was updated successfully, but these errors were encountered: