Open kiaking opened 4 years ago
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.
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 👍
in vuex 4.0.0-beta.2, I don't find useState, just mapState has been given, how do i use mapState it?
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
}
You can't use mapXXX
helpers inside setup
hook. And useXXX
helpers are not there too. Please wait until this issue is being tackled! 🙇
You can't use
mapXXX
helpers insidesetup
hook. AnduseXXX
helpers are not there too. Please wait until this issue is being tackled! 🙇
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 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>>
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:
setup() {
const { isBusy, count } = useState({
isBusy: s => s.state.isBusy,
count: s => s.getters.itemCount
});
}
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 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...
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 `./helpers` file where I store all hooks to all modules to avoid creating them in multiple components
2. dispatch and commit
They can be easily typed with a pattern used in redux
```tsx
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 `UserGetters` to type the hooks
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 useX
-hooks. It'll clean up my project quite some bit.
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 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.
useState
- same as mapState
useGetters
- same as mapGetters
useMutations
- same as mapMutations
useActions
- same as mapActions
useStore
- same as Vuex 4.x composition api useStoreimport { 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'
])
}
}
}
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'
})
}
}
}
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')`
})
}
}
}
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')`
})
}
}
}
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
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
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
https://github.com/asasugar/vuex-composition-maphooks 【modified to vuex helpers】
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.