やりたいこと
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を読み解いたものになります。
追記(2019年12月24日)
つい4時間ほど前に3.8.0がリリースされて、公式にTypeScript対応されるようになりました!動作を確認して、その結果をこの記事にも反映できたらと思います。
(https://github.com/foxbenjaminfox/vue-async-computed/issues/25#issuecomment-568567343)
まずは、以下のコマンドでライブラリをインストールします。
npm i -S vue-async-computed
次にvue-async-computedの型定義を追加します。
types
というディレクトリを作ってその中に置きました。
// (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
を違う場所に置いた人や、もともと似た設定がある人は、適宜変更してください。
...
"paths": {
...
"*": [
"types/*"
]
},
...
次は以下のコードをsrc/main.ts
に追記します。
(この追加はvue-async-computedの公式READMEに書かれているものと同じです。)
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対応してくれると嬉しいですね。