vuex + typescript = vuexok. Das Fahrrad, das fuhr und alle überholte

Schönen Tag.



Wie viele Entwickler schreibe ich in meiner Freizeit mein eigenes  relativ  kleines Projekt. Früher habe ich in React geschrieben, aber bei der Arbeit benutze ich Vue. Nun, um Vue zu pumpen, fing ich an, mein Projekt darauf zu sehen. Zuerst war alles in Ordnung, geradezu rosig, bis ich entschied, dass ich das Typoskript noch verbessern musste. So erschien Typoskript in meinem Projekt. Und wenn alle Komponenten  gut waren , dann war vuex alles traurig. Also musste ich alle 5 Phasen durchlaufen, um das Problem zu akzeptieren, na ja, fast alles.



Negation



Grundvoraussetzungen für ein Geschäft:



  1. Typoskripttypen sollten in Modulen funktionieren
  2. Module sollten einfach in Komponenten zu verwenden sein, Typen für Zustände, Aktionen, Mutationen und Getter sollten funktionieren
  3. Lassen Sie sich keine neue API für vuex einfallen. Sie müssen sicherstellen, dass Typoskripttypen irgendwie mit vuex-Modulen funktionieren, damit Sie nicht die gesamte Anwendung auf einmal neu schreiben müssen
  4. Das Aufrufen von Mutationen und Aktionen sollte so einfach und unkompliziert wie möglich sein
  5. Das Paket sollte so klein wie möglich sein
  6. Ich möchte keine Konstanten mit Namen von Mutationen und Aktionen speichern
  7. Es sollte funktionieren (und was ist ohne es)


Es kann nicht sein, dass ein so ausgereiftes Projekt wie vuex keine normale Typoskript-Unterstützung hatte. Nun, wir haben Google  Yandex geöffnet  und sind gefahren. Ich war mir zu 100500% sicher, dass mit Typoskript alles in Ordnung sein sollte (wie falsch ich war). Es gibt viele verschiedene Versuche, Freunde zu Vuex und Typoskript zu machen. Ich werde einige Beispiele nennen, an die ich mich erinnere, ohne den Code, um den Artikel nicht aufzublähen. Alles finden Sie in der Dokumentation unter den folgenden Links.



vuex-smart-module



github.com/ktsn/vuex-smart-module

Gut, sehr gut. Alles bei mir, aber persönlich hat mir nicht gefallen, dass Sie für Aktionen, Mutationen, Zustände, Getter separate Klassen erstellen müssen. Das ist natürlich Geschmack, aber das bin ich und mein Projekt.) Und im Allgemeinen wurde das Problem der Eingabe nicht vollständig gelöst ( Kommentarthread mit einer Erklärung, warum ).



Vuex Typescript Support



Netter Versuch, aber viel Umschreiben und im Allgemeinen von der Community nicht akzeptiert.



vuex-module-decorators



Dies schien der perfekte Weg zu sein, um Vuex- und Typoskript-Freunde zu finden. Es sieht aus wie der Vue-Property-Decorator, den ich in der Entwicklung verwende. Sie können mit dem Modul wie mit einer Klasse arbeiten, im Allgemeinen super, aber ...



Aber es gibt keine Vererbung. Modulklassen werden nicht korrekt vererbt und das Problem hängt schon sehr lange an diesem Problem! Und ohne Vererbung wird es viele Codeduplikationen geben. Pfannkuchen…



Zorn



Dann war es überhaupt nicht sehr viel, gut oder ± dasselbe - es gibt keine ideale Lösung. Dies ist genau der Moment, in dem Sie sich sagen: Warum habe ich angefangen, ein Projekt in vue zu schreiben? Nun, Sie wissen, reagieren, nun, ich würde auf reagieren schreiben, es würde keine solchen Probleme geben! Bei der Hauptarbeit ist das Projekt in vollem Gange und Sie müssen es aktualisieren - treffen Sie das Argument. Lohnt es sich, die Nerven und schlaflosen Nächte zu verbringen? Setz dich wie alle anderen, schreibe komponentiki, nein, du brauchst am allermeisten! Wirf diesen Vue! Schreiben Sie, um zu reagieren, ein Upgrade durchzuführen und mehr dafür zu bezahlen!



In diesem Moment war ich bereit, Vue wie kein anderer zu hassen, aber es war Emotion, und die Intelligenz war immer noch höher. Vue hat (meiner subjektiven Meinung nach) viele Vorteile gegenüber der Reaktion, aber es gibt keine Perfektion sowie Gewinner auf dem Schlachtfeld. Sowohl Vue als auch React sind auf ihre eigene Weise gut, und da ein wesentlicher Teil des Projekts bereits in Vue geschrieben ist, wäre es so dumm wie möglich, jetzt zu reagieren. Ich musste mich entscheiden, was ich mit Vuex machen sollte.



Schnäppchen



Nun, die Dinge laufen nicht gut. Vielleicht dann vuex-smart-Modul? Dieses Paket scheint gut zu sein, ja, Sie müssen viele Klassen erstellen, aber es funktioniert großartig. Oder versuchen Sie vielleicht, Typen für Mutationen und Aktionen manuell in Komponenten zu schreiben und reinen Vuex zu verwenden? Dort ist vue3 mit vuex4 unterwegs, vielleicht machen sie es besser mit Typoskript. Probieren wir also reinen Vuex. Im Allgemeinen wirkt sich dies nicht auf die Arbeit des Projekts aus, es funktioniert immer noch, es gibt keine Typen, aber Sie halten fest. Und warte)



