Nuxt.js で Slack のようなリアクション機能を実装する

## Prologue
Slack のようなリアクション機能をNuxt.js(Vue.js) を使って実装してみました。
CSSの実装やカウント、リアクションをつけたユーザーの表示等に関しては割愛して、今回は Picker と絵文字の表示部分についてのみ説明します。
公式のドキュメントがかなりしっかり作られているので、そちらも確認してみてください。


参考: 

- Emoji Mart: https://github.com/missive/emoji-mart
- Emoji Mart Vue: https://github.com/jm-david/emoji-mart-vue

## 環境
- macOS: 10.15.4
- terminal: iTerm
- node.js: 10.16.0
- エディタ: VS Code
- 使用言語: TypeScript, Nuxt.js, CompositionAPI(v0.5.0)

- Project概要
    - パッケージマネージャ: `yarn`

***

## インストール

`emoji-mart-vue` をインストールします。

```
yarn add emoji-mart-vue
```

## セットアップ

挙動を見たかった為、 Nuxt のプロジェクトを新しく作成し、必要最低限の code を書いていきます。

``` TS
<template>
  <div class="section">
    <div>
      <Picker />
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from '@vue/composition-api'
// @ts-ignore
import { Picker } from 'emoji-mart-vue'

export default defineComponent({
  name: 'EmojiPicker',
  components: {
    Picker,
  },
})
</script>
```

最低限これだけでも emoji-picker は表示されました。


### プロパティをセット

`emoji-mart-vue` は様々なプロパティを用意してくれているので、必要なものを確認していきます。
`<Picker />` component にプロパティを渡していきます。

参考: https://github.com/jm-david/emoji-mart-vue

``` TS
<template>
  <div class="section">
    <div>
      <Picker
        title="pick emoji"
        emoji="grinning"
        set="twitter"
        color="pink"
      />
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from '@vue/composition-api'
// @ts-ignore
import { Emoji, Picker } from 'emoji-mart-vue'

export default defineComponent({
  name: 'EmojiPicker',
  components: {
    Picker
  },
})
</script>
```

内容: 

- title: フッターに表示されるテキスト
- emoji: フッターに表示される初期値の絵文字
- set: pickerに使われる絵文字のデザイン
- color: ヘッダーに使われるカテゴリ hover 時のカラー

個人的には Color を標準で簡単に切り替えられるのはすごい助かりました。CSSをいじればいいだけですが、バージョンアップ等を考えるといじらないに越したことはないので...


## 表示する内容(カテゴリ)を限定する

`emoji-mart-vue` はデフォルトでたくさんの絵文字を提供してくれています。
ただ、プロジェクトによっては不要だったり制限したりする可能性もあるので、カテゴリの制限の仕方を確認しました。
余談ですが、実はここがなかなかうまくいかず、一番てこずりました。ドキュメントをよく読まなかったのが敗因です。
 

`include` というプロパティが用意されています。
そこに `l18n` と同じキーをセットします。複数使用する場合には配列で書くだけです。

``` TS
<template>
  <div class="section">
    <div>
      <Picker
        title="pick emoji"
        emoji="grinning"
        set="twitter"
        color="pink"
        :include="cat"
      />
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from '@vue/composition-api'
// @ts-ignore
import { Emoji, Picker } from 'emoji-mart-vue'

export default defineComponent({
  name: 'EmojiPicker',
  components: {
    Picker
  },
  setup(){
    const cat = ['search', 'recent', 'people', 'objects']
    return { cat }
  }
})
</script>
```


## 選択した絵文字を任意の場所に表示する

- `Emoji` という component が用意されているので import します。
- `Picker` に `select` という event が用意されているため、そこから emoji object を取得します。
- Emoji component の `emoji` プロパティ に bind します。

``` TS
<template>
  <div class="section">
    <div v-for="(item, index) in state.selectedItem" :key="index" class="emoji-wrapper">
      <div>
        <Emoji :emoji="item" :size="20" class="emoji-image" />
      </div>
    </div>
    <div>
      <Picker
        title="pick emoji"
        emoji="grinning"
        set="twitter"
        color="pink"
        :include="cat"
        @select="selectEmoji"
      />
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, reactive } from '@vue/composition-api'
// @ts-ignore
import { Emoji, Picker } from 'emoji-mart-vue'

export default defineComponent({
  name: 'EmojiPicker',
  components: {
    Picker,
    Emoji
  },
  setup(props, context) {
    const state = reactive({
      selectedItem: ['grinning'],
    })

    const cat = ['search', 'recent', 'people', 'objects']

    function selectEmoji(item: any) {
      if (!state.selectedItem) {
        state.selectedItem = item.unified
      }
      state.selectedItem.push(item)
    }

    return {
      selectEmoji,
      state,
      cat,
    }
  }
})
</script>
```

これで表示されるはずです。


## Emoji component を使わないパターンを検討する

ここまでで特に問題なく実装ができました。ただ今回はこの内容をデータとして、なるべくシンプルな形で保存しておく必要がありました。
Emoji Component を使わず、 string だけでやりとりができないものかと方法を探していたら、以下の記事を見つけました。

参考: https://medium.com/@allegra9/add-emoji-picker-to-your-react-chat-app-30d8cbe8d9a6

`select` event で取得できる object の中にある `unified` に接頭辞 `0x` をつけて Unicode とし、それを `String.fromCodePoint` で絵文字として表示します。

- String.fromCodePoint  
参考: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/fromCodePoint

以下の code は 抜粋になります。
`state.emoji` を template に置くと表示されます。

``` JS
function selectEmoji(item: any) {
  let splitCode = item.unified.split('-');
  let codeArr = new Array()
    splitCode.forEach((c, index) => {
      splitCode[index] = `0x${c}`
    })
    state.emoji = String.fromCodePoint(...splitCode)
}
```

注意したいのは、 `emoji-mart-vue` は肌の色なども変えることができるため、取得した `unified` の中身は `-` 区切りの string になる場合があります。
その string 一つ一つの頭に `0x` をつけないと、望む形の絵文字は表示されません。
最終的にはこの形で渡せればOKです。

```JS
String.fromCodePoint('0x1f44d', '0x1f3fd')
```

`unified` の中に `-` がいくつ存在しうるのか、肌の色以外にもどのパターンでそうなるのか、は一つ一つ検証していないのですが、この形で大丈夫だと思います。
何かあればフィードバックして頂けると助かります。＾＾
 

## Epilogue

絵文字を表示する、という一見するとたくさんやることがありそうな内容ですが、今回見つけた `emoji-mart` は `Vue.js` 用に作られたもの(`emoji-mart-vue`) があり、助かりました。
ドキュメントもわかりやすく、進めるのが楽しかったです。

最後の `unified` の部分だけ、実装段階まで知らず、気付かずで焦りましたが... JSが素敵な関数用意してくれていてよかった＾＾　というJSの勉強にもなりました。

今回言及していませんが、この他にもSlack風にするためにカウントを増やしたり tooltip を実装したりしています。また、絵文字のサイズも任意で変えられたりするので、ぜひ実装する際には公式ドキュメントを見ながら進めてみてください。

何かおかしなところや、こういう方法もある、というものがありましたらお気軽にメンションしてください。