This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!

We are now in the fourth chapter of our Vue3 Construction project Ramp Up series, and we have already introduced ESLint, Husky, Lint-Staged and large Elements into our projects. Those of you who don’t see it can click on the portal to look back.

  • Vue3+Vite+TS+Eslint (Airbnb rules) Build production projects, tread pits for details (I)
  • Vue3+Vite+TS+Eslint (Airbnb rule) Build production projects and ramp up (II) : Configure HusKY and Lint-staged projects
  • Vue3+Vite+TS+Eslint (Airbnb rules) Build production projects, step on the hole for details (3) : introduce element-Plus, solve the font file 404 problem

Today, we originally planned to introduce vuex and VUE-Router at one time, but after writing the introduction of Vuex, we found it was too long, so we had to write a single article.

But good, and can water an article, ha ha ha ha

I’m sure you all know what the Vuex is. So I don’t have to explain too much, just do it

The installation

Here we need to install vuex@next, which is the version that matches Vue3

npm install vuex@next --save
Copy the code

Initialize the

Single file form

We create a SRC /store/index.ts file and initialize vuex within the file.

// src/store/index.ts
import { createStore } from 'vuex';

const store = createStore({
  // Vuex related content
  state() {
    return {
      count: 0}; },mutations: {
    increment(state) {
      state.count += 1; ,}}});export default store;
Copy the code

This file is then imported and bound to the Vue instance in main.ts

//main.ts.import store from './store/index'; // ++

createApp(App).use(ElementPlus, { locale }).use(store).mount('#app'); // edit
Copy the code

ESLint will report an error after importing a Store file

This is the first time we introduce TS files, because Airbnb-Base only provides validation of native JS, and I haven’t found the official Airbnb rule library so far. So just like last time, we modify the verification rules of Airbnb-base.

// .eslintrc.js

settings: {
    'import/resolver': {
      node: {
        extensions: ['.js'.'.jsx'.'.ts'.'.tsx'],}}},rules: [
    'import/extensions': [
      'error'.'ignorePackages',
      {
        js: 'never'.mjs: 'never'.jsx: 'never'.ts: 'never',}],]Copy the code

After the modification, restart the ESLint service and the error will no longer exist.

After completing the initialization, we will find that the method in mutations reports an ESLint error, so don’t change the parameters directly. But in VUEX, we have to modify state directly.

Just look at the Airbnb-base definition of this rule, as you did with the introduction of Vite last time, and add state to the exception. Airbnb-base rule definition

We added the following rule to ESLint:

