With the emergence of Vue3, other core packages in the Vue community have been upgraded to support VUe3, such as VUE-Router and vuex. So what has changed with Vuex? Today let’s talk about Vuex.

We know that vue3 has two core upgrades: composition-API and better typescript support. Vuex4? Vex4 updates these things:

  • forvue3The support of
  • New Installation mode
  • A little bit bettertypescriptsupport
  • Directly in thevuexThe inside integrationLoggerThe plug-in

Let’s try vex4 first.

Basic use of VUex4 + VUE3

Let’s try to create a vue3 + vuex4 project:

This is done using vite

yarn create @vitejs/app
cd my-vite-app
yarn
yarn vuex@next 
Copy the code

We get the following directory:

➜. SRC ├── assets │ ├─ logo.png ├── HelloWorld ├─ main.tsCopy the code

Let’s use vuex4: create a store directory under SRC and create index.ts under store

import { createStore, createLogger } from 'vuex'
import config from '.. /config'

const store = createStore({
	state: {},
	mutations: {},
	actions: {},
	strict: config.isDev,
	plugins: config.isDev ? [createLogger()] : []
})

export default store
Copy the code

Vex4 provides us with the createStore method, which allows us to create a store and expose it to reference it in main.ts:

import { createApp } from 'vue'
import store from './store'
import router from './router'
import App from './App.vue'

const app = createApp(App)

app.use(store)
app.use(router)
app.mount('#app')
Copy the code

This is a bit different from vue2, where the store is injected via app.use.

Add modules

We don’t usually create variables directly in the root state, so we thought of module: create modules directory under store directory, create home and about directory under modules directory, create state and export:

// store/modules/home.index.ts
export interface HomeState {
	homeInfo: string
}

const state: HomeState = {
	homeInfo: 'info from home state model'
}
const getters = {}
const mutations = {}
const actions = {}

export default {
	namespaced: true,
	state,
	getters,
	mutations,
	actions
}

Copy the code
// store/modules/about.index.ts
export interface AboutState {
	aboutInfo: string
}

const state: AboutState = {
	aboutInfo: 'info from about state model'
}
const getters = {}
const mutations = {}
const actions = {}

export default {
	namespaced: true,
	state,
	getters,
	mutations,
	actions,
}
Copy the code

We’ll create an index.ts file in the modules directory and export the modules together:

// store/modules/index.ts
import home from './home'
import about from './about'

const modules = {
    home,
    about
}
console.log('modules',modules)

export default modules
Copy the code

Vex4 then provides us with the useStore method to get the state.

This article is mainly based on composition-API, so I won’t cover the use of mapState, mapGetters, mapActions, etc

Let’s try it out:

<template>
	<div class="home">
		<img alt="Vue logo" src=".. /assets/logo.png" />
		<p>{{ homeInfo }}</p>
	</div>
</template>

<script lang="ts">
import { useStore } from 'vuex'
import { computed,defineComponent } from 'vue'
import HelloWorld from '.. /components/HelloWorld.vue'
import styles from './example.module.css'

export default defineComponent({
	name: 'Home'.setup() {
		const store = useStore()
		const homeInfo = computed(() = > store.state.home.homeInfo)

		return {
			homeInfo
		}
	}
})
</script>
Copy the code

So far, we have implemented the basic use of configuring VUex4 + VUe3. What if we now need to fetch multiple states from home.vue? Then it would look like this:

.const store = useStore()
const homeInfo = computed(() = > store.state.home.homeInfo)
const value1 = computed(() = > store.state.home.value1)
const value2 = computed(() = > store.state.home.value2)
const value3 = computed(() = > store.state.home.value3)
...
Copy the code

Seems like a lot of duplicate code, right? In place of these repetitions, we can define an hooks:

import { computed } from 'vue'
import { useStore } from 'vuex'

const useVuexValue = (moduleName: string, storeKeys: Array<string>) = > {
	let values: any = []
	const moduleNames = moduleName.split('/')
	const state = useCurry(moduleNames)
	storeKeys.forEach((storeKey) = > {
		const value = computed(() = >state[storeKey])
		values.push(value ? value : null)})return values
}

const useCurry = (moduleNames: Array<string>) = > {
	const store = useStore()
	let state = store.state
	moduleNames.forEach((moduleName) = > {
		state = state[moduleName]
	})
	return state
}

export default useVuexValue
Copy the code

Then we get the state variable as:

import { useVuexValue } from '.. /hooks'.setup() {
    const [homeInfo,value1, value2] = useVuexValue('home'['homeInfo'.'value1'.'value2'])
    return {
        value1,
        value2,
        homeInfo
    }
}
...
Copy the code

