Vue 3.0   vue function api の 書き方一覧 with TypeScript

# 2019/09/08 追記
現在 function apiはcloseして Composition APIが新たにRFCとなっています。
Composition APIについても以下にまとめています。

https://qiita.com/ryo2132/items/f055679e9974dbc3f977

# はじめに

最近のVue.jsの話題といえば、次期メジャーバージョンアップのVue3.xで採用が予定され、現在RFCとなっている[vue-function-api](https://www.google.com/search?q=vue-function-api&oq=vue-function-api&aqs=chrome..69i57j69i60l3j0l2.3613j0j7&sourceid=chrome&ie=UTF-8) ですね。親しみがある今のオブジェクトベースのAPIとはかなり違うので賛否両論あるようですが、実際に使ってみるとTypeScriptで書きやすく個人的には好みです。

この記事ではTypeScriptでのvue-function-apiの書き方を紹介します。（随時追加予定）
自分もまだまだ勉強段階なので間違ってる箇所あれば、気軽にコメントで指摘お願いします。

※ サンプルコードでは、明示的に型アノテーションつけていますが、実際には自動的に型推論される箇所が多いです。
※ Vue3.x では従来のオブジェクトベースのAPIはサポートされるので、バージョンアップの際に置き換えが必ず必要というわけではないです。

# vue-function-apiの導入
vue-function-apiはpluginとして提供されているので、vue2.x でも使用可能です。
https://github.com/vuejs/vue-function-api

パッケージの追加

```bash
$ yarn add vue-function-api
```

pluginの設定

```typescript:main.ts
import Vue from 'vue'
import { plugin } from 'vue-function-api'

Vue.use(plugin)
```

以下にvue-function-api & typescript の練習用に作ったTodoアプリがあるので、実際の使い方はそちら参考にしてください。
https://github.com/kawamataryo/sandbox-vue-functional-api



# componetの作成 - `createComponent()`, `setup()`
vueのSFCの基本単位となるcomponentの作成は、`createComponent<T>()`を使います。
そして慣れ親しんだvueのリアクティブなデータや、メソッド定義は`createComponent<T>()`の引数オブジェクト内で`setup()`メソッドを定義して記述します。
また、template側で使う関数、変数は`setup()`内で変数に代入し、オブジェクトとしてreturnする必要があります。

**従来**

```javascript
export default {
  methods: {
    greet: function() {
      return "hello world"
    }
  }
}
```

**vue-function-api(TypeScript)**

```typescript

export default createComponent({
  setup() {
    const greet: string = () => {
      return "hello world";
    };
    return {
      greet
    };
  }
});
```

**型定義**


```typescript
declare type ComponentOptionsWithSetup<Props> = Omit<ComponentOptions<Vue>, 'props' | 'setup'> & {
    props?: Props;
    setup?: (this: undefined, props: {
        [K in keyof Props]: Props[K];
    }, context: Context) => object | null | undefined | void;
};
export declare function createComponent<Props>(compOpions: ComponentOptionsWithSetup<Props>): ComponentOptions<Vue>;
```


# リアクティブなデータの保持 - value()
オブジェクトベースのAPIでの`data(){return {}}`に相当するのが`value()`です。
vue-function-apiではオブジェクトでまとめず要素ごとに個別に定義します。そしてTypeScriptでは、`Wrapper<T>`を使って型定義します。
また、template側で使う変数は関数と同様`setup()`内で変数に代入し、オブジェクトとしてreturnする必要があります。

**従来**

```javascript
export default {
  data() {
    return {
      name: "taro",
      address: {
        postCode: 1234,
        city: "tokyo"
      }
    };
  }
};
```

**vue-function-api(TypeScript)**

```typescript
type Address = {
  postCode: number;
  city: string;
};

export default createComponent({
  setup() {
    const name: Wrapper<string> = value("taro");
    const address: Wrapper<Address> = value({
      postCode: 1234,
      city: "tokyo"
    });

    return {
      name,
      address
    };
  }
});

```

**型定義**

```typescript
export declare function value<T>(value: T): Wrapper<T>;
```



# 算出プロパティ - `computed`
リアクティブな依存関係にもとづきキャッシュされる算出プロパティは、`computed()`関数で定義します。


**従来**

```javascript
export default {
  data() {
    return {
      message: "Hello world";
    }
  },
  computed: {
    reversedMessage: function () {
      return this.message.split("").reverse().join("")
    }
  }
}
```

**vue-function-api(TypeScript)**

```typescript
export default createComponent({
  setup() {
    const message: Wrapper<string> = value("hello");
    
    const reverseMessage: Wrapper<string> = computed(() => {
      return message.value.split("").reverse().join("")
    });
    
    return {
      message,
      reverseMessage
    };
  }
});
```


**型定義**

```typescript

export declare function computed<T>(getter: () => T, setter?: (x: T) => void): Wrapper<T>;
```




# 親から子への値の受け渡し - `props`
親から子コンポーネントに値を受け渡す`props`は、`createComponentのジェネリクスとしてporpsの型を渡して、引数オブジェクトのpropsのプロパティの値として文字列配列をanyにupキャストした上で型定義が必要です。
型定義見ると、ジェネリクスと Mapped typesを使っていて難解ですね。



**従来**

```javascript
export default {
  props: {
    name: String,
    age: Number
  },
  methods: {
    userProfile: function {
      return `名前: ${name}, 年齢: ${age}`
    }
  }
}
```

**vue-function-api(TypeScript)**

```javascript
type Props = {
  name: string;
  age: string;
};

export default createComponent<Props>({
  props: (["name", "age"] as any) as PropType<Props>,
  setup(props) {
    const userProfile = (): string  => {
      return `名前: ${props.name}, 年齢: ${props.age}`
    }

    return {
      userProfile
    };
  }
});

```

**型定義**

```typescript
export declare type PropType<T> = T;
declare type ComponentOptionsWithSetup<Props> = Omit<ComponentOptions<Vue>, 'props' | 'setup'> & {
    props?: Props;
    setup?: (this: undefined, props: {
        [K in keyof Props]: Props[K];
    }, context: Context) => object | null | undefined | void;
};
```


# context関数 - `emit`, `refs`, `slot`...
子から親へのデータ通信の際のイベント発火に使うemitなどのインスタンスメソッドは、`setup()`メソッドが第2引数で受け取る、contextオブジェクトに定義されています。それを従来と同様の書き方で使えば大丈夫です。
例 emit



**従来**

```javascript
export default {
  methods: {
    emitGreet: function() {
      this.$emit('greet', 'Hello')
    }
  }
}
```
**vue-function-api(TypeScript)**

```typescript
export default createComponent({
  setup(props, context: Context) {
    const emitGreet = () => {
      context.emit("greet", 'Hello');
    };
  }
});

```

**型定義**

```typescript

export interface Context {
    readonly parent: Vue;
    readonly root: Vue;
    readonly refs: {
        [key: string]: Vue | Element | Vue[] | Element[];
    };
    readonly slots: {
        [key: string]: VNode[] | undefined;
    };
    readonly attrs: Record<string, string>;
    emit(event: string, ...args: any[]): void;
}
```


# 変更の監視 - `watch()`
カスタムウォッチャの`watch()`の定義は、`setup()`メソッド内の`watch`関数で定義します。
value側で適切に型定義をしていれば、特に型アノテーションは必要なく、型推論されます。


**従来**

```javascript
export default {
  data() {
    return {
      count: 0
    }
  },
  watch: {
    count: function (newVal, oldVal) {
      if (newVal > oldVal) {
        console.log("カウントアップ");
      } else {
        console.log("カウントダウン");
      }
    }
  },
}
```

**vue-function-api(TypeScript)**

```typescript
export default createComponent<Props>({
  setup() {
    const count: Wrapper<number> = () => value(0);

    watch(count, (newVal, oldVal) => {
      if (newVal.value > oldVal.value) {
        console.log("カウントアップ");
      } else {
        console.log("カウントダウン");
      }
    });

    return {
      count
    };
  }
});
```

**型定義**

```typescript

declare type watcherCallBack<T> = (newVal: T, oldVal: T) => void;
declare type watchedValue<T> = Wrapper<T> | (() => T);
declare type FlushMode = 'pre' | 'post' | 'sync';
interface WatcherOption {
    lazy: boolean;
    deep: boolean;
    flush: FlushMode;
}
export declare function watch<T>(source: watchedValue<T>, cb: watcherCallBack<T>, options?: Partial<WatcherOption>): () => void;
export declare function watch<T>(source: Array<watchedValue<T>>, cb: watcherCallBack<T[]>, options?: Partial<WatcherOption>): () => void;

```


#  Lifecycle Hooks
vueのライフサイクルに合わせて処理を行いたい場合に使うライフサイクルフックは、`setup()`メソッド内で関数として呼び出します。
処理はコールバックで渡します。

**従来**

```javascript

export default {
  created: function() {
   // 何らかの処理
  },
  mouted: function() {
   // 何らかの処理
  },
  beforeDestroy: funciton() {
   // 何らかの処理
  }
}
```

**vue-function-api(TypeScript)**

```typescript
export default createComponent({
  setup() {
    onCreated(() => { 
      //何らかの処理}
    })
    onMounted(() => { 
      //何らかの処理}
    })
    onBeforeDestroy(() => { 
      //何らかの処理}
    })
  }
});
```


**型定義**

```typescript

export declare const onCreated: (callback: Function) => void;
export declare const onBeforeMount: (callback: Function) => void;
export declare const onMounted: (callback: Function) => void;
export declare const onBeforeUpdate: (callback: Function) => void;
export declare const onUpdated: (callback: Function) => void;
export declare const onActivated: (callback: Function) => void;
export declare const onDeactivated: (callback: Function) => void;
export declare const onBeforeDestroy: (callback: Function) => void;
export declare const onDestroyed: (callback: Function) => void;
export declare const onErrorCaptured: (callback: Function) => void;
export declare const onUnmounted: (callback: Function) => void;
```


# 参考
* https://github.com/vuejs/vue-function-api
* https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md#complex-prop-types