数学ガールの秘密ノート／ビットとバイナリー 第2章を実装してみた（Vue.js版）

# これまでのあらすじ

『数学ガールの秘密ノート／ビットとバイナリー』第2章の「変幻ピクセル」をReactで実装する。
[数学ガールの秘密ノート／ビットとバイナリー 第2章を実装してみた（React版）](https://qiita.com/junjis0203/items/c5b953688e7a2c41ff1a)

第4章「フリップ・トリップ」を実装する。あまりにも簡単に書けてしまったのでReact版に加えVue.js版も作る。
[数学ガールの秘密ノート／ビットとバイナリー 第4章を実装してみた×2](https://qiita.com/junjis0203/items/5132f8af4e5f291ee3d5)

# 今回のお話

上記二つの記事では「webpack使おうかと思ったけど（すでに作ったものを直すのめんどくさくて）やめた」「webpack使おうと思ったけどモジュール化することもない規模だった」ということでwebpackを使いませんでした。webpack使いたいなー（知見を得たいなー）とは思っていたので最終章（？）として、

変幻ピクセル（これは少し規模も大きいしモジュール化する意味がある）をVue.jsで実装する。ついでにVue.jsに関するいろんな知見も学ぶ。

ということを行いました。
コードはここに置いています。
https://github.com/junjis0203/math_girls-secret_notebook_bits_and_binary-vuejs

# webpackとVue.jsのための設定

「webpackを使わないのは先に作ってからwebpack（モジュール）化しようとするからだ」ということで、今回は形（webpack使う場合のテンプレ）から入ることにしました。

というわけで「webpack vuejs」で検索して引っかかったこちらの記事で学習。githubに置いてあるpackage.jsonとwebpack.config.jsもほぼこのサイトのコードを
コピペ
参考にさせていただきました。
https://log.pocka.io/posts/vue-webpack-tutorial/

「Vueってコンポーネントどう作るんだろうなー」ということについても単一ファイルコンポーネントの説明が非常にわかりやすかったです。ありがとうございます。
一つだけ補足しておくと最近のVueLoaderPluginはnewしないとオブジェクトが作れないようになったようです。

## ナウい（？）JavaScriptの書き方

上記の記事および以下で紹介するいくつかの記事読んでて次のように書けることを知りました。記述量が減るのでいいなと思いました。

```js
  components: { Pixel },
  // components: { Pixel: Pixel }と書いたのと同じ

  methods: {
    handleClick(e) {
      this.$emit('click', e);
    }
  }
  // handleClick: function(e) {}と書いたのと同じ
```

ところで最近は行末の「;」は付けないのがナウいのですかね。オライリーの本で「付けないのはバッドプラクティス」って書かれてた気がしますが。

# PixelおよびFig

それでは本題の変幻ピクセルの実装（解説）に入ります。

React版では「初めFigure（画像全体）とPixelを分けたがイベント伝搬が面倒なので結局Figure内にまとめた」と書きましたが、今回は後述するテストもしっかりやろうとPixel（画像中の一つの点）をちゃんとコンポーネント化しました。
当初はPixel自体にクリックに反応する／しないの制御、初期値として渡された値とは別に現在の表示状態を持たせるということをしていたのですが、最終的に親コンポーネントから渡されたpropsをそのまま表示するだけの「状態を持たない」コンポーネントにしました。
https://github.com/junjis0203/math_girls-secret_notebook_bits_and_binary-vuejs/blob/master/src/components/Pixel.vue

次に画像全体を表すFigコンポーネントです。FigureじゃなくてFigなのはFigureだと素のhtmlのfigureタグと被る問題が発生したためです。
https://github.com/junjis0203/math_girls-secret_notebook_bits_and_binary-vuejs/blob/master/src/components/Fig.vue

React版では書籍に準じて「画像の一行」は整数にしていたのですが、Vue版では「0/1を格納した配列」にしています。つまり画像は「0/1を格納した配列の配列」になりメモリ効率等は落ちますがまあお勉強プログラムなので汎用性（整数のビット数以上の画像対応）も考えるとこっちの方がいいかなと思います[^1]。
初めは「画像一行」に対応するPixelLineコンポーネントを作りましたが、「君ぶっちゃけいなくていいよね」ということで無駄な中間管理コンポーネントは首にしました(笑)

[^1]: Twitterで「配列処理にpush使っていいのは小学生まで」みたいなのを見かけたということもあり（これも書籍に準じたためです）、今回のフィルター関数はmap使って実装しました(笑)

```vuejs:Fig.vue抜粋
<template>
  <div class="figure">
    <div
      v-for="(line, y) in lines"
      v-bind:key="y"
      class="pixel-line"
    >
      <pixel
        v-for="(value, x) in line"
        v-bind:key="x"
        v-bind:x="x"
        v-bind:y="y"
        v-bind:value="value"
        v-on:click="handleClick"
      ></pixel>
    </div>
  </div>
</template>
```

## vue-test-utilsとvue-jest

さてと前振りしたコンポーネントのテストについて。[vue-test-utils](https://vue-test-utils.vuejs.org/ja/guides/)のページを参考にPixelのテストを書いてみました。
当初は「このコンポーネントはクリックに反応する／しないの切り替えなどややこしいから仕様通りの動作をするようにちゃんとテストしてから先に進む必要がある」というのがテストを導入しようと考えた理由でしたが、今思うと「ややこしい仕様」をどうにかするべきでしたね(笑)。ともかくテストの仕方がわかったのでよかったです。
https://github.com/junjis0203/math_girls-secret_notebook_bits_and_binary-vuejs/blob/master/src/components/__test__/Pixel.test.js

テストランナーはJestを利用。走らせると以下のissueにあるように「babel-coreがない」というエラーになりました。READMEにはjest < 24.0.0の場合は`babel-core@bridge`が必要と書かれているのですがjest 24.9.0でも`babel-core@bridge`を入れる必要がありました。
https://github.com/vuejs/vue-jest/issues/160

なおPixel以外のテストは書いていません。TDDなにそれおいしいの？

# Vuexの利用

さてとPixelをクリックされたときに黒白を反転し、それに応じてフィルタの出力も変えるという一番おもしろいところに入ります。
React版ではオレオレStoreを作りましたが今回はちゃんと[Vuex](https://vuex.vuejs.org/ja/)使おうと思いました。またReact版ではFigureコンポーネントがStoreとやりとりしていましたがそのために「ごちゃごちゃしたクラス」になっていました。これも解決しました。

## StoreとInputFig

表示「だけ」を行うFigにwrapする形で、Storeから画像情報を取り出す（computedでプロパティを定義する）InputFigコンポーネントを定義します。またFigがクリックされたことによるピクセルの反転処理もこのコンポーネントで行います。
https://github.com/junjis0203/math_girls-secret_notebook_bits_and_binary-vuejs/blob/master/src/components/InputFig.vue

```vuejs:InputFig.vue
<script>
export default {
  props: {
    tag: String
  },
  components: { Fig },
  computed: mapState({
    lines(state) {
      return state.pool[this.tag];
    }
  }),
  methods: {
    handleClick(e) {
      const payload = {
        tag: this.tag,
        x: e.x,
        y: e.y
      }
      this.$store.commit('toggle', payload);
    }
  }
}
</script>

<template>
  <fig v-bind:lines="lines" v-on:click="handleClick" />
</template>
```

pool、tagとあるのは、最終形（ビットとバイナリー第2章の最後という意味）の縁取りフィルタではフィルタをつなげて縁取りを実装しているので、途中計算を保持できるようにと導入したものです。React版と同様ですがReact版ここら辺説明していませんね(笑)

## FilterFig

次に「フィルタ処理結果」の画像を表示するFilterFigです。余談ですが初めはFilteredFigだったのですが2入力フィルタがFiltered2Figだと意味がわからないのでFilterFigに改名しました。
https://github.com/junjis0203/math_girls-secret_notebook_bits_and_binary-vuejs/blob/master/src/components/FilterFig.vue

こちらはlinesの定義が少し複雑です。特に「computedのメソッド中でStoreのstateを更新」してる点についてはすっごいevilな気がするのですがここ以外に場所がないのでこのような実装になっています。

```js:FilterFig.vue抜粋
export default {
  props: {
    in_tag: String,
    func: Function,
    out_tag: String
  },
  computed: {
    name() {
      return this.func.name.toUpperCase()
    },
    ...mapState({
      lines(state) {
        const input = state.pool[this.in_tag];
        // 前の出力がまだ計算されてなければとりあえず空（一周目のみ）
        if (!input) {
          return [[]];
        }
        const output = this.func(input);
        // 計算結果をStoreに登録
        if (this.out_tag) {
          const payload = {
            tag: this.out_tag,
            lines: output
          };
          this.$store.commit('register', payload);
        }
        return output;
      }
    })
  },
  components: { FilterBox, Fig },
}
```

templateはこのようになっています。

```vuejs:FilterFig.vue抜粋
<template>
  <span>
    <slot />
    <filter-box v-bind:name="name" />
    <fig v-bind:lines="lines" />
  </span>
</template>
```

slot（input-figの子要素）はフィルタ対象の画像（フィルタされた画像をさらにフィルタにかけるのももちろんあり）を想定しています。

```vuejs
<filter-fig in_tag="OUTPUT1" v-bind:func="right">
  <filter-fig in_tag="INPUT" v-bind:func="right" out_tag="OUTPUT1">
    <input-fig tag="INPUT" />
  </filter-fig>
</filter-fig>
```

これは「入力→1ピクセル右シフト→1ピクセル右シフト」というフィルタ構成なのですが、上記のように見た目上そうなっていません。これの解決策についてはComposerのところで説明します。

# Filter2FigとVerticalLayout

2入力フィルタですが以下のように縦2列に並べたいです。

![and-filter.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/141091/941844c0-07fb-131c-9b75-4c245d4ded5b.jpeg)

これを実現するためにReact版では以下のようなタグ構造が出力されるようにしてました。VerticalLayoutの子要素を`div.vertical-layout-child`でwrapしたものを出力するようにしていました。

```html
<div class="vertical-layout">
  <div class="vertical-layout-child">
    縦に並べたい要素1
  </div>
  <div class="vertical-layout-child">
    縦に並べたい要素2
  </div>
</div>
```

Vueではrenderを使うことはできるもののできればtemplateでとのことだったので、一度以下のように要素1、要素2を区別して渡すように実装しました。

```vuejs
<filter2-fig in1_tag="OUTPUT1_1" in2_tag="OUTPUT1_2" v-bind:func="and" out_tag="OUTPUT2_1">
  <template v-slot:component1>
    <filter-fig in_tag="INPUT" v-bind:func="right" out_tag="OUTPUT1_1">
      <input-fig tag="INPUT" />
    </filter-fig>
  </template>
  <template v-slot:component2>
    <filter-fig in_tag="INPUT" v-bind:func="left" out_tag="OUTPUT1_2">
      <input-fig tag="INPUT" />
    </filter-fig>
  </template>
</filter2-fig>
```

ただしこれだと書くのも面倒だし、「縦並び」に対する汎用性もないということで、renderを使うようにしました。

```vuejs:VerticalLayout.vue
<script>
export default {
  render(createElement) {
    const wrappedSlots = this.$slots.default.map(s => createElement('div', {class: 'vertical-layout-child'}, [s]));
    return createElement('div', {class: 'vertical-layout'}, wrappedSlots);
  }
}
</script>
```

# ComposerとApp

ここまでで最終形を実装するのに必要なコンポーネントは完成しました。
しかし先ほどもFilterFigのところで述べたように適用されるフィルタを順に書くのではなく、構文木を直接書くように全体を記述するのはいまいちです。というわけで今回もComposerを作って逐次的に書けるようにしました。

```js:index.js抜粋
const composer = new Composer();

const in1 = composer.addInput(input);

const out1_1 = composer.addFilter(in1, right);
const out1_2 = composer.addFilter(in1, left);
const out1_3 = composer.addFilter(in1, up);
const out1_4 = composer.addFilter(in1, down);

const out2_1 = composer.addFilter2(out1_1, out1_2, and);
const out2_2 = composer.addFilter2(out1_3, out1_4, and);
```

この際に悩みどころだったのが、Reactでは全体を構成するパーツを個々にインスタンス化できたもののVueではできない（調べた限りではそれっぽい方法がない）ということでした。これを解決するために以下のようにしました。

1. addFilterなどではインスタンス化されたコンポーネントを返すのではなくインスタンスを作るために必要な情報を格納したオブジェクトを返す。
2. ComposerにmakeAppメソッドを定義。別途Appコンポーネントを定義し、renderを使って1.で作る情報から全体をレンダリングする。

処理が重複して気が進まなかったのですが、実際書いてみると簡単なコンパイラを書くようなもので中間表現への変換、中間表現を愚直にアセンブラに変換みたいな感じになりました。
https://github.com/junjis0203/math_girls-secret_notebook_bits_and_binary-vuejs/blob/master/src/composer.js
https://github.com/junjis0203/math_girls-secret_notebook_bits_and_binary-vuejs/blob/master/src/components/App.vue

# おわりに

以上今回は前に作成したReact版変幻ピクセルをVueに移植、というかだいぶきれいな形に整理が行えました。その過程でReactとVueの流儀の違いなどなどいろいろと勉強になりました。