If the home Module has detail, list, and other sub-modules, then we should fetch the data as follows:

.setup() {
    const [value1, value2] = useVuexValue('home/detail'['value1'.'value2'])
    return {
        value1,
        value2
    }
}
...
Copy the code

This method is similar to mapState, but it is our custom method. The same idea can encapsulate our own mutation, action, etc.

As you may have noticed as you read, we declare interface for each module, which we all know is a typescript type definition. But do we use it:

We can’t use typrscript to intelligently indicate our state. Is there a way? (Property) Store

. State: any, vuex4 provides a Store

.state.

Combining with the typescript

Typecript and Vex4 can be combined to modify store/index.ts:

// store/index.ts
import { InjectionKey } from 'vue'
import { createStore, createLogger, Store, useStore as baseUseStore } from 'vuex'
import modules from './modules'
import config from '.. /config'

interface StateType {

}

export const key: InjectionKey<Store<StateType>> = Symbol(a)const store: Store<StateType> = createStore({
	modules,
	mutations: {},
	actions: {},
	strict: config.isDev,
	plugins: config.isDev ? [createLogger()] : []
})

export function useStore() {
	return baseUseStore(key)
}
export default store

Copy the code

We can add a type definition to our stroe by exporting const key: InjectionKey

> = Symbol(). For StateType, we’ll have to find a way to include modules’ home and about type definitions, but generally speaking, our modules have a lot of modules, so we don’t define StateType directly in index.ts. We should pull out the StateType definition as types, so we create a new types directory under SRC and create a new index.ts:

// types/index.ts
import { HomeState } from '.. /store/modules/home'
import { AboutState } from '.. /store/modules/about'

type VuexModuleType = {
	home: HomeState
	about: AboutState
}

export type StateType = VuexModuleType
Copy the code

Here, we import the type files for each module and export them together. Under store/index.ts, we can fetch the StateType here:

import { StateType } from '.. /types'.export const key: InjectionKey<Store<StateType>> = Symbol(a)const store: Store<StateType> = createStore({
	modules,
	mutations: {},
	actions: {},
	strict: config.isDev,
	plugins: config.isDev ? [createLogger()] : []
})
....
Copy the code

Next, we create a new vuex.d.ts under SRC:

import { ComponentCustomProperties } from 'vue'
import { StateType } from './types'
import { Store } from 'vuex'

declare module '@vue/runtime-core' {
	// provide typings for `this.$store`
	interface ComponentCustomProperties {
		$store: Store<StateType>
	}
}
Copy the code

Now for the final step, we get the store exposed key in main.ts:

// main.ts
import { createApp } from 'vue'
import store, { key } from './store'
import router from './router'
import App from './App.vue'

const app = createApp(App)

app.use(router)
app.use(store, key)
app.mount('#app')

Copy the code

Now, how about we try it again?

We succeeded, and now our state can be intelligently signaled. It seems perfect, right? In fact, this method doesn’t work when we have child modules under a module, such as adding a child module: list under modules/home

// store/modules/home/list/index.ts
export interface HomeListState {
	homeList: string
}

const state: HomeListState = {
	homeList: 'list form home list state model'
}
const getters = {}
const mutations = {}
const actions = {}

export default {
	namespaced: true,
	state,
	getters,
	mutations,
	actions
}
Copy the code

Then import from home/index.ts:

// moudles/home/index.ts
import list from './list'
const getters = {}
const mutations = {}
const actions = {}

export default {
	namespaced: true,
	state,
	getters,
	mutations,
	actions,
	modules:{
		list
	}
}
Copy the code

However, when we use it, we have a problem:

You need to add the HomeListState of the list to the HomeState.

But the results were not as good as expected. Even vuex’s official repository has a similar problem: How to use modules in Vuex 4.0

So why does that happen? For the parent module, the child module is of type dynamic. Dynamic means dynamic and indeterminate. So how do we avoid this problem? For now, we can only push the child modules to the root module, which means do not nest modules within modules. Let’s just say it’s a shame. InjectionKey

> = Symbol() is, at first glance, an uncomfortable adaptation to typescript. For these reasons, Vex5 is completely rewritten to get full typescript support.

Vuex5 new features

From the latest vuue.js Global Online Conference, we can know that although Vuex4 is still in the beta stage, Vuex5 is already on the agenda. According to Vuex core contributor: Kia King, Vuex 5 will have the following features:

  • At the same time supportOption APIComposition API
  • The completeTypeScriptsupport
  • Give upMutations, onlyActions
  • Give upnested modulesTo replaceCompose
  • automaticCode splitting

