preface

Vue3 is here, the new version of Vuex is also in beta, before it is applied to the project, it went through the water first, I almost broke my computer, why did I break it, let’s start the text…

The installation

npm init vite-app vue3-vuex-ts
npm install
Copy the code

To the body

1. Convert js files into TS files

  • main.js -> main.ts
  • Change script type in vue file to lang = “ts”
  • Modified index. HTML
<script type="module" src="/src/main.ts"></script>
Copy the code

2. Create a vUE file declaration

  • There is no VUE module declaration in the TS environment

  • SRC create vue.d.ts file
declare module '*.vue' {
  import { FunctionalComponent, defineComponent } from 'vue';
  const component: ReturnType<typeof defineComponent> | FunctionalComponent;
  export default component;
}
Copy the code

Access vuex

npm install vuex@next --save
Copy the code

Installing, let’s talk about vuex

Why can VEX2 be bidirectional binding?

let Vue;

class Store {
  constructor(options) {
    const { getters, state, mutations, actions } = options;
    this._mutations = mutations;
    this._actions = actions;
    if (getters) {
      this.handleGetters(getters);
    }
    this._vm = new Vue({
      data: {
        $$state: state,
      },
    });
    this.commit = this.commit.bind(this);
    this.dispatch = this.dispatch.bind(this);
  }

  get state() {
    return this._vm.data.$$state;
  }

  commit(type, payload) {
    const entry = this._mutations[type];
    if (entry) {
      entry(this.state, payload); }}dispatch(type, payload) {
    const entry = this._actions[type];
    if (entry) {
      entry(this, payload); }}handleGetters(getters) {
    this.getters = {};
    Object.keys(getters).forEach(key= > {
      Object.defineProperty(this.getters, key, {
        get: () = > getters[key](this.state), }); }); }}function install(_Vue) {
  Vue = _Vue;
  Vue.mixin({
    beforeCreate() {
      if (this.$options.store) {
        Vue.prototype.$store = this.$options.store; }}}); }export default {
  Store,
  install,
};
Copy the code
  • Vuex is strongly bound to Vue(new Vue for observing data), so VUX cannot be used as a state tool for other states
  • Mixin’s beforeCreate will attach $store to Vue’s prototype chain without blowing it. Every time a component is used, go through the prototype chain. During the Vue rendering process, beforeCreate will be executed countless times ๐Ÿ˜“

vuex3

  • Reactive and provide are used for bidirectional binding. I won’t go into details here, but point out a few key points
store._state = reactive({
  data: state
})

install (app, injectKey) {
  app.provide(injectKey || storeKey, this)
  app.config.globalProperties.$store = this
}
Copy the code

Get ready to squat

1.main.ts

import { createApp } from 'vue';
import App from './App.vue';
import store from './store';
import './index.css';

createApp(App).use(store).mount('#app');
Copy the code

2. Create something related to the Store

  • index.ts
import { createStore } from 'vuex';
import modules from './modules';

import { userState } from './modules/user';
import { detailState } from './modules/detail';

export interface State {
  user: userState;
  detail: detailState;
}

export default createStore <
  State >
  {
    modules,
  };
Copy the code
  • modules.ts
import user from './modules/user';
import detail from './modules/detail';

export default {
  user,
  detail,
};
Copy the code
  • modules/user.ts
export type userState = {
  isLogin: boolean,
};

const state: userState = {
  isLogin: true};export default {
  namespaced: true,
  state,
  getters: {
    loginInfo: (state: userState): string= > {
      return `${state.isLogin ? 'Logged in' : 'Not landed'}`; }},mutations: {
    setUserInfo(state: userState, payload: boolean): void {
      console.log('Data Request', payload); state.isLogin = payload; }},actions: {
    changeUserInfo({ commit }, payload: { data: boolean }): void {
      console.log('Action executed successfully');
      setTimeout(function () {
        commit('setUserInfo'.false);
      }, 2000); ,}}};Copy the code
  • Detail.ts is omitted here

  • HelloWorld.vue

<template>
  <div>
    <h1>{{ msg }}</h1>
    <button @click="count++">count is: {{ count }}</button>
    <div>{{ info1 }}</div>
    <div>{{ info2 }}</div>
    <button @click="logout">exit</button>
  </div>
</template>

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

