像许多开发人员一样,我在业余时间编写自己的
否定
商店的基本要求:
- 打字稿类型应在模块中工作
- 模块应易于在组件中使用,状态,动作,突变和吸气剂的类型应起作用
- 不要为vuex提出新的api,您需要确保打字稿类型可以以某种方式与vuex模块一起使用,这样就不必一次重写整个应用程序
- 调用变异和操作应尽可能简单明了
- 包装应尽可能小
- 我不想存储带有突变和动作名称的常量
- 它应该工作(没有它怎么办)
像vuex这样的成熟项目不可能没有正常的打字稿支持。好吧,我们打开
vuex-smart-module
github.com/ktsn/vuex-smart-module
很好,很好。与我有关的一切,但就我个人而言,我不喜欢以下事实:对于动作,变异,状态,获取器,您需要创建单独的类。当然,这是一种品味,但这是我和我的项目。而且,总的来说,打字的问题尚未完全解决(注释线程,并附上原因说明)。
Vuex Typescript支持
vuex-module-decorators
这似乎是结交vuex和打字稿朋友的理想方法。看起来像我在开发中使用的vue-property-decorator,您可以像处理类一样使用模块,一般来说是super,但是...
但是没有继承。模块类没有正确继承,并且这个问题已经困扰了很长时间了!如果没有继承,将会有很多代码重复。饼子…
愤怒
然后,它根本不是非常好,还是差不多-没有理想的解决方案。这是您对自己说的那一刻:为什么我开始在vue中编写项目?好吧,你知道反应,好吧,我会写反应,不会有这样的问题!在主要工作中,该项目正在进行中,您需要对其进行升级-按参数进行。值得度过紧张的神经和不眠之夜吗?像其他人一样坐,写komponentiki,不,您最需要的!抛出这个线索!写反应,升级它,并为此付出更多!
在那一刻,我准备讨厌别具一格的仇恨,但这是情感,而智力仍然高于一切。在我看来,Vue在反应上有很多优势,但没有完美的表现,还有战场上的胜利者。vue和react都以其自己的方式很好,并且由于该项目的很大一部分已经用vue编写,所以现在转而做出响应将是愚蠢的。我必须决定如何处理vuex。
讨价还价
好吧,事情进展不顺利。也许然后vuex-smart-module?这个软件包看起来不错,是的,您必须创建很多类,但是效果很好。或者,也许他可以尝试在组件中手动编写突变和动作类型,并使用纯vuex?在那里,带有vuex4的vue3正在开发中,也许他们在打字稿方面做得更好。因此,让我们尝试纯vuex。通常,这不会影响项目的工作,它仍然可以工作,没有类型,但是请耐心等待。继续)
最初,我开始这样做,但是代码却非常可怕……
萧条
我必须继续前进。但是哪里未知。这是一个完全绝望的步骤。我决定从头开始制作一个 状态容器。该守则是在几个小时内起草的。结果还不错。类型起作用,状态是反应性的,甚至那里都是继承。但是不久,绝望的痛苦开始消退,常识开始重燃。总而言之,这个想法进入了垃圾箱。总的来说,这是全球事件总线模式。它仅适用于小型应用程序。总体而言,编写自己的Vuex仍然过高(至少在我看来)。然后我已经猜到我已经筋疲力尽了。但是现在撤退为时已晚。
但是,如果有人感兴趣,那么代码就在这里:(可能是徒劳地添加了这个片段,但是路径将是)
不要看起来紧张
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]
)
}
采用 率超越一切的自行车的诞生。Vuexok
对于不耐烦的人,代码在这里,简短的文档在这里。
最后,我写了一个很小的库,涵盖了所有的愿望清单,甚至超出了它的要求。但是首先是第一件事。
最简单的vuexok模块如下所示:
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
},
},
})
有点像vuex,但是...第10行是什么?
counterModule.mutations.plus(1)
哇!合法吗好吧,有了vuexok-是的,从法律上来说是这样的)createModule方法返回一个对象,该对象完全重复vuex模块的对象的结构,只是没有命名空间属性,我们可以使用它来调用变量和操作或获取状态和获取方法,所有类型都被保留。并从任何可以进口的地方。
组件呢?
有了它们,一切都很好,因为实际上它是vuex,所以原则上什么都没有改变,提交,分派,mapState等。像以前一样工作。
但是现在您可以使模块中的类型在组件中起作用:
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
}
}
就像在store.state中一样,模块中的state属性是反应性的,因此要在Vue组件中使用模块状态,只需在计算属性中返回模块状态的一部分即可。只有一个警告。我故意将Readonly状态设为类型,因此更改vuex状态并不好。
调用动作和突变很容易让人感到耻辱,并且还保存了输入参数的类型
private async doSomething() {
counterModule.mutations.setNumber(10)
// this.$store.commit('counterModule/setNumber', 10)
await counterModule.actions.increment()
// await this.$store.dispatch('counterModule/increment')
}
这真是美。稍后,我还需要对jwt中的更改做出反应,该更改也存储在商店中。然后watch方法出现在模块中。模块监视程序的工作方式与store.watch相同。唯一的区别是模块的状态和getter作为getter函数的参数传递。
const unwatch = jwtModule.watch(
(state) => state.jwt,
(jwt) => console.log(`New token: ${jwt}`),
{ immediate: true },
)
所以我们有:
- 打字的一面-是的
- 类型在组件中起作用-是
- 像vuex中的api一样,纯vuex之前的所有内容都不会破坏-是
- 与对方进行声明式工作-是的
- 小封包大小(〜400字节gzip)-是
- 无需将动作和突变的名称存储在常量中-
- 它应该工作-是
总的来说,奇怪的是,开箱即用的vuex中没有如此出色的功能,它多么方便!
至于对vuex4和vue3的支持-我尚未测试过,但从文档来看,它应该兼容。
这些文章中提出的问题也得到解决:
Vuex-用新方法解决旧争议
Vuex破坏封装
梦dream以求的
做到这一点太好了,这样可以在操作的上下文中使用突变和其他操作。
如何在打字稿类型的上下文中执行此操作-迪克知道这一点。但是,如果您可以这样做:
{
actions: {
one(injectee) {
injectee.actions.two()
},
two() {
console.log('tada!')
}
}
我的快乐是没有止境的。但是生活,像打字稿一样,是件艰苦的事情。
这是vuex和打字稿的历险记。好吧,我有点说出来。感谢您的关注。