Пошаговый тур по Vue3-Vuex-TypeScript

Vue.js

предисловие

Приехал Vue3,и новая версия Vuex тоже в бете.Прежде чем применить в проекте,пришлось сначала сдать воду.Я так разозлился,что чуть комп не разбил.Зачем ломать,начнем текст. ..

Установить

npm init vite-app vue3-vuex-ts
npm install

начать текст

1. Преобразуйте файл js в файл ts

  • main.js -> main.ts
  • Измените тип скрипта в файле vue на lang = "ts"
  • Изменить index.html
<script type="module" src="/src/main.ts"></script>

2. Создайте объявление файла vue

  • В среде ts нет объявления модуля vue

  • Создайте файл vue.d.ts в src
declare module '*.vue' {
  import { FunctionalComponent, defineComponent } from 'vue';
  const component: ReturnType<typeof defineComponent> | FunctionalComponent;
  export default component;
}

доступ к vuex

npm install vuex@next --save

установка, поговорим о vuex

Почему vuex2 может выполнять двустороннюю привязку?

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,
};
  • vuex сильно привязывает Vue (будет отслеживать данные new vue), поэтому vux не может использоваться другими штатами в качестве инструмента состояния.
  • Повесьте $store на цепочку прототипов Vue через beforeCreate Vue.mixin Никакого выдувания, никакого распыления: Vue не может быть переработан, все состояния не могут быть переработаны, и все плагины Vue не могут быть переработаны. Каждый раз, когда вы используете компонент, вы проходите цепочку прототипов.В процессе рендеринга vue функция beforeCreate будет выполняться бесчисленное количество раз 😓

vuex3

  • Используя реактивный и обеспечивающий двухстороннюю привязку, я не буду здесь вдаваться в подробности, но укажу на несколько ключевых моментов
store._state = reactive({
  data: state
})

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

Будьте готовы приседать

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');

2. Создайте что-нибудь связанное с магазином

  • 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,
  };
  • modules.ts
import user from './modules/user';
import detail from './modules/detail';

export default {
  user,
  detail,
};
  • 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 ? '已登陆' : '未登陆'}`;
    },
  },
  mutations: {
    setUserInfo(state: userState, payload: boolean): void {
      console.log('数据请求', payload);
      state.isLogin = payload;
    },
  },
  actions: {
    changeUserInfo({ commit }, payload: { data: boolean }): void {
      console.log('action执行成功');
      setTimeout(function () {
        commit('setUserInfo', false);
      }, 2000);
    },
  },
};
  • Detail.ts здесь опущен

  • HelloWorld.vue

<template>
  <div>
    <h1>{{ msg }}</h1>
    <button @click="count++">count is: {{ count }}</button>
    <div>{{ info1 }}</div>
    <div>{{ info2 }}</div>
    <button @click="logout">退出</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']); // 🚀 已登陆

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

    const logout = () => commit('user/setUserInfo', false); // 数据请求 false
    // dispatch()
    return { info1, info2, logout };
  },
});
</script>

Когда все закончилось, я чувствую себя таким милым 🐶

Нет.Нет.Нет, если командный проект это сделает, то предполагается, что мозги будут распроданы... 🤯

Это действительно конец?

  • смотреть картинки и говорить

О, государство, я завернул слой, кончики грубые

Аксиба, какого черта, любого, у меня в это время сердце сжалось, не могу жаловаться...

Dispatch предлагает передать тип строкового типа, я знаю, что передать (коммит тоже является достоинством), так что я мог бы написать это напрямую в js.

Я столько предугадывал на ранней стадии, и оказалось, что надо передать тип строкового типа.Как я могу знать, что передать и оставить это на ts.В свою очередь, пусть я сделаю это сам.

Это как в клубе, прелюдия очень хорошая, а когда выходишь, то обнаруживаешь, что это кружево~ 😓

Должен ли я это сделать или я должен это сделать, безмолвный

начать восхождение

1. Исправить проблему с состоянием, modules/user.ts

const state = {
  isLogin: true,
};

export type userState = typeof state;

export default {
  // ....
};

Но это тоже не работает, я не могу обратиться к каждому состоянию компонента один раз, давайте сразу перейдем к следующему шагу.

2. Исправить проблему геттеров~

  • Vue обрабатывает состояние через такие хуки, как react, поэтому давайте также инкапсулируем целые хуки, useStore и исправим проблему в нем.

  • Необходимо предоставить комментарии, такие как «logininfo» в getters, отправка, коммитируют комментарии

    • Пройдитесь по модулям, найдите все геттеры, действия, мутации и получите их тип
    • раздать их через крючки
  • Изменить 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();
    console.log('🍊', state.user.isLogin); // 🍊 true
    console.log('🍎', state.detail.title); // 🍎 hello
    console.log('🚀', getters['user/loginInfo']); // 🚀 已登陆

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

    const logout = () => commit('user/setUserInfo', false); // 数据请求 false
    // dispatch()
    return { info1, info2, logout };
  },
});
</script>
  • Создать хуки/index.ts
import { useStore } from 'vuex';
import { State } from '../store';
// 这几个玩意的type
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;
  // 自定义的进行输出结果
  const { state, getters, dispatch, commit }: UseStoreHooks = store;
  return {
    state,
    getters,
    commit,
    dispatch,
  };
};

export { useStoreHooks };
export default useStoreHooks;
  • hooks/utils.ts
    • Пишите типа, это дело не js, каждый абзац нужно читать снизу вверх
    • Установите последний машинописный текст, добавьте пряжу typescript@next (в настоящее время)
    • vscode переключение версии TypeScrit
/* 拿到store的modules */
import modules from '../store/modules';

/* 获取到 getters 结构类型 */
// 匹配到 单个module 下的 getter,小技巧 infer 为某一项
type GetGetter<GetterType> = GetterType extends { getters: infer G } ? G : unknown;
// 获取 vuex 所有的 getters 模块
type GetGetters<GetterTypes> = {
  [K in keyof GetterTypes]: GetGetter<GetterTypes[K]>;
};
type ModuleGetters = GetGetters<typeof modules>;

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

/* 获取到 mutations 结构类型 */
// 配到 单个 module 下的 mutations
type GetMutation<MutationType> = MutationType extends { mutations: infer M } ? M : unknown;
// 获取 vuex 所有的 mutations 模块
type GetMutations<MutationTypes> = {
  [K in keyof MutationTypes]: GetMutation<MutationTypes[K]>;
};
type ModuleMutations = GetMutations<typeof modules>;

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

/* 获取到 actions 结构类型 */
// 配到 单个 module 下的 action
type GetAction<ActionType> = ActionType extends { actions: infer A } ? A : unknown;
// 获取 vuex 所有的 actions 模块
type GetActions<ActionTypes> = {
  [K in keyof ActionTypes]: GetAction<ActionTypes[K]>;
};
type ModuleActions = GetActions<typeof modules>;

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

/* Getter/Commit/Dispatch 智能提示处理 */
// gettters[模块名/方法]、commit[模块名/方法]、dispatch[模块名/方法]
// ts4.1 以上支持 模板字符串语法,需要安装最新的 yarn typescript(目前yarn add typescript@next)
// 传入的是 keyof 有可能是symbol | number,所以 P & string 取其中的string
type AddPrefix<P, K> = `${P & string}/${K & string}`;

// 调换一下顺序:user: "user/loginInfo" => "user/loginInfo": user
type GetSpliceKey<Module, Key> = AddPrefix<Key, keyof Module>
/**
 * { 'user/loginInfo': () => {} }
 */
// type GetSpliceKeys<Modules> = {
//   [K in keyof Modules]: GetSpliceKey<Modules[K], 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}`   相当于  user/loginInfo  A=>user B=>loginInfo
  [K in GetSpliceKeys<T>]:K extends `${infer A}/${infer B}` ? GetFunc<T, A, B> : unknown
}

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

