Как получить intellisense mapGetters, mapActions Vuex и typescript без синтаксиса стиля класса или декораторов - PullRequest
1 голос
/ 28 мая 2020

Я использую Vue. js и Vuex какое-то время, но всегда с javascript.

Я пытаюсь использовать Vue с Typescript, nuxt. js чтобы быть более конкретно, но без использования декораторов или компонента класса стиля, продолжайте только с обычным синтаксисом Vue

Это код, который у меня есть в моем магазине Vuex

/store/todos/types.ts

export interface Todo {
  id: number
  text: string
  done: boolean
}

export interface TodoState {
  list: Todo[]
}

/store/todos/state.ts

import { TodoState } from './types'

export default (): TodoState => ({
  list: [
    {
      id: 1,
      text: 'first todo',
      done: true
    },
    {
      id: 2,
      text: 'second todo',
      done: false
    }
  ]
})

/store/todos/mutations.ts

import { MutationTree } from 'vuex'
import { TodoState, Todo } from './types'

export default {
  remove(state, { id }: Todo) {
    const index = state.list.findIndex((x) => x.id === id)
    state.list.splice(index, 1)
  }
} as MutationTree<TodoState>

/store/todos/actions.ts

import { ActionTree } from 'vuex'
import { RootState } from '../types'
import { TodoState, Todo } from './types'

export default {
  delete({ commit }, { id }: Todo): void {
    commit('remove', id)
  }
} as ActionTree<TodoState, RootState>

/store/todos/getters.ts

import { GetterTree } from 'vuex'
import { RootState } from '../types'
import { TodoState, Todo } from './types'

export default {
  list(state): Todo[] {
    return state.list
  }
} as GetterTree<TodoState, RootState>

Это код, который у меня есть в моем компоненте,

<template>
  <div>
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        {{ todo.text }}
        <button @click="destroy(todo)">delete</button>
      </li>
    </ul>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'
import { mapGetters, mapActions } from 'vuex'

export default Vue.extend({
  computed: {
    ...mapGetters({
      todos: 'todos/list'
    })
  },
  methods: {
    ...mapActions({
      destroy: 'todos/delete'
    })
  }
})
</script>

Все работает отлично, кроме автозаполнения / intellisense для геттеров или действий, исходящих из Vuex

Кто-то может помогите мне?

Спасибо за это o /

1 Ответ

1 голос
/ 29 мая 2020

Vuex в текущей форме не работает с Typescript. Вероятно, это изменится в Vue 3.

Как и вы, я также не хочу использовать декораторы @Component, особенно потому, что они устарели. Однако, когда дело доходит до использования стиля компонента Vue typescript по умолчанию:

<script lang="ts">
  import Vue from 'vue';
  export default Vue.extend({...})
</script>

... после тестирования нескольких решений я обнаружил, что проще всего использовать плагин, который делает использовать декораторы : vuex-module-decorators

Модуль Vuex:

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

Магазин выглядит так:

import Vue from 'vue';
import Vuex from 'vuex';
import { getModule } from 'vuex-module-decorators';
import Whatever from '@/store/whatever';

Vue.use(Vuex);

const store = new Vuex.Store({
  modules: {
    whatever: Whatever
  }
});

getModule(Whatever, store); // this is important for typescript to work properly

export type State = typeof store.state;
export default store;

Вот несколько примеров вычислений mapState, mapGetters или get / set, которые работают напрямую с магазином:

computed: {
  ...mapGetters({
    foo: 'whatever/foo',
    bar: 'whatever/bar'
  }),
  ...mapState({
    prop1: (state: State): prop1Type[] => state.whatever.prop1,
    prop2: (state: State): number | null => state.whatever.prop2
  }),
  // if i want get/set, for a v-model in template
  baz: {
    get: function(): number {
      return this.$store.state.whatever.baz;
    },
    set: function(value: number) {
      if (value !== this.baz) { // read * Note 1
        this.$store.dispatch('whatever/setBaz', value);
        // setBaz can be an `@Action` or a `@MutationAction`
      }
    }
  }
}

baz теперь можно использовать в v-model. Примечание. mapGetters должны быть фактическими получателями хранилища модулей:

