👾

疲れたあなたを癒す「ライフゲーム」コンポーネントライブラリを作った

2022/01/31に公開

👾はじめに

この Gif をご覧ください。

lifegame

これはライフゲームと呼ばれる簡単なゲームを描画したものです。
生き物が動いているのをみているようで、ぼんやり眺めてるだけでも心が落ち着く感じがありますね(?)

業務には全く活きないライフゲームですが、webページの遊び要素として取り入れてもらえたらなと思います。codesandboxにて触ってみてください。

https://github.com/kj455/react-life-game

🧑‍💻技術的な話

以下の2点について書きます。既にご存じの方はご容赦ください🙏 (ライフゲームの作成自体は簡単なので割愛します)

  • key を使ったコンポーネントの再生成
  • Vite + React + TypeScript でのコンポーネントライブラリ作成

🔑 key を使ったコンポーネントの再生成

結論から書くと、「何かに応じてコンポーネントをステートごと再生成[1]したいときはkeyプロパティを使うと良い」 です。

以下に例を示します。

function App() {
  const key = useKeyOnResize(); // ウィンドウの面積を返すカスタムフック
  return (
    <Component key={key} />
  );
}

具体的に説明します。
本ライブラリでは、ライフゲームの描画を

  1. (デフォルトだと)ライフゲームフィールドが画面いっぱいに広がるように、縦横の細胞数を計算する
  2. 各細胞の状態ステートを 1.で計算した形状の配列で管理する
  3. その配列の通りに DOM を配置する

という手順で行っています。
そのため、もし画面サイズが変更された場合には、新しい画面幅に応じた形状の二次元配列を再度生成する必要があります。そこで色々と調査していた時に以下の stackoverflow の回答に遭遇し、上記の学びを得ました。

https://stackoverflow.com/a/64726746/15553908

map などの処理で key を書かないと React に怒られるのでいつも思考停止で使っていましたが、 key をコンポーネントの再生成のために使う という発想が自分にとっては新しく、非常に良い学びでした。

📝 Vite + React + TypeScript でのコンポーネントライブラリ作成

本ライブラリでは Vite, React, TypeScript を採用しました。
この構成でコンポーネントライブラリを作る記事が少なかったので、参考になった記事を紹介するとともに、補足をしておきます。

この記事がとてもわかりやすく参考になりました🙏
https://zenn.dev/drop_table_user/articles/7b043bef6cec29

この記事は非常にわかりやすかったのですが、 TS 対応がオプショナルなものとして書かれており、私がパッケージ作成の初心者すぎて最終形のイメージがつきづらく、少し苦労しました。同じ技術構成でライブラリを作る初心者の方に向けてメモを残しておきます。

今回の技術構成(react + ts)なら、 vite.config.tspackage.json は次のようになるはずです。

vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import path from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [react()],
  build: {
    lib: {
      entry: path.resolve(__dirname, 'src/index.ts'),
      name: 'ReactLifeGame',
      fileName: (format) => `${好きな名前(ライブラリ名など)}.${format}.js`, // *1
    },
    rollupOptions: {
      external: ['react', 'react-dom'],
      output: {
        globals: {
          react: 'React',
        },
      },
    },
  },
});
package.json
{
  "name": "...",
  "version": "...",
  "files": [
    "dist", // ビルドされたもの
    "types" // 自動生成された型定義ファイル
  ],
  "types": "types/index.d.ts",
  "main": "./dist/${ vite.config.ts の *1 で指定したファイル名}.umd.js",
  "module": "./dist/${ *1 で指定したファイル名}.es.js",
  "exports": {
    ".": {
      "import": "./dist/${ *1 で指定したファイル名}.es.js",
      "require": "./dist/${ *1 で指定したファイル名}.umd.js"
    }
  },
  "scripts": {
    ...
    "prepare": "yarn build" // パッケージの publish に呼ばれる
  },
  "dependencies": {
	...
  },
  "devDependencies": {
    ...
  },
  "peerDependencies": {
    "react": "...",
    "react-dom": "..."
  },
}

最後に

作成したライフゲームのコンポーネントの紹介と、開発時の学びをまとめました。
少しでもライフゲームを楽しめたという方や、 key の使い方なるほどな〜となった方など、「いいね」をよろしくお願いします!

https://kj455.github.io/

脚注
  1. 本記事では再生成(ステートの初期化を伴う)と再レンダリング(ステートは維持される)を区別しています。 ↩︎

Discussion