export default defineComponent({
  name: 'HelloWorld'.props: {
    msg: String,},data() {
    return {
      count: 0}; },setup(props) {
    const { state, getters, dispatch, commit } = useStore<State>();
    console.log('๐ŸŠ', state.user.isLogin); / / ๐ŸŠ true
    console.log('๐ŸŽ', state.detail.title); / / ๐ŸŽ hello
    console.log('๐Ÿš€', getters['user/loginInfo']); // ๐Ÿš€ is logged in

    const info1 = computed(() = > getters['user/loginInfo']);
    const info2 = computed(() = > getters['detail/detailInfo']);

    const logout = () = > commit('user/setUserInfo'.false); // Data request false
    // dispatch()
    return{ info1, info2, logout }; }});</script>
Copy the code

At the end of the time, feel lovely ๐Ÿถ

No, No, No, No, No, No, No, No, No, No, No, No, No. ๐Ÿคฏ

Is this really over?

  • Look at the picture speak

Oh oh, State I wrapped a layer, the hint is rough

Ahiba, what the hell is this? Any, he’s too broken to talk…

Dispath: pass a string type of type. I know what to pass, so I might as well write in js

So much preparation has been made in the early stage, and the result is that the prompt let me pass the type of string, how the hell do I know what to pass, the work given to TS, let me do it myself, all the previous in vain?

It’s like going to a bar with plenty of foreplay, only to find out it’s lace ~ ๐Ÿ˜“

Do it or do it, speechless ING

Began to climb the pit

1.ไฟฎๅค State ้—ฎ้ข˜๏ผŒmodules/user.ts

const state = {
  isLogin: true};export type userState = typeof state;

export default {
  / /...
};
Copy the code

But that doesn’t work either. I can’t invoke State once for every component. Let’s go to the next step

2. Fix getters

  • Like React, vue uses hooks to handle state, so let’s do the whole thing

  • Gettters annotations such as ‘loginInfo’, dispatch and commit annotations need to be provided

    • Traverse modules, find all getters, Actions, mutations, and get their type
    • Pass them through hooks
  • Modify the HelloWorld. Vue

<script lang="ts"> // import { useStore } from 'vuex'; import { defineComponent, computed } from 'vue'; // import { State } from '.. /store'; import { useStoreHooks } from '.. /hooks'; export default defineComponent({ name: 'HelloWorld', props: { msg: String, }, data() { return { count: 0, }; }, setup(props) { // const { state, getters, dispatch, commit } = useStore<State>(); const { state, getters, dispatch, commit } = useStoreHooks(); The console. The log (' ๐ŸŠ 'state. User. IsLogin); / / ๐ŸŠ true on the console. The log (' ๐ŸŽ 'state. The detail. The title); / / ๐ŸŽ hello console. The log (' ๐Ÿš€, getters [' user/loginInfo ']); // ๐Ÿš€ logged in const info1 = computed(() => getters['user/loginInfo']); const info2 = computed(() => getters['detail/detailInfo']); const logout = () => commit('user/setUserInfo', false); // Dispatch () return {info1, info2, logout}; }}); </script>Copy the code
  • Create hooks/index. Ts
import { useStore } from 'vuex';
import { State } from '.. /store';
// The type of these things
import { Getters, Dispatch, Commit } from './utils';

interface UseStoreHooks {
  state: State;
  getters: Getters;
  commit: Commit;
  dispatch: Dispatch;
}

const useStoreHooks = (): UseStoreHooks= > {
  const store = useStore<State>();
  console.log(store);
  // return store;
  // Custom output results
  const { state, getters, dispatch, commit }: UseStoreHooks = store;
  return {
    state,
    getters,
    commit,
    dispatch,
  };
};

export { useStoreHooks };
export default useStoreHooks;
Copy the code
  • hooks/utils.ts
    • Write type, this is not JS, each paragraph needs to be viewed from the bottom up
    • Install the latest typescript, yarn Add typescript@next (currently)
    • Vscode switches typescrit versions

/* Get modules */ from the store
import modules from '.. /store/modules';

/* Get the getters structure type */
Infer matches the getters under a single module and infer an entry
type GetGetter<GetterType> = GetterType extends { getters: infer G } ? G : unknown;
// Get all vuex getters modules
type GetGetters<GetterTypes> = {
  [K in keyof GetterTypes]: GetGetter<GetterTypes[K]>;
};
type ModuleGetters = GetGetters<typeof modules>;

// --------------------

/* Obtain the structural type */
// mutations are available on a single module
type GetMutation<MutationType> = MutationType extends { mutations: infer M } ? M : unknown;
// Obtain all mutations modules of VUEX
type GetMutations<MutationTypes> = {
  [K in keyof MutationTypes]: GetMutation<MutationTypes[K]>;
};
type ModuleMutations = GetMutations<typeof modules>;

// --------------------