import { $http, $store } from '@/main'; // read * Note 2
import { Action, Module, Mutation, MutationAction, VuexModule } from 'vuex-module-decorators';

@Module({ namespaced: true, store: $store, name: 'whatever' })
export default class Whatever extends VuexModule {

  get foo() {
    return // something. `this` refers to class Whatever and it's typed
  }
  baz = 0;
  prop1 = [] as prop1Type[];       // here you cast the type you'll get throughout the app
  prop2 = null as null | number;   // I tend not to mix types, but there are valid cases 
                                   // where `0` is to be treated differently than `null`, so...
  @MutationAction({ mutate: ['baz'] })
  async setBaz(baz: number) {
    return { baz }
  }
}

Теперь у вас не будет проблем с использованием декораторов @Action или @Mutation, и вы можете остановиться на этом, у вас не будет любые проблемы с машинописным текстом. Но поскольку они мне нравятся, я часто использую @MutationAction sa, хотя, честно говоря, они гибрид. Хак, если хотите.
Внутри @MutationAction, this не является классом модуля. Это ActionContext (в основном то, что будет первым параметром в обычном действии js vuex):

interface ActionContext<S, R> {
  dispatch: Dispatch;
  commit: Commit;
  state: S;
  getters: any;
  rootState: R;
  rootGetters: any;
}

И даже не в этом проблема. Проблема в том, что Typescript считает, что this - это класс модуля внутри @MutationAction. И вот тогда вам нужно начать кастинг или использовать typeguards. Как правило, я стараюсь свести количество кастингов к минимуму и никогда не использую any. Typeguards могут go очень далеко.
Золотое правило: если мне нужно использовать as any или as unknown as SomeType, это явный знак, что я должен разделить @MutationAction на @Action и @Mutation. Но в подавляющем большинстве случаев достаточно охранника. Пример:

import { get } from 'lodash';
...
@Module({ namespaced: true, store: $store, name: 'whatever' })
export default class Whatever extends VuexModule {
  @MutationAction({ mutate: ['someStateProp'] })
  async someMutationAction() {
    const boo = get(this, 'getters.boo'); // or `get(this, 'state.boo')`, etc...
    if (boo instaceof Boo) {
      // boo is properly typed inside a typeguard
      // depending on what boo is, you could use other typeguards:
      // `is`, `in`, `typeof`  
    }
}

Если вам нужны только значения state или getters: this.state?.prop1 || [] или this.getters?.foo также работают.

Честно говоря, @MutationAction требует некоторая форма взлома типов, поскольку вам нужно объявить типы: они не выводятся должным образом. Итак, если вы хотите быть на 100% правильным, ограничьте их использование случаями, когда вы просто устанавливаете значение свойства состояния и не хотите писать как действие, так и мутацию:

@MutationAction({ mutate: ['items'] })
async setItems(items: Item[]) {
  return { items }
}

Что заменяет:

@Action
setItems(items: Item[]) {
  this.context.commit('setItems', items);
  // btw, if you want to call other @Action from here or any @MutationAction
  // they work as `this.someAction();` or `this.someMutationAction()`;
}

@Mutation
setItems(items: Item[]) {
  this.items = items;
}

@MutationAction s зарегистрированы как @Action s, они принимают { mutate: [/* full list of props to be mutated*/]} и возвращают объект, имеющий все объявленные свойства состояния, которые объявлены в массиве свойств.

Вот и все.


* Примечание 1 : мне пришлось использовать эту проверку, когда я использовал два разных входа (обычный и ползунок ввода) на том же get/set v-model. Без этой проверки каждый из них при обновлении вызовет set, что приведет к ошибке переполнения стека. Обычно эта проверка не требуется, если у вас только 1 вход.

* Примечание 2 : вот как мой main.ts обычно выглядит

import ...
Vue.use(...);
Vue.config...

const Instance = new Vue({
  ...
}).$mount(App);

// anything I might want to import in components, store modules or tests:
export { $store, $t, $http, $bus } = Instance; 
/* I'd say I use these imports more for correct typing than for anything else 
 (since they're already available on `this` in any component). But they're 
 quite useful outside of components (in services, helpers, store, translation 
 files, tests, etc...)
 */
...