Let’s take a look at the changes to Vex5.

Option API

Let’s first look at how to define a store based on the Option API

import { defineStore } from 'vuex'
const countStore = defineStore({
    name:'couter',})Copy the code

First, the store creation method changed from createStore to defineStore, which included a name as identify. Next we need to create some states:

import { defineStore } from 'vuex'
const countStore = defineStore({
    name:'couter'.state() {
        return {
            count: 1}}})Copy the code

Let’s create another getters:

import { defineStore } from 'vuex'
const countStore = defineStore({
    name:'couter',
    state () {
        return {
            count: 1}},getters: {
        double () {
            return this.count * 2}}})Copy the code

And of course actions:

import { defineStore } from 'vuex'
const countStore = defineStore({
    name:'couter',
    state () {
        return {
            count: 1}},getters: {
        double () {
            return this.count * 2}},actions:{
        increment () {
            this.count++
        }
    }
})
Copy the code

Now that the store is created, let’s look at how to use it:

import { createVuex } from 'vuex'
import countStore from './countStore'

const vuex = createVuex()
const counter = Vuex.store(countStore)

// counter.count -> 1
// counter.double -> 2
// counter.increment() -> Increment
// counter.count -> 2
// counter.double -> 4
Copy the code

In this way, all state variables get the full typescript type definition. You don’t need to define generics and interfaces yourself to get their type definitions. All the problems we just encountered in Vex4 have been solved.

Let’s see how to use vue with vex5. First again, we should import it from main.ts and inject it into the app:

import { createApp } from 'vue'
import { createVuex } from 'vuex'
import App from './app.vue'

const app = createApp()
const vuex = createVuex()

app.use(vuex)
app.mount('#el')
Copy the code

Then in the place we want to use:

<template>
	<div class="home">
		<p>count is:{{ couter.count }}</p>
        <p>double is:{{ couter.double }}</p>
        <button @click="counter.increment">
          Increment
        </button>
	</div>
</template>

<script lang="ts">
import { computed,defineComponent } from 'vue'
import counterStore from './counterStore'

export default defineComponent({
    name: 'Home'.computed: {
        ...mapStore({
            counter: counterStore
        })
    }
})
</script>
Copy the code

Vex5 provides hooks for mapStore to get state. Here mapStore is the syntactic sugar of this.$vuex.store(counterStore).

Here’s the magic:

We’ll see that the store definition looks almost identical to the VUE component definition. What’s the good of that? By doing so, we can easily move the logic of vUE components into VUex. It also ensures a high degree of consistency between the components and vuex logic at development time.

Now let’s look at composition-API.

Composition API

import { ref,computed } from 'vue'
import { defineStore } from 'vuex'

const counterSore = defineStore('counter'.() = > {
    const count = ref(1)
    const double = computed(() = > count.value * 2)

    function increment() {
        count.value++
    }

    return {
        count,
        double,
        increment
    }
})
Copy the code

If this looks familiar to you, it looks almost exactly like the way the VUE3-composition-API component is now defined. DefineStore is also used to define a Store and, unlike option-API, a callback is used instead of an Object. In this callback Function, we can use all the features contained in vue ReActivity. We also don’t need to define state, getters, actions up front. Here we just created a variable using ref, created a variable to calculate attributes using computed, and defined a simple increment function to change count.

With this mechanism, we can use all the other features in VUE3, such as Reactive, Watch, and so on. It is entirely up to us to combine freely, to maximize the hooks’ ideas. Also, this way, everything in typescript gets a full type definition, finally fully supporting typescript!!

Here’s how to use it in vUE components:

<template>
	<div class="home">
		<p>count is:{{ couter.count }}</p>
        <p>double is:{{ couter.double }}</p>
        <button @click="counter.increment">
          Increment
        </button>
	</div>
</template>

<script lang="ts">
import { computed,defineComponent } from 'vue'
import { useStore } from 'vuex'
import counterStore from './counterStore'

export default defineComponent({
    name: 'Home',
    setup () {
        const counter = useStore(counterStore)
        
        return {
            counter
        }
    }
})
</script>
Copy the code

Everything seemed so perfect. But there is still a problem, vuex5 does not have modules, how to do?

Store Composition

With no modules, Vex5 gives us Store Compostion:

import { defineStore } from 'vuex'
const greatStore = defineStore({
    name:'great'.state() {
        return {
            info: 'hello'}}})Copy the code

