vuex + typescript = vuexok。みんなに乗って追い抜いたバイク

良い一日。



多くの開発者のように、私は自分の比較的 小さなプロジェクトを空き時間に書いてい ます。以前はreactで書いていましたが、仕事ではvueを使用しています。さて、ビューをポンピングするために、私はそれに自分のプロジェクトを見始めました。最初は、タイプスクリプトを改善する必要があると判断するまで、すべてが順調で、まったくバラ色でした。これが私のプロジェクトでtypescriptがどのように現れたかです。そして、すべてのコンポーネントが 良ければ、vuexはすべてが悲しかったです。それで、私は問題を受け入れる5つの段階すべてを通過しなければなりませんでした、まあ、ほとんどすべて。



否定



ストアの基本要件:



  1. Typescriptタイプはモジュールで機能するはずです
  2. モジュールはコンポーネントで使いやすく、状態のタイプ、アクション、ミューテーション、ゲッターが機能する必要があります
  3. vuexの新しいapiを考え出さないでください。アプリケーション全体を一度に書き直す必要がないように、typescriptタイプがvuexモジュールで何らかの形で機能することを確認する必要があります。
  4. ミューテーションとアクションの呼び出しは、可能な限り単純でわかりやすいものにする必要があります
  5. パッケージはできるだけ小さくする必要があります
  6. 突然変異やアクションの名前を持つ定数を保存したくない
  7. それは機能するはずです(そしてそれなしではどうですか)


vuexのような成熟したプロジェクトが通常のタイプスクリプトをサポートしていなかったということはあり得ません。さて、Google  Yandexを開いて 、運転しました。私は、typescriptですべてがうまくいくはずだと100500%確信していました(私がどれほど間違っていたか)。友達にvuexとtypescriptを作るためのさまざまな試みがたくさんあります。記事を肥大化させないように、コードなしで覚えているいくつかの例を示します。すべては、以下のリンクのドキュメントにあります。



vuex-smart-module



github.com/ktsn/vuex-smart-module

良い、とても良い。私と一緒にすべてが、個人的には、アクション、ミューテーション、状態、ゲッターのために、別々のクラスを作成する必要があるという事実が好きではありませんでした。もちろん、これは好みですが、これは私と私のプロジェクトです)そして、一般的に、入力の問題は完全には解決されていません(理由を説明したコメントスレッド)。



Vuex Typescriptサポート



素晴らしい試みですが、多くの書き直しがあり、一般的にコミュニティに受け入れられていません。



vuex-module-decorators



これは、vuexとtypescriptの友達を作るのに最適な方法のように思えました。私が開発で使用しているvue-property-decoratorのように見えます。クラスと同じようにモジュールを操作できますが、一般的にはスーパーですが...



しかし、継承はありません。モジュールクラスは正しく継承されておらず、この問題は非常に長い間この問題にかかっています。そして、継承がなければ、多くのコードの重複が発生します。パンケーキ…



怒り



それから、それはまったく、まあ、または±同じではありませんでした-理想的な解決策はありません。これはあなたが自分自身に言うまさにその瞬間です:なぜ私はvueでプロジェクトを書き始めたのですか?まあ、あなたは反応することを知っています、まあ、私は反応について書きます、そのような問題はありません!主な作業では、プロジェクトは進行中であり、アップグレードする必要があります-議論を打ってください。神経質になって眠れない夜を過ごす価値はありますか?他のみんなと同じように座って、komponentikiを書いてください、いや、何よりも必要です!このビューを投げる!反応するために書いて、それにアップグレードして、それにもっとお金を払ってください!



その瞬間、私は他に類を見ないほどのvueを嫌う準備ができていましたが、それは感情であり、知性はまだそれを上回っていました。Vueには(私の主観的な意見では)反応よりも多くの利点がありますが、戦場での勝者と同様に完璧はありません。vueとreactはどちらも独自の方法で優れており、プロジェクトの大部分はすでにvueで記述されているため、今すぐreactに切り替えるのは可能な限り愚かです。私はvuexをどうするかを決めなければなりませんでした。



バーゲン



まあ、物事はうまくいっていません。多分それならvuex-smart-module?このパッケージは良いようです、はい、たくさんのクラスを作成する必要がありますが、それはうまく機能します。あるいは、コンポーネントに手動で突然変異とアクションのタイプを書いて、純粋なvuexを使用することもできますか?そこでは、vuex4を使用したvue3が進行中です。おそらく、typescriptを使用した方がうまくいっています。それでは、純粋な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
  }
}


モジュールのstateプロパティは、store.stateの場合と同様にリアクティブであるため、Vueコンポーネントでモジュールの状態を使用するには、計算されたプロパティでモジュールの状態の一部を返す必要があります。注意点は1つだけです。意図的に読み取り専用状態をタイプにしたので、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と同じように機能します。唯一の違いは、モジュールの状態とゲッターがゲッター関数のパラメーターとして渡されることです。



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はカプセル化を破ります



夢精:



突然変異や他のアクションがアクションのコンテキストで利用できるようにするのは素晴らしいことです。



タイプスクリプトタイプのコンテキストでこれを行う方法-ディックはそれを知っています。しかし、これを行うことができれば:



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


私の喜びに制限はありません。しかし、タイプスクリプトのように、人生は厳しいものです。



これがvuexとtypescriptを使った冒険です。ええと、私は一種の発言をしました。ご清聴ありがとうございました。



All Articles