.ruls: {
  'no-param-reassign': [
    'error',
    {
      props: true.ignorePropertyModificationsFor: [
        'acc'.// for reduce accumulators
        'accumulator'.// for reduce accumulators
        'e'.// for e.returnvalue
        'ctx'.// for Koa routing
        'context'.// for Koa routing
        'req'.// for Express requests
        'request'.// for Express requests
        'res'.// for Express responses
        'response'.// for Express responses
        '$scope'.// for Angular 1 scopes
        'staticContext'.// for ReactRouter context
        'state'.// for vuex state ++],},],}Copy the code

After the rule is added, check again and find that the error message is still displayed, but the error message has changed.

This is because we are not declaring the type of state. To solve this problem, we first create an interface for declaring the type of state.

// src/store/index.ts.interface State {
  count: number; }...Copy the code

Now that we’ve defined the type interface, where do we use it?

As you can see from the Vuex definition of type, the createStore method can receive a parameter S for type and a parameter Options of type StoreOptions<S>.

Looking at the StoreOptions type interface, it takes S as the parameter of the interface and defines it as the type of the state property. Meanwhile, it also passes Sto other properties as a parameter, so that the mutations method can use state as a parameter to get the type definition of state.

Therefore, we pass the custom State interface to createStore as a generic parameter, as follows:

import { createStore } from 'vuex';

interface State {
  count: number;
}

const store = createStore<State>({
  // Vuex related content. });export default store;
Copy the code

When the change is complete, you can see that the state parameter received by the increment method is ready to get the type.

So far, our VUex is working normally, but in the project, we probably have a lot of data to maintain in VUex. If we put all the data into a file, it will make the code less readable and difficult to maintain. Therefore, if there is a large amount of data, you are advised to divide the data into modules based on functions.

Let’s break down the above example into modules.

The module combination

File transfer

We start by creating a new SRC /store/module.ts file, and transfer state and mutations originally defined in index.ts to the newly created module.ts file.

// src/store/module.ts

// Type declaration
export interface ModuleState {
  count: number;
}
/ / the module information
export default {
  state() {
    return {
      count: 0}; },mutations: {
    increment(state: ModuleState) {
      state.count += 1; ,}}};Copy the code
// src/store/index.ts

import { createStore } from 'vuex';
import module from './module';

const store = createStore({
  modules: {
    module,}});export default store;
Copy the code

Module Type declaration

After the module module.ts is created, the methods in mutations will still report the state type.

The obvious thing to think about is the declaration in the example above, where you create a module-level type interface and then declare the state parameter of the mutation method. This statement is certainly ok. But if there are 10 mutations in the module, and even 10 getters, that means we have to declare the type every time we use state. This declaration does not conform to the Module and State type declaration logic in vuex source code.

Type declarations in source code

In the single file form section, createStore receives a StoreOptions<S> parameter. In the screenshot, we can see that this interface declares modules as ModuleTree<S> and passes Sto ModuleTree as a parameter. This is the state type parameter passed by createStore.

Take a look at ModuleTree’s statement:

The S received by ModuleTree is used as R and passes R to the Module interface.

The Module interface receives S and R, declares state as type S, and passes S and R to internal getters, etc.

From the type definition of Getter and so on, we know that R is actually the type of rootState because it is the type parameter received from createStore.

Improve our type declarations

Knowing the definition of a type in the source code, we can use it to optimize the way we declare types in the Store.

First, in a Module we can declare the Object thrown as Module type in VUEX and pass it S and R arguments. The S argument is our declared module state interface; R is the rootState type and has not been passed in yet, so it is replaced by R first.

// src/store/module.ts

import { Module } from 'vuex';

export interface ModuleState {
  count: number;
}
export default {
  state() {
    return {
      count: 0}; },mutations: {
    increment(state) {
      state.count += 1; }},}as Module<ModuleState, R>;
Copy the code

After modification, state in mutations has type information, but R has no type passed in at this time. No hurry, next we modify index.ts and solve the type problem of R.

In index.ts, you need to create a State interface to declare the type of rootState as follows

import { createStore } from 'vuex';
import module from './module';

export interface State{
  rootCount: number; // If there is no root state, it can be null. Here, add the type information for later verification
}

const store = createStore<State>({
  state() {
    return {
      rootCount: 111,}},modules: {
    module,}});export default store;
Copy the code

Now that we have the root type declaration, we can introduce the State interface into the Module, pass the R parameter required by the Module interface, and add a getters for verification.

// src/store/module.ts

import { State } from './index'

export default{...getters: {
    getCount(state, getters, rootState) {
      return `${state.count}- =${rootState.rootCount}`}}}as Module<ModuleState, State>;
Copy the code

RootState can now get the type information.

At this point, we have completed our own type optimization following the logic of the official type declaration.

use

As for the method of use, there is a detailed introduction of use in the official document. For the sake of space, we do not make a detailed introduction, just refer to the document. (next.vuex.vuejs.org/guide/compo…).

Vue3 prefers to use the Composition API because it is the only way to use it in our project source code.

The current configuration works fine, but we found that the state variable in the component did not return type information, which could be problematic in some cases, such as using the.length method for the count property we defined.

This is also explained in detail in the official documentation, so we can check it for ourselves.

UseStore combined function type declaration

Returns a typed store

We first modified the vuEX configuration according to the official document format

// src/store/index.ts

import { InjectionKey } from 'vue';
import { createStore, Store, useStore as baseUseStore } from 'vuex'; .export interface State{
  rootCount: number;
}
export const key: InjectionKey<Store<State>> = Symbol(a)// ++

// define your own 'useStore' combinatorial function ++
export function useStore () {
  return baseUseStore(key)
}
Copy the code
// main.ts.import store, { key } from './store/index'; // edit

createApp(App).use(ElementPlus, { locale }).use(store, key).mount('#app'); // edit
Copy the code

When used, we introduce a custom useStore method that retrieves state as a stateful value.

Module Type handling

If we access the state inside the Module, we will be told that the module does not exist on the state type:

This is because when we declare InjectionKey, we only define the State type, which only contains the type definition of rootState. However, State is passed to createStore, so we can’t change it directly. We need to create an interface that contains both Module and rootState information, and then pass the new interface to the InjectionKey definition.

// src/store/index.ts

export interface InjectionState extends State{
  module: ModuleState,
  module2: Module2State,
}
export const key: InjectionKey<Store<InjectionState>> = Symbol(a)Copy the code

This way, module information is normally accessible within the component.

legacy

According to the code structure of the article so far, we can use vuex normally, but if we use rootState in module, you will find that there is no problem with introducing rootState. RootCount; However, if rootState.module2.count is used, the same problem as in the [Return typed Store] section occurs, and Module2 does not exist in type State.

This is because our State type really only defines the property types contained in the root State. However, our State is passed to the createStore as a rootState type declaration, and it is also the type of the rootState (see the relationship below), so we cannot add module information to it as InjectionKey.

I haven’t found a perfect solution to this problem yet. However, we can pass InjectionKey to createStore as a type parameter without defining the root state variable, and also introduce this interface when defining module as R in module

. This can be used as a compromise to access state between modules. Students who need specific code can check git projects themselves.
,>

The code has been updated in the Git repository: github.com/YuanDaoDao0…

Those who want a better solution to this remaining problem are generous with their advice