vuex +打字稿= vuexok。骑行超越所有人的自行车

美好的一天。



像许多开发人员一样,我在业余时间编写自己的 相对 较小的项目。我曾经写过react,但是在工作中我使用vue。好吧,为了发挥作用,我开始在上面看到自己的项目。刚开始时一切都很好,非常红润,直到我决定仍然需要改进打字稿。这就是打字稿出现在我的项目中的方式。如果所有组件都 很好,那么vuex一切都会令人难过。因此,我必须经历接受问题的所有5个阶段,几乎所有事情。



否定



商店的基本要求:



  1. 打字稿类型应在模块中工作
  2. 模块应易于在组件中使用,状态,动作,突变和吸气剂的类型应起作用
  3. 不要为vuex提出新的api,您需要确保打字稿类型可以以某种方式与vuex模块一起使用,这样就不必一次重写整个应用程序
  4. 调用变异和操作应尽可能简单明了
  5. 包装应尽可能小
  6. 我不想存储带有突变和动作名称的常量
  7. 它应该工作(没有它怎么办)


像vuex这样的成熟项目不可能没有正常的打字稿支持。好吧,我们打开 Google  Yandex开车。我100500%确信打字稿应该没问题(我错了)。有很多不同的尝试可以结识朋友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 },
)


所以我们有:



  1. 打字的一面-是的
  2. 类型在组件中起作用-是
  3. 像vuex中的api一样,纯vuex之前的所有内容都不会破​​坏-是
  4. 与对方进行声明式工作-是的
  5. 小封包大小(〜400字节gzip)-是
  6. 无需将动作和突变的名称存储在常量中-
  7. 它应该工作-是


总的来说,奇怪的是,开箱即用的vuex中没有如此出色的功能,它多么方便!

至于对vuex4和vue3的支持-我尚未测试过,但从文档来看,它应该兼容。



这些文章中提出的问题也得到解决:



Vuex-用新方法解决旧争议

Vuex破坏封装



梦dream以求的



做到这一点太好了,这样可以在操作的上下文中使用突变和其他操作。



如何在打字稿类型的上下文中执行此操作-迪克知道这一点。但是,如果您可以这样做:



{
  actions: {
    one(injectee) {
       injectee.actions.two()
    },
    two() {
      console.log('tada!')
    }
}


我的快乐是没有止境的。但是生活,像打字稿一样,是件艰苦的事情。



这是vuex和打字稿的历险记。好吧,我有点说出来。感谢您的关注。



All Articles