Seiichi Yonezawa

Preactを使ってみました

Reactを使えるようになって嬉しいことといえば、Reactから派生したプロジェクトも利用できることです。 どちらもサーバーが絡んでいますが、Next.jsやGatsbyなどは一度使ってみたいものです。 本当はこのブログや編集画面もNext.jsで作る予定だったのですが、現在はRubyでその場しのぎをしています。

Reactは間違いなく書いていて面白いのですが、面白いからといって必ずしも簡単ではありません。 とはいえReactを書いていると以前よりもプログラミングがわかりやすくなりました。 Reactそのものは簡単ではないけれど、簡単に思えてしまう不思議な魅力があるような気がします。


今回取り上げるのはPreactです。 PreactはReactとから派生したというよりも、Reactに代わるものだと説明されています。 例えるならばjQueryとZepto.jsの関係と似ているでしょうか。

良い点

READMEにも書かれていますが、PreactはReactに比べて軽量です。 また、軽量であるということは動作も軽快な気がします。 とはいえベンチマークを使って調べてはいないので、体感上は極端に違いがあるわけでもありません。

はっきりするのは、Reactを書いているとラップトップのファンの音が気になっていました。 ラップトップ自体が5年近く経過しているので、単に経年劣化の影響なのかもしれません。 しかし、Preactを使っていると静かなままなのでCLI環境では負担がすくないようです。

こちらもうたい文句どおりですが、Reactと同じAPIが用意されているため習得も容易でした。 Preact CLIもCreate React Appと同様のコマンドが揃っているため、ビルドに対する不満はありませんでした。

もうひとつ、PreactはVue.jsのようにルーターなどのライブラリが付属しています。 歴史的な経緯もあると思いますが、ReactはReduxやReact Routerがサードパーティのライブラリです。 必ずしも良いこととは言い切れませんが、ライブラリの選択に迷うことはないかもしれません。

気になる点

Reactに比べるとエラーメッセージが貧弱です。 どこでエラーが起きたかトレースメッセージを追うのに苦労しました。 例えば以下のようなコードを見てみましょう:

import { Component } from "preact"
class App extends Component {
  foo = () => this.setState({ bar: 'baz' })
  render() {
    return (<div onClick={this.foo()} />)
  }
}

これは実際に起きたミスだったのですが、このコードを実行するとブラウザが止まります。 もちろん構文的に問題はないので上記のコードで無限ループが発生してしまうのです。 一方Reactに書き換えて同じコードを実行してみました:

Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

  54 | foo = () => {
> 55 |   this.setState({ 'bar': 'baz' })
  56 | }

  62 | return (
> 63 |     <div onClick={this.foo()}></div>
  64 | )

Reactは無限ループの検出に加えて、実際にエラーが発生した場所までバッチリ教えてくれました。 ちなみにコード内容が微妙に異なるのは別プロジェクトで実行したからです。 Preactも開発環境ではエラー周りがもっと充実してくれると扱いやすいなと思います。

Preact CLIで生成したプロジェクトではCRAで使える構文がいくつか使えませんでした。 とはいえ、これはWebpackのプラグインを追加していくことで対応可能だと思います。 どのように追加するかはまだ調べていませんが、わかり次第いつかブログに残したいものです。

PreactもHMRが使えますが、一度エラーが発生するとブラウザをリロードしなければなりません。 細かい点ではありますが、エラーは比較的頻繁に起こり得るものなのでその度にリロードするのは辛いです。 生産性という観点ではパフォーマンスも重要ですが、こういった諸々がPreactで今後サポートされていくと嬉しいです。

悪い点

ここからは主にPreact CLIの問題ですが、Preact CLIはlodashに依存関係があるため脆弱性があります。 もちろんPreactそのものに脆弱性が含まれているわけではありませんので、エンドユーザには影響がありません。 Preactに限りませんが、新規作成したプロジェクトがいきなり脆弱性を抱えているのは気持ちよいものではありません。

また、Preact CLIで生成したプロジェクトはタブの使用を強制されます。 このIssueではEditorconfigが出てきますが、スペースを指定しても変換されてしまいました。 原因はeslint-config-synacorというパッケージがESLintの設定を上書きしていたためでした。 スペースやタブの論争に興味はありませんが、特定のスタイルを強制することは悪いことだと言わざるを得ません。

Reversiについて

https://seiichiyonezawa.com/reversi/

Preactの題材として単純なリバーシを作ってみました。 コードは書籍に書かれていたものを参考にしているため残念ながら公開できません。 とはいえ、プログラミングの題材としてはよくあるものなので簡単に解説します。

巷ではディープラーニングが取りざたされていますが、次の手を予測するなどの操作は行なっていません。 大まかな流れは現時点で人間(プレイヤー)が先行なので、盤面をクリックしたあとにコンピュータに最善手を打たせるというものです。 ではその最善手はどのように計算しているかが今回のキモです。

重み付けの図
引用: オセロ(リバーシ)の作り方(アルゴリズム) ~石の位置による評価~より

それぞれのマスに対してあらかじめ評価値を決めておき、評価値の合計のもっとも高い場所に置くというものです。 なので、この場合他に石が置ける場所があったとしても、評価値がもっとも高い四隅を優先的に狙ってきます。 ボードゲームはリバーシに限りませんが、とにかく盤面の評価にループを多用するので書くのは大変でした。

今回リバーシの題材としてPreactを選択したのは成功でした。 ネットや書籍で紹介されているのは当然手続き型が多いのですが、おそらく写経しながらだとしても完成させるのは難しかったと思います。 なぜなら手続き型の言語だとUIとAIやシーン遷移などの処理がひとつの大きな流れなので、すべての処理を頭に把握しながら書くのは言うまでもなく大変だからです。

いつもはオブジェクト指向で石やらボードやらとそれぞれクラスを細かく用意していました。 そして個々のオブジェクトが他のオブジェクトと密結合しています。 それでいてUIとAI部分はひとつのクラスに結合しがちです。 UIとAIを別々にしたとしてもその分クラスが増えてしまいます。

一方で今回存在するクラスはPreactのコンポーネントがふたつのみです。 そのかわり配列やオブジェクトを多用します。 オブジェクトに比べて配列やオブジェクトはimmutableにすることでオブジェクトに比べてテストが書きやすくなります。

当然テストケースを書くのも大変ですが、盤面を一度テスト上で表現してしまうと案外すんなりとコードが書けるようになります。 そして、個々のテストが完成したらあとはコードの断片を組み立てていくだけです。 オブジェクト志向でも似たことは可能ですが、UIとAI部分をそれぞれ関心の分離に当てはめることができたので最後まで進められました。

まとめ

今までJavaScriptで簡単なゲームを書くのにわざわざ他のフレームワークを使う必要はないと思っていました。 しかし、簡単なゲームでも状態の遷移や、UIの管理など面倒ごとは少なからずあるものです。 その点で今回初めてPreactを導入してみましたが、コードをうまく分割することができました。 今後は他のJavaScriptのフレームワークも試していきたいと思っています。

投稿一覧