LoginSignup
16
7

More than 3 years have passed since last update.

Vue + TypeScriptで非同期な算出プロパティを使いたい! - Promiseとかasync-await

Last updated at Posted at 2019-04-08

やりたいこと

VueをTypeScriptでを使うときに、非同期な(Promiseとかasync-await)オブジェクトを算出プロパティとして使いたいです。

最終的な見た目

以下のように、@AsyncComputedをつけたasync functionが使えるようになります。

@Component
export default class HelloWorld extends Vue {

  @AsyncComputed()
  async myAsyncMessage(): Promise<string> {
    return new Promise(resolve => {
      setTimeout(()=>{
        resolve("This is an asynchronous message!");
      }, 1000);
    });
  }
}

テンプレートは以下みたいになります。

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    {{ myAsyncMessage }}
  </div>
</template>

動くコード

以下が、オンラインのデモページです。
開いて1秒後に「This is a asynchronous message!」と出るはずです。見逃したらリロードしてみてください。
https://nwtgck-typescript-vue-async-computed-prac.netlify.com/

nwtgck-typescript-vue-async-computed-prac.mp4.opt.gif

GitHubリポジトリ

こちらがリポジトリです。
なるべくミニマルに作るように心がけてます。
https://github.com/nwtgck/typescript-vue-async-computed-prac

以下のgitの差分をみることで、どうやって@AsyncComputedに対応するかは、記事を読まなくても分かると思います。
https://github.com/nwtgck/typescript-vue-async-computed-prac/commit/ed88abd38452722ff9da346d6f840a965c54812c

「記事通りに進めば対応できる」を目指して、ステップバイステップで対応方法を書きたいと思います。

対応手順

vue-async-computedというライブラリを使います。
ただ、vue-async-computed公式がTypeScriptに対応していません。TypeScript対応に関してのissueは出ています。
Issue: https://github.com/foxbenjaminfox/vue-async-computed/issues/25
この対応手順はこのissueを読み解いたものになります。


追記(2019年12月24日)
つい4時間ほど前に3.8.0がリリースされて、公式にTypeScript対応されるようになりました!動作を確認して、その結果をこの記事にも反映できたらと思います。
image.png
(https://github.com/foxbenjaminfox/vue-async-computed/issues/25#issuecomment-568567343)


まずは、以下のコマンドでライブラリをインストールします。

npm i -S vue-async-computed

次にvue-async-computedの型定義を追加します。
typesというディレクトリを作ってその中に置きました。

types/vue-async-computed.d.ts
// (base: https://raw.githubusercontent.com/saraedum/vue-async-computed/5debb7dcd81f52183be55e05b866fc43278a9905/types/index.d.ts)

declare module 'vue-async-computed' {
  import Vue, { PluginFunction } from "vue";

  interface IAsyncComputedOptions {
    errorHandler?: (error: string[]) => void;
    useRawError?: boolean;
    default?: any;
  }

  export default class AsyncComputed {
    constructor(options?: IAsyncComputedOptions)
    static install: PluginFunction<never>;
    static version: string;
  }

  type AsyncComputedGetter<T> = () => (Promise<T> | T);
  export interface IAsyncComputedProperty<T> {
    default?: T | (() => T);
    get?: AsyncComputedGetter<T>;
    watch?: () => void;
    shouldUpdate?: () => boolean;
    lazy?: boolean;
  }

  interface IAsyncComputedProperties {
    [K: string]: AsyncComputedGetter<any> | IAsyncComputedProperty<any>;
  }
}

declare module "vue/types/options" {
  import Vue from "vue";
  import { IAsyncComputedProperties } from "vue-async-computed";

  export class InjectKey{}

  // tslint:disable-next-line:interface-name
  interface ComponentOptions<V extends Vue> {
    asyncComputed?: IAsyncComputedProperties;
  }
}

interface IASyncComputedState {
  state: "updating" | "success" | "error";
  updating: boolean;
  success: boolean;
  error: boolean;
  exception: Error | null;
  update: () => void;
}

declare module "vue/types/vue" {
  // tslint:disable-next-line:interface-name
  interface Vue {
    $asyncComputed: {[K: string]: IASyncComputedState };
  }
}

以下のように、tsconfig.json"*": [ "types/*"]を追加します。
これは上記のvue-async-computed.d.tsを違う場所に置いた人や、もともと似た設定がある人は、適宜変更してください。

tsconfig.json
...
    "paths": {
      ...

      "*": [
        "types/*"
      ]
    },
...

次は以下のコードをsrc/main.tsに追記します。
(この追加はvue-async-computedの公式READMEに書かれているものと同じです。)

src/main.ts
import AsyncComputed from 'vue-async-computed'

Vue.use(AsyncComputed)

最後に以下のAsyncComputed.tsを好きなところに置きます。
src/AsyncComputed.tsに置きました。

// (from: https://github.com/foxbenjaminfox/vue-async-computed/issues/25#issuecomment-459369072)

import { createDecorator, VueDecorator } from 'vue-class-component';
import { IAsyncComputedProperty } from 'vue-async-computed';

export function AsyncComputed<T>(
    computedOptions?: IAsyncComputedProperty<T>,
): VueDecorator {
    return createDecorator((_options, key) => {
        // TODO: Not use any casting
        const options: any = _options as any;
        options.asyncComputed = options.asyncComputed || {};
        const method = options.methods![key];
        options.asyncComputed![key] = <IAsyncComputedProperty<T>>{
            get: method,
            ...computedOptions,
        };
        delete options.methods![key];
    });
}

あとは、AsyncComputedを使えます。
例えば、以下みたいに自分のVueのコンポーネントに@AsyncComputedが使えます。

import { AsyncComputed } from '../AsyncComputed';

@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;

  @AsyncComputed()
  async myAsyncMessage(): Promise<string> {
    return new Promise(resolve => {
      setTimeout(()=>{
        resolve("This is an asynchronous message!");
      }, 1000);
    });
  }
}

おわりに

繰り返しにはなってしまいますが、
以下のgitの差分をみるのが確実です。動きも確認できますし、デプロイできてますし、git cloneして、npm run serveすればローカルでも試せるはずです。

vue-async-computed公式でTypeScript対応してくれると嬉しいですね。

16
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
7