Zuerst habe ich damit angefangen, aber der Code stellt sich als monströs heraus ...



Depression



Ich musste weitermachen. Aber wo ist unbekannt. Es war ein völlig verzweifelter Schritt. Ich beschloss, einen  Staatscontainer von Grund auf neu zu erstellen . Der Code wurde in wenigen Stunden erstellt. Und es stellte sich sogar gut heraus. Typen funktionieren, der Status ist reaktiv und sogar die Vererbung ist vorhanden. Aber bald ließ die Qual der Verzweiflung nach und der gesunde Menschenverstand kehrte zurück. Alles in allem ging diese Idee in den Mülleimer. Im Großen und Ganzen war dies das globale Ereignisbusmuster. Und es ist nur für kleine Anwendungen gut. Und im Allgemeinen ist das Schreiben eines eigenen Vuex immer noch ziemlich übertrieben (zumindest in meiner Situation). Dann habe ich schon vermutet, dass ich völlig erschöpft war. Aber es war zu spät, um sich zurückzuziehen.



Aber wenn jemand interessiert ist, dann ist der Code hier: (Wahrscheinlich vergeblich dieses Fragment hinzugefügt, aber der Pfad wird sein)



nicht nervös aussehen
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]
  )
}




Annahme Die  Geburt des Fahrrads, das alle überholt hat. vuexok



Für die Ungeduldigen ist der Code hier , die kurze Dokumentation ist hier .



Am Ende habe ich eine winzige Bibliothek geschrieben, die die gesamte Wunschliste abdeckt und sogar ein bisschen mehr, als dafür erforderlich war. Aber das Wichtigste zuerst.



Das einfachste vuexok-Modul sieht folgendermaßen aus:



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
    },
  },
})


Na ja, irgendwie wie Vuex ... was steht in Zeile 10?



counterModule.mutations.plus(1)


Whoa! Ist es legal? Nun, mit vuexok - ja, legal) Die createModule-Methode gibt ein Objekt zurück, das die Struktur des Objekts des vuex-Moduls genau wiederholt, nur ohne die Eigenschaft namespaced, und wir können damit Mutationen und Aktionen aufrufen oder Status und Getter abrufen. Alle Typen bleiben erhalten. Und von jedem Ort, an dem es importiert werden kann.



Was ist mit den Komponenten?



Und mit ihnen ist alles in Ordnung, da es sich tatsächlich um Vuex handelt, hat sich im Prinzip nichts geändert, festgeschrieben, versendet, mapState usw. arbeite wie zuvor.



Jetzt können Sie die Typen aus dem Modul in den Komponenten zum Laufen bringen:



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
  }
}


Die state-Eigenschaft in einem Modul ist genau wie in store.state reaktiv. Um den Modulstatus in Vue-Komponenten zu verwenden, müssen Sie nur einen Teil des Modulstatus in einer berechneten Eigenschaft zurückgeben. Es gibt nur eine Einschränkung. Ich habe den Readonly-Status absichtlich zu einem Typ gemacht. Es ist nicht gut, den Vuex-Status so zu ändern.



Das Aufrufen von Aktionen und Mutationen ist einfach in Ungnade zu fallen, und die Arten von Eingabeparametern werden ebenfalls gespeichert



 private async doSomething() {
   counterModule.mutations.setNumber(10)
   //   this.$store.commit('counterModule/setNumber', 10)
   await counterModule.actions.increment()
   //   await this.$store.dispatch('counterModule/increment')
 }


Hier ist so eine Schönheit. Wenig später musste ich auch auf die Änderung von jwt reagieren, die ebenfalls im Laden gespeichert ist. Und dann erschien die Watch-Methode in Modulen. Modulbeobachter arbeiten genauso wie store.watch. Der einzige Unterschied besteht darin, dass der Status und die Getter des Moduls als Parameter der Getter-Funktion übergeben werden.



const unwatch = jwtModule.watch(
  (state) => state.jwt,
  (jwt) => console.log(`New token: ${jwt}`),
  { immediate: true },
)


Also was wir haben:



  1. getippte Seite - ja
  2. Typen arbeiten in Komponenten - ja
  3. api wie in vuex und alles was vorher auf reinem vuex war bricht nicht - ist
  4. deklarative Arbeit mit der Seite - ja
  5. kleine Paketgröße (~ 400 Bytes gzip) - ja
  6. Es ist nicht erforderlich, die Namen von Aktionen und Mutationen in Konstanten zu speichern
  7. es sollte funktionieren - ist


Im Allgemeinen ist es seltsam, dass eine so wunderbare Funktion in vuex nicht sofort verfügbar ist. Es ist fantastisch, wie praktisch sie ist!

Was die Unterstützung für vuex4 und vue3 betrifft - ich habe es nicht getestet, aber nach den Dokumenten sollte es kompatibel sein.



Die in diesen Artikeln vorgestellten Probleme werden ebenfalls gelöst:



Vuex - Lösung eines alten Streits mit neuen Methoden

Vuex unterbricht die Kapselung



Feuchte Träume:



Es wäre großartig, Mutationen und andere Aktionen im Kontext von Aktionen zur Verfügung zu haben.



Wie man das im Kontext von Typoskripttypen macht - der Schwanz weiß es. Aber wenn Sie dies tun könnten:



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


Dass meine Freude keine Grenzen haben würde. Aber das Leben ist wie das Typoskript eine harte Sache.



Hier ist das Abenteuer mit Vuex und Typoskript. Nun, ich habe mich irgendwie ausgesprochen. Vielen Dank für Ihre Aufmerksamkeit.



All Articles