Vue.jsで関数型コンポーネントをつかった話

この記事は、 [大阪工業大学 Advent Calendar 2019](https://adventar.org/calendars/4049)の5日目の記事です。

## はじめに
つい先日のお話。新規サービス作ろうね〜ということで新しいタスクが降ってきました。
新しいアプリじゃん！！

ということで、このサービス開発の一部分にAtomic Designと関数型コンポーネントを使ってみたよってのが今回のおはなし。

## 自己紹介
某でフロントまわりを触ってます。ケモミミが好き。
大学自体はすでにOBだけど、参加条件的にはOKみたいなんでやります！

## Atomic Design
Atomic Designは、ざっくり書くと画面を段階に分けてパーツの作成・組み合わせを行う手法です。
![Atomic Design](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478491/a88a4e3b-855b-3e5d-7f24-e43612ac957e.png)
最小の要素（Atoms）から作って
Atomsを組み合わせて1つの機能をもつ要素（Molecules）を作って
Atoms,Moleculesを組み合わせて、画面を構成するいち要素（Organisms）を作って
最後、作った要素群を利用してテンプレート・ページを構成していく感じ。
（画像はわかりにくくてすいません:bow:）

Atomic Designの詳細と、それをVue.jsに落とし込むところについては、他の方の記事をお探しくださいな。

今回Atomic Designで組んだコンポーネントのうち、一部のAtomsを関数型コンポーネントで組んでみました。

## 関数型コンポーネント
※Vue.jsの公式リファレンスでは関数型コンポーネントの説明にRender関数を利用していますが、今回は主に単一ファイルコンポーネントでやっていきます

[公式](https://jp.vuejs.org/v2/guide/render-function.html#%E9%96%A2%E6%95%B0%E5%9E%8B%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88)曰く、関数型コンポーネントは状態を持たず、描画コストの少ないコンポーネントを作ることができるやつですね。
（具体的になんで描画コストが少なく済むのかをちゃんと理解してない:innocent:）

単一ファイルコンポーネントで使うときは、templateタグに`functional`を追加すると関数型コンポーネントとして認識されます。

```vue:HeadOne.vue
<template functional>
  <h1 class="text-2xl font-bold">
    <slot />
  </h1>
</template>
```
こんなイメージ。
ちなみに、vueのdevtoolではこんな感じで`functional`って表示されます。
![開発時の表示イメージ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478491/fea14f98-429a-4303-4266-e6044fa4da82.png)

### 値周りは気をつけて
関数型コンポーネントは、値の統合などを明示的に書いてあげる必要があります。
例えば**クラス**の設定ですね。

仮に通常のコンポーネント`Child.vue`を用意したとして

```vue:Child.vue
<template>
  <p class="leading-loose">ほげほげ</p>
</template>
```

任意のコンポーネントで読み込んであげて、呼び出し時にクラスを足します。

```vue:Parent.vue
<template>
  <div>
    <child class="text-gray" />
  </div>
</template>

<script>
import Child from '@/components/Child.vue'
export default {
  components: {
    Child,
  },
}
<script>
```

これを実行すると、pタグのclassに`text-gray`が追加されます。

```html:出力結果
<div>
  <p class="leading-loose text-gray">ほげほげ</p>
<div>
```
よくある挙動ですね。

じゃあ関数型コンポーネントだとどうなる？
関数型コンポーネント`Child.vue`で今回、上の例と同じように静的なclassを統合させたい時には

```vue:Child.vue
<template functional>
  <p 
    class="leading-loose" 
    :class="data.staticClass"
  >
    ほげほげ
  </p>
</template>
```

のように書けばOK。

ただ、上の書き方の場合はVue.jsによって操作可能なクラス`v-bind:class`は統合されません。
もし、静的・動的なクラスのどちらも取得して反映させたい時は、ちゃんと動的なクラスも含めるように書くといい感じになります。

```vue:Child.vue
<template functional>
  <p 
    class="leading-loose" 
    :class="[data.class, data.staticClass]" 
  >
    ほげほげ
  </p>
</template>
```
**自分はこのあたりでちょっとハマってました。**

## 感想
### Atomic Designええやん
コードの量は増える感じはあるけど、むっちゃ使いまわしが効くから幸せに。(モノによってはプロジェクトをまたいで再利用できるとも思った)
ただ、色周りの定義ってどう分けたらいいのか困ってた。どうやるのがきれいなんだろうね。

### 関数型コンポーネントを適用させる範囲ひろげたい
知見が足らなかったためにAtomsだけを関数型コンポーネントに置き換えた形になったけど、もうちょっと動作に影響のないレベルで置き換えることはできたよなぁと。
せめてMoleculesの一部までは.....

### パフォーマンスなんもわからん
これ。
どこかで試してみて、パフォーマンスまわりのデータ比較してみるのも面白そうだなぁ。

### 関数型のイベント周りなんもわからん
この記事書くときに試してみて、すずめの涙ほどですが知見が得られたので下の方のおまけにメモしておきますね。

## さいごに

**Vue.jsなんもわからん**
（書いてることが正しいか確認するためにNuxt.jsを使わず、数ヶ月ぶりにVue.jsベースのプロジェクト作ったら、プロジェクトのテンプレートがwebpack使ってなくて探り探りでさわることに。）

あと、Atomic DesignってDRY原則に合ってる認識だけどどうなんでしょう...おしえて...

## おまけ: 関数型コンポーネントへの置換
自分の備忘録も兼ねて、対応を残しておきます。
新たに知ったものとかがあれば足したり、間違いがわかれば修正加えます。

### class

```vue:Child.vue
<template functional>
  <p :class="[data.class, data.staticClass]">
    ほげほげ
  </p>
</template>
```
親のコンポーネントからクラス名をもらう時は、ちゃんと読み込みを指示する必要があります。
静的・動的なクラスどちらもバインドさせたい場合は`[data.class, data.staticClass]`でclass名を取る感じにすれば困らないかと。

### props

`props.`を足す。以上！

```vue:Child.vue
<template functional>
  <div>
    <h3>{{props.title}}</h3>
    <p>{{props.text}}</p>
  </div>
</template>
<script>
export default {
  props: {
    title: { type: String },
    text:  { type: String },
  },
}
</script>
```



### slot
これは変更せずそのままで

```vue:Child.vue
<template functional>
  <p>
    <slot />
  </p>
</template>
```

### イベントリスナー

#### 特定のイベントを受け渡しする場合
通常時はこんな感じで書くやつ

```vue:Child.vue
<template>
  <button @click="$emit('click', $event)">おしてちょ</button>
</template>
```
```vue:Parent.vue
<template>
  <div>
    <component-button @click="callMethod" />
  </div>
</template>
<script>
import ComponentButton from '@/components/Button.vue'
export default {
  components: {
    ComponentButton,
  },
  methods: {
    callMethod() { /* 処理 */ },
  },
}
</script>
```

関数型コンポーネントではこんな感じで。

```vue:Child.vue
<template functional>
  <button @click="listeners.click">おしてちょ</button>
</template>
```
`$emit('click', $event)` → `listeners.click`

#### 雑にイベントのやりとりをさせる場合

```vue:Child.vue
<template functional>
  <button v-on="listeners">おしてちょ</button>
</template>
```