Edited at

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


やりたいこと

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/


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を読み解いたものになります。

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

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すればローカルでも試せるはずです。

https://github.com/nwtgck/typescript-vue-async-computed-prac/commit/ed88abd38452722ff9da346d6f840a965c54812c

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