Then how do we get it from another store:

import { ref,computed } from 'vue'
import { defineStore } from 'vuex'
import greatStore from './greatStore'

const counterSore = defineStore('counter'.({ use }) = > {
    const great = use(greatStore)
    const count = ref(1)
    const double = computed(() = > count.value * 2)

    const countWithGreate = computed(() = > {
        return `${great.info}-${count.value}`
    })

    function increment() {
        count.value++
    }

    return {
        count,
        double,
        increment
    }
})
Copy the code

Vex5 gives us the use function to get other states. Let’s just say it’s perfect, because with Compose, we can combine other stores within any store to achieve the desired effect.

Implement our own state management

Through the new features of VUex5 mentioned above, we found that vuex5 made good use of the idea of composition-API and combined with the features of VUe3 reactivity to achieve such an effect. With that in mind, you can create your own state management, right? After hooks came out, many React developers abandoned Redux and implemented state management directly with useReducer in their projects. Can we do the same with VUE3? Let’s give it a try.

In the first step, we define our state by combining reactive:

// custom_store/info/state.ts
import { reactive } from 'vue'

export interface infoState {
	info: string
}

const state: infoState = {
	info: 'info from info state model'
}

export const createStore = () = > {
    return reactive(state)
}
Copy the code

Ok, that’s it. Ready to use. If THAT’s all I say, you’ll probably follow the wire and hammer me. Don’t worry. We’ll take it one step at a time. We created a state, and we use reactive because we use vue3-reactivity to make the state variable responsive. So far, we’ve just defined state.

Next, let’s define some methods to change state, namely actions:

// custom_store/info/action.ts
import { infoState } from "./state"

function chageInfo(state: infoState) {
    return (value: string) = > {
        state.info = value
    }
}
   
export function createAction(state: infoState) {
    return {
        chageInfo: chageInfo(state)
    }
}
Copy the code

Okay, now we have an action to change state. Let’s put them together to make them look more like vuex:

import {  readonly } from 'vue'
import { createAction } from './action'
import { createStore } from './state'

const state = createStore()
const action = createAction(state)

export const useStore = () = > {
 return {
    state: readonly(state),
    action: readonly(action)
 }
}
Copy the code

I think it smells like that. Why use readOnly here? This is because we can’t change the state variable directly. We can just change the state value through the action and increase the readOnly.

Let’s use this in the VUE component:

<template>
	<div class="home">
		<img alt="Vue logo" src=".. /assets/logo.png" />
		<p>{{ info }}</p>
		<button @click="changeInfo">
			Click to change Info
		</button>
	</div>
</template>

<script lang="ts">
import { computed,defineComponent } from 'vue'
import { useStore } from '.. /custom_store/info'

export default defineComponent({
	name: 'Home'.setup() {
		const store = useStore()
		const info = computed(() = > store.state.info)

		function changeInfo() {
			store.action.chageInfo('hhh')}return {
			info,
			changeInfo
		}
	}
})
</script>
Copy the code

This is exactly how Vex5 works, and in case you haven’t noticed, we implement typescript support beautifully, and all types are automatically inferred:

Also, we’re implementing Store compose as well, so we can just import any other store that we want. We also support other vuE3 hooks that you can freely combine. Leave the rest to your friends to explore.

When it comes to the final

Let’s say react hooks and vue3 composition-API. It has greatly changed our way of thinking in programming. By discussing the future generations of VUEX, we can clearly realize the advantages of functional programming and the development direction of the front end. This is definitely one of the biggest advances in the front end in the last year, in conjunction with typescript, in terms of programming thinking, pushing the front end forward. No need to say more words, everyone partners quickly operate it.

Also, if you want to see a sample code for this article, click here: vue-viet-template.

digression

And a little AD, too. Recently wrote a gadget: Monia – CLI

  • What is the monia – cli?

    This is a project scaffold that supports VUE2, VUe3, React and Flutter simultaneously.

  • Monia – What can CLI do?

    1. Every time we create a new project, we need to spend a lot of time to create some repeated code files, such as Request,config, etc. “A programmer who is not lazy is not a good programmer”. With Monia, you can eliminate all this rework.

    2. The templates provided by Monia are the latest and most complete project templates at present. The Template of Vue3, Monia adopts the latest vue + Vite + typescript project organization. The Template of Flutter will save you a lot of repetitive work, including Comon Componts, Comon Utils, etc.

    3, Monia itself also supports error prompts, command association and other functions.

    4. Example: monia create test_flutter_app