/* Get the actions structure type */
// assign an action to a single module
type GetAction<ActionType> = ActionType extends { actions: infer A } ? A : unknown;
// Get all vuex actions modules
type GetActions<ActionTypes> = {
  [K in keyof ActionTypes]: GetAction<ActionTypes[K]>;
};
type ModuleActions = GetActions<typeof modules>;

// --------------------

/* Getter/Commit/Dispatch Intelligent prompt processing */
// gettters[module name/method], COMMIT [module name/method], dispatch[module name/method]
Ts4.1 supports template string syntax. Install the latest YARN typescript (currently yarn add typescript@next).
/ / incoming keyof is probably the symbol | number, so P & string in the string
type AddPrefix<P, K> = `${P & string}/${K & string}`;

// Switch the order: user: "user/loginInfo" => "user/loginInfo": user
type GetSpliceKey<Module, Key> = AddPrefix<Key, keyof Module>
/** * { 'user/loginInfo': () => {} } */
// type GetSpliceKeys<Modules> = {
// [K in keyof Modules]: GetSpliceKey
      [k],>
// }
// type xx = GetSpliceKeys<ModuleGetters>

type GetSpliceKeys<Modules> = {
  [K in keyof Modules]: GetSpliceKey<Modules[K], K>
}[keyof Modules]
// type xx = GetSpliceKeys<ModuleGetters>

type GetFunc<T, A, B> = T[A & keyof T][B & keyof T[A & keyof T]];
type GetSpliceObj<T> = {
  // K extends' ${infer A}/${infer B} 'equivalent to user/loginInfo A=>user B=>loginInfo
  [K in GetSpliceKeys<T>]:K extends `${infer A}/${infer B}` ? GetFunc<T, A, B> : unknown
}

// --------------------

/ * Getters/Mutations Actons together * / / XXX XXX format
type GenGetters = GetSpliceObj<ModuleGetters>;
type Getters = {
  [K in keyof GenGetters]:ReturnType<GenGetters[K]>
}

// --------------------

type GenMutations = GetSpliceObj<ModuleMutations>;
type Mutations = {
  [K in keyof GenMutations]:ReturnType<GenMutations[K]>
}

// --------------------

type GenActions = GetSpliceObj<ModuleActions>;
type Actions = {
  [K in keyof GenActions]:ReturnType<GenActions[K]>
}

// --------------------

// commit Obtains the payload parameter type
type MutationsPayload = {
  // Parameters gets function Parameters
  [K in keyof GenMutations]:Parameters<GenMutations[K]>[1]
}

interface GetCommit<T> {
  // Optional parameter type. Undefined is automatically added<K extends keyof T>(mutation: K, payload? : T[K]): void; } type Commit = GetCommit<MutationsPayload>; // -------------------- // dispatch Obtain payload Parameter Type Type ActionPayload = {// Parameters Obtain function Parameters [K in keyof GenActions]:Parameters<GenActions[K]>[1]} interface GetDispatch<T> { Undefined <K extends keyof T>(action: K, payload? : T[K]): Promise<unknown>; } type Dispatch = GetDispatch<ActionPayload>; // -------------------- export { Getters, Mutations, Actions, Dispatch, Commit };Copy the code
  • So let’s see what happens

Hooks, type is already in there

I’m f * * king mad. I can get getters in hooks, but I can’t get them outside

Wake up from a dream, like a different world

โค๏ธ full of holes

  • Ts can be obtained, vue can not be obtained, ha ha ~

  • Ok, get the script out, that’s enough, patience is running out…

    1. Put all the ts parts of HelloWorld.vue into HelloWorld.ts

    2. Modify the HelloWorld. Vue

<template>
  <div>
    <h1>{{ msg }}</h1>
    <button @click="count++">count is: {{ count }}</button>
    <div>{{ info1 }}</div>
    <div>{{ info2 }}</div>
    <button @click="logout">exit</button>
  </div>
</template>

<script lang="ts" src="./HelloWorld.ts"></script>
Copy the code
  • Go to HelloWorld.ts and check it out. It finally worked

getters

commit

dispatch

In this paper, the code

The end of the

Look out of the window, the day is already bright, smoke has finished. Neither happy nor sad, tired of…

Really learn not to move: hand lighter, where will not point where, mother no longer need to worry about my learning…

Join us at โค๏ธ

Bytedance Xinfoli team

Nice Leader: Senior technical expert, well-known gold digger columnist, founder of Flutter Chinese community, founder of Flutter Chinese community open source project, well-known developer of Github community, author of dio, FLY, dsBridge and other well-known open source projects

We look forward to your joining us to change our lives with technology!!

Recruitment link: job.toutiao.com/s/JHjRX8B