VueCompostionAPIにおける refとreactiveの使い方について

## はじめに

前回の記事からの続きになります

[Vue3.xから採用予定のCompositionAPIとVue2.xを比較してみる](https://qiita.com/tomopict/items/95ef9e043c1fc4d9f218)

[前回の記事](https://qiita.com/tomopict/items/95ef9e043c1fc4d9f218)を書いた際に`ref`と`reactive`の違いについて

> これらについてはまだ実装しながら確認している最中なので、また別途まとめたいと思います。

と記載していました。
今回はそれらについて実装を進めつつまた残しておきたいと思います。

なお確認の際に実装したソースコードは以下のgithubにあげています。
[vue-composition-api-test](https://github.com/tomopict/vue-composition-api-test)

## 前提とする環境
VueCLI v4.0.5
node v10.16.0
yarn v1.13.0

## CompositionAPIのリアクティブなオブジェクト

compostionAPIでリアクティブなオブジェクトを定義する場合、refとreactiveの２種類があります。
それらについてふれていきます。

## refについて

refは単一のvalueプロパティをもつプリミティブオブジェクトです。
イメージ的には以前data()で定義されていたものが近いです。

### 呼び出しかた

vue2.x系で使用する場合はcompositionAPIからインポートします。

```vue:HelloRef.vue
import { createComponent, ref } from "@vue/composition-api";
```

### 定義のしかた

`setup()`関数内で以下のように変数を定義します。

```js:HelloRef.vue
const RefHelloCount = ref(1);
```

配列を定義することも可能です。

```js:HelloRef.vue
const Animals = ref(["cat", "dog", "hamster"]);
```

以下のようなデータを定義する場合は後述の`reactive`を使用します。

```js:HelloRef.vue
let RefAnimalObjects = ref({
  cat: 1,
  dog: 2
});
```

### 値へのアクセス方法

変数の値には、`setup()`関数内のメソッドなどで呼び出す場合は`変数名.value`で、テンプレートからはそのまま`{{変数名}}`で呼び出します。

それぞれ確認していきます、

#### setup()関数内での呼び出し

数字を増加させるだけのメソッドを導入してみます。
以下のように記述すると失敗します。

```js:HelloRef.vue
const incrementHello = () => {
   RefHelloCount++;
};
```

なぜなら`ref`で定義された変数はプリミティブな**オブジェクト**であるからです。
consoleに出力してみたデータを確認したところ以下のような表示になります。

<img width="157" alt="スクリーンショット 2019-11-15 20.59.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191638/4178b9ac-7c1b-1874-bed6-78db4d2acad1.png">

そのため先ほどの処理を以下のように修正します。

```js:HelloRef.vue
const incrementHello = () => {
    refHelloCount.value++;
};
```

これで問題なく動作します。

#### templateでの呼び出し

tempalteへの表示を行う際には`value`の表記はせずに記載しても問題なく表示がなされます。
APIの内部で`value`を`expose`するような処理が走っているので`value`なしの状態で記載します。


```vue:HelloRef.vue
<p>{{ RefHelloCount }}</p>
```
### 注意点

* setup関数内で呼び出す際の扱い方とtemplate内での呼び出し方の違い
    * templateないで呼び出すときは`value`がなくても大丈夫。API側がよしなに変更してくれる
    * setup関数内で呼び出すときは`value`が必要
* オブジェクトを定義する場合は`reactive`を使用する

## reactive

もう一つの`reactive`を確認します。
こちらもrefと同じように`composition-api`からインポートします。

```js:HelloRef.vue
import { createComponent, reactive } from "@vue/composition-api";
```

### 定義のしかた

先ほど一つ一つrefにで定義して分散して記載していたものをまとめることができます。
returnする際にも定義した一つの変数を記載すればすみます。

```js:HelloRef.vue
const ReactiveData = reactive({
    count: 1,
    ReativeAnimals: ["cat", "dog", "hamster"]
});

return {
    ReactiveData
};
```

### 値へのアクセス方法

上記のように定義した場合、`setup()`関数内で使用する場合は`変数名.key`でアクセスします。テンプレート変数で利用する場合も`変数名.key`(valueは不要)でアクセスをします。

それぞれ確認していきます。


#### setup()関数内での呼び出し

`変数名.key`で該当の値にアクセスします。
なおこの際に`value`は不要です。

この点に関しては`ref`で作成したデータにアクセスするよりはシンプルでわかりやすいように思えます。

```js:HelloRef.vue
const incrementHelloReactve = () => {
    ReactiveData.count++;
};
```

#### templateでの呼び出し
値へのアクセスで触れましたが、`template`内で該当の変数を呼び出すのに`ref`に比べて冗長になりがちです。

```vue:HelloRef.vue

return {
    ReactiveData
};

↓ 上記データをtemplate内で使用する

<p>
    {{ReactiveData.count}}
    {{ReactiveData.animals}}
</p>
```

そこでこのオブジェクトをプレーンなオブジェクトに変換する`toRefs`というメソッドを利用します。

https://vue-composition-api-rfc.netlify.com/api.html#torefs

これを利用するとそれぞれのプロパティがplainなオブジェクトに変換され、templateから呼び出す際に冗長な書き方が不要になります。

```js
return {
    ...toRefs(ReactiveData)
};
```

return分の中で上記のように記載すると`template`内での記述が以下のように変わります。

```vue
<p>
    {{count}}
    {{animals}}
</p>
```

非常にシンプルになりました。

### API内部で何をやっているのか

compostionAPIの内部で何をやっているのか確認してみました。
`node_modules/@vue/compostion-api/vue-composition-api.module.js`の
174行目から191行目にそれらしき記述があります。

```js:composition-api
function isPlainObject(x) {
    return toString(x) === '[object Object]';
}

function toRefs(obj) {
    if (!isPlainObject(obj))
        return obj;
    var res = {};
    Object.keys(obj).forEach(function (key) {
        var val = obj[key];
        // use ref to proxy the property
        if (!isRef(val)) {
            val = createRef({
                get: function () { return obj[key]; },
                set: function (v) { return (obj[key] = v); },
            });
        }
        // todo
        res[key] = val;
    });
    return res;
}
```

`isPlainObject`でオブジェクトかどうかを判別して、該当していた場合は渡され`obj`を`forEach`で回してgetter,setterを定義してやっているようです。

## まとめ

最後までお読みいただきありがとうございます。
refとreactiveの違いについてざっくりと確認してきました。

ここまで触ってきての印象は`ref`で定義する変数は他と関連性が薄いもの、独立しているものが適しているように感じます。
かたや`reactive`に関していうと、一つの変数の中に`key:value`の形でそれぞれが関連するものとして定義した方がいいものが適していると感じます。
例えばユーザーの情報など。

例：

```js
const User = reactive({
    name: 'Taro',
    age: '19',
    sex: 'male'
})
```

まだcompostionAPI自体ドキュメントが揃いきっているわけではないので、どのように運用していくのがいいのか、個人のプロダクトなどに突っ込んでいくことで判断していきたいと思います。

## 参考

* [公式APIリファレンス](https://vue-composition-api-rfc.netlify.com/api.html#torefs)