/* Getters/Mutations/Actons 拼接好 xxx/xxx 的格式  */
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 获取 payload 参数类型
type MutationsPayload = {
  // Parameters 获取函数参数
  [K in keyof GenMutations]:Parameters<GenMutations[K]>[1]
}

interface GetCommit<T> {
  // 可选参数类型,会自动加上undefined
  <K extends keyof T>(mutation: K, payload?: T[K]): void;
}

type Commit = GetCommit<MutationsPayload>;

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

// dispatch 获取 payload 参数类型
type ActionPayload = {
  // 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
};
  • взгляните на результаты

крючки, тип был получен в нем

Мой менталитет сломлен, я могу поймать добытчиков на крючок, но не могу вывести их наружу ~~

Как проснуться от сна, как всю жизнь

❤️ продырявлен

  • В тс можно, а во вью нет~

  • Ну, забирайте скриптовую часть, ничего страшного, терпение на исходе...

    1. Поместите все части ts из HelloWorld.vue в HelloWorld.ts.

    2. Измените HelloWorld.vue

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

<script lang="ts" src="./HelloWorld.ts"></script>
  • Перейдите на HelloWorld.ts, чтобы убедиться, что это, наконец, удалось

getters

commit

dispatch

Код этой статьи

конец

Глядя в окно, уже рассвело, и сигареты были докурены. Не рад, не грустен, устал...

Я действительно больше не могу учиться: я держу в руке зажигалку, где я не могу ее заказать, маме больше не нужно беспокоиться о моей учебе...

❤️ присоединяйтесь к нам

ByteDance · Команда счастья

Хороший лидер: старший технический эксперт, известный обозреватель Nuggets, основатель китайского сетевого сообщества Flutter, инициатор открытого проекта китайского сообщества Flutter, известный разработчик сообщества Github, автор многих известных проектов с открытым исходным кодом, таких как дио, муха, dsBridge

Мы с нетерпением ждем вашего присоединения и вместе используем технологии, чтобы изменить жизнь! ! !

Ссылка для набора: job.toutiao.com/s/JHjRX8B