Como muitos desenvolvedores, escrevo meu próprio projeto
Negação
Requisitos básicos para uma loja:
- Tipos de texto datilografado devem funcionar em módulos
- Módulos devem ser fáceis de usar em componentes, tipos de estados, ações, mutações e getters devem funcionar
- Não crie uma nova api para vuex, você precisa se certificar de que os tipos de texto digitados de alguma forma funcionam com módulos vuex para que você não tenha que reescrever todo o aplicativo de uma vez
- Chamar mutações e ações deve ser o mais simples e direto possível
- O pacote deve ser o menor possível
- Não quero armazenar constantes com nomes de mutações e ações
- Deve funcionar (e sem ele)
Não pode ser que um projeto tão maduro como o vuex não tivesse suporte normal de digitação. Bem, abrimos o
vuex-smart-module
github.com/ktsn/vuex-smart-module
Bom, muito bom. Tudo comigo, mas pessoalmente não gostei do fato de que para ações, mutações, estados, getters, você precisa criar classes separadas. Isso, é claro, é bom gosto, mas este sou eu e meu projeto) E, em geral, o problema de digitação não foi totalmente resolvido ( tópico de comentários com uma explicação do porquê ).
Suporte para Vuex Typescript
vuex-module-decorators
Esta parecia ser a maneira perfeita de fazer amigos vuex e datilografados. Parece que o vue-property-decorator que uso no desenvolvimento, você pode trabalhar com o módulo como com uma classe, em geral, super, mas ...
Mas não há herança. As classes de módulo não são herdadas corretamente e o problema está pendurado há muito tempo! E sem herança, haverá muita duplicação de código. Panqueca…
Raiva
Então não era muito, bom, ou ± o mesmo - não há solução ideal. Este é o momento exato em que você diz a si mesmo: Por que comecei a escrever um projeto em vue? Bem, você sabe reagir, bem, eu escreveria sobre reagir, não haveria tais problemas! No trabalho principal, o projeto está em voga e você precisa atualizá-lo - acerte o argumento. Vale a pena passar os nervos e as noites sem dormir? Sente-se como todo mundo, escreva komponentiki, não, você precisa acima de tudo! Jogue essa vue! Escreva para reagir, atualize-o e pague mais por isso!
Naquele momento, eu estava pronto para odiar vue como nenhum outro, mas era emoção, e inteligência ainda estava acima disso. Vue tem (na minha opinião subjetiva) muitas vantagens sobre reagir, mas não há perfeição, assim como vencedores no campo de batalha. Tanto a visão quanto a reação são boas em sua própria maneira, e como uma parte significativa do projeto já está escrita em vue, seria o mais tolo possível mudar para reagir agora. Eu tive que decidir o que fazer com vuex.
Pechincha
Bem, as coisas não estão indo bem. Talvez então vuex-smart-module? Este pacote parece bom, sim, você tem que criar muitas classes, mas funciona muito bem. Ou talvez ele pudesse tentar escrever tipos para mutações e ações manualmente em componentes e usar vuex puro? Lá, vue3 com vuex4 está a caminho, talvez eles estejam se saindo melhor com o texto datilografado. Então, vamos tentar o vuex puro. No geral isso não atrapalha o trabalho do projeto, ainda funciona, não tem tipos, mas você aguenta. E espere)
No começo eu comecei a fazer isso, mas o código acabou sendo monstruoso ...
Depressão
Eu tive que seguir em frente. Mas onde é desconhecido. Foi um passo completamente desesperado. Decidi fazer um contêiner de estado do zero . O código foi elaborado em algumas horas. E acabou bem. Os tipos funcionam, o estado é reativo e até a herança existe. Mas logo a agonia do desespero começou a diminuir e o bom senso começou a voltar. Em suma, essa ideia foi para a lata de lixo. Em geral, esse era o padrão de barramento de evento global. E só é bom para pequenas aplicações. E, em geral, escrever seu próprio vuex ainda é um exagero (pelo menos na minha situação). Aí já adivinhei que estava completamente exausto. Mas era tarde demais para recuar.
Mas se alguém estiver interessado, então o código está aqui: (Provavelmente em vão adicionou este fragmento, mas o caminho será)
não parecer nervoso
const getModule = <T>(name:string, module:T) => {
const $$state = {}
const computed: Record<string, () => any> = {}
Object.keys(module).forEach(key => {
const descriptor = Object.getOwnPropertyDescriptor(
module,
key,
);
if (!descriptor) {
return
}
if (descriptor.get) {
const get = descriptor.get
computed[key] = () => {
return get.call(module)
}
} else if (typeof descriptor.value === 'function') {
// @ts-ignore
module[key] = module[key].bind(module)
} else {
// @ts-ignore
$$state[key] = module[key]
}
})
const _vm = new Vue({
data: {
$$state,
},
computed
})
Object.keys(computed).forEach((computedName) => {
var propDescription = Object.getOwnPropertyDescriptor(_vm, computedName);
if (!propDescription) {
throw new Error()
}
propDescription.enumerable = true
Object.defineProperty(module, computedName, {
get() { return _vm[computedName as keyof typeof _vm]},
// @ts-ignore
set(val) { _vm[computedName] = val}
})
})
Object.keys($$state).forEach(name => {
var propDescription = Object.getOwnPropertyDescriptor($$state,name);
if (!propDescription) {
throw new Error()
}
Object.defineProperty(module, name, propDescription)
})
return module
}
function createModule<
S extends {[key:string]: any},
M,
P extends Chain<M, S>
>(state:S, name:string, payload:P) {
Object.getOwnPropertyNames(payload).forEach(function(prop) {
const descriptor = Object.getOwnPropertyDescriptor(payload, prop)
if (!descriptor) {
throw new Error()
}
Object.defineProperty(
state,
prop,
descriptor,
);
});
const module = state as S & P
return {
module,
getModule() {
return getModule(name, module)
},
extends<E>(payload:Chain<E, typeof module>) {
return createModule(module, name, payload)
}
}
}
export default function SimpleStore<T>(name:string, payload:T) {
return createModule({}, name, payload)
}
type NonUndefined<A> = A extends undefined ? never : A;
type Chain<T extends {[key:string]: any}, THIS extends {[key:string]: any}> = {
[K in keyof T]: (
NonUndefined<T[K]> extends Function
? (this:THIS & T, ...p:Parameters<T[K]>) => ReturnType<T[K]>
: T[K]
)
}
Adoção O nascimento da bicicleta que conquistou a todos. vuexok
Para os impacientes, o código está aqui , a documentação resumida está aqui .
No final, escrevi uma pequena biblioteca que cobre toda a lista de desejos e até um pouco mais do que o necessário. Mas as primeiras coisas primeiro.
O módulo vuexok mais simples se parece com este:
import { createModule } from 'vuexok'
import store from '@/store'
export const counterModule = createModule(store, 'counterModule', {
state: {
count: 0,
},
actions: {
async increment() {
counterModule.mutations.plus(1)
},
},
mutations: {
plus(state, payload:number) {
state.count += payload
},
setNumber(state, payload:number) {
state.count = payload
},
},
getters: {
x2(state) {
return state.count * 2
},
},
})
Bem, meio como vuex ... o que está na linha 10?
counterModule.mutations.plus(1)
Uau! Isso é legal? Bem, com vuexok - sim, legalmente) O método createModule retorna um objeto que repete exatamente a estrutura do objeto do módulo vuex, apenas sem a propriedade com namespace, e podemos usá-lo para chamar mutações e ações ou para obter estados e getters, todos os tipos são preservados. E de qualquer lugar onde possa ser importado.
E quanto aos componentes?
E com eles está tudo bem, pois na verdade é vuex, então, em princípio, nada mudou, commit, dispatch, mapState, etc. trabalhe como antes.
Mas agora você pode fazer os tipos do módulo funcionarem nos componentes:
import Vue from 'vue'
import { counterModule } from '@/store/modules/counterModule'
import Component from 'vue-class-component'
@Component({
template: '<div>{{ count }}</div>'
})
export default class MyComponent extends Vue {
private get count() {
return counterModule.state.count // type number
}
}
A propriedade state em um módulo é reativa assim como em store.state, então para usar o estado do módulo nos componentes Vue, você só precisa retornar uma parte do estado do módulo em uma propriedade computada. Existe apenas uma advertência. Eu deliberadamente fiz o estado Readonly um tipo, não é bom mudar o estado vuex assim.
Ações de chamada e mutações são simples de desgraçar e os tipos de parâmetros de entrada também são salvos
private async doSomething() {
counterModule.mutations.setNumber(10)
// this.$store.commit('counterModule/setNumber', 10)
await counterModule.actions.increment()
// await this.$store.dispatch('counterModule/increment')
}
Aqui está uma beleza. Um pouco mais tarde, também precisei reagir à mudança no jwt, que também está armazenado na loja. E então o método do relógio apareceu em módulos. Os observadores de módulo funcionam da mesma maneira que store.watch. A única diferença é que o estado e os getters do módulo são passados como parâmetros da função getter.
const unwatch = jwtModule.watch(
(state) => state.jwt,
(jwt) => console.log(`New token: ${jwt}`),
{ immediate: true },
)
Então, o que temos:
- lado digitado - sim
- tipos funcionam em componentes - sim
- api como em vuex e tudo o que estava antes em vuex puro não quebra - é
- trabalho declarativo com o lado - sim
- tamanho de pacote pequeno (~ 400 bytes gzip) - sim
- não há necessidade de armazenar os nomes das ações e mutações em constantes - há
- deve funcionar - é
Em geral, é estranho que um recurso tão maravilhoso não esteja disponível no vuex fora da caixa, é incrível como ele é conveniente!
Quanto ao suporte para vuex4 e vue3 - não testei, mas a julgar pelos documentos deve ser compatível. Resolvem-
se também os problemas apresentados nestes artigos:
Vuex - resolvendo uma velha disputa com novos métodos
Vuex quebra o encapsulamento
Sonhos molhados:
Seria ótimo ter mutações e outras ações disponíveis no contexto das ações.
Como fazer isso no contexto de tipos de texto datilografado - o idiota sabe disso. Mas se você pudesse fazer isso:
{
actions: {
one(injectee) {
injectee.actions.two()
},
two() {
console.log('tada!')
}
}
Que minha alegria não teria limite. Mas a vida, como o texto datilografado, é uma coisa dura.
Aqui está a aventura com vuex e datilografado. Bem, eu meio que falei. Obrigado pela atenção.