技術書展10で「いまからはじめるSvelte3」という本を購入して読みました。
いまからはじめるSvelte3

この本の中では、Svelteを使ってライフゲームを実装していくのですが、似たようなやり方でリバーシもできそうだなと思ったので実装してみました。

できあがったもの

https://svelte-reversi.netlify.app/

リポジトリ

https://github.com/EringiV3/svelte-reversi

実装の雰囲気

ボード(マスの集合体)を状態と保持しておき、クリック操作によってボードの状態が更新され、最新のボードの状態がレンダリングされます。

<main>
    <div class="board">
        {#each board as row, i}
            {#each row as square, j}
                <div
                    class="square"
                    class:white={square.color === 'White'}
                    class:black={square.color === 'Black'}
                    on:click={(e) => handleClick(i, j)}>
                    {#if square.color}
                        {square.color === 'White' ? '○' : '●'}
                    {/if}
                </div>
            {/each}
        {/each}
    </div>
    <div>currentPlayer: {player.color}</div>
    <div>
        {#if errorMessage !== null}{errorMessage}{/if}
    </div>
    <div>
        {#if gameResult !== null}
            <div>{gameResult}</div>
        {/if}
    </div>
</main>


    let board: Board;
    let player: Player;
    let errorMessage: string | null = null;
    let { boardStore, playerStore, gameResult } = initializeGame();
    boardStore.subscribe((value) => {
        board = value.board;
    });
    playerStore.subscribe((value) => {
        player = value;
    });


    function handleClick(i: number, j: number) {
        const position = { row: i, col: j };
        if (canPutStone(board, player.color, position)) {
            errorMessage = null;
            putStone(player.color, position);
            togglePlayer();


            const existsEmptySquare = !!board.find(
                (row) => row.find((square) => !square.isFilled) !== undefined
            );
            if (!existsEmptySquare) {
                let whiteStoneCount = 0;
                board.forEach((row) => {
                    whiteStoneCount += row.filter(
                        (square) => square.color === "White"
                    ).length;
                });
                const blackStoneCount =
                    ROW_SIZE * COLUMN_SIZE - whiteStoneCount;
                gameResult =
                    whiteStoneCount > blackStoneCount
                        ? "Whiteの勝利です"
                        : "Blackの勝利です";
            }
        } else {
            errorMessage = "無効な一手です";
        }
    }


既に.svelteファイルのscriptタグ内でのTypeScriptのサポートがされており、lang="ts"と記述すれば.svelteファイル内でTypeScriptが使えます。
ただ、自分が解決できなかった問題として、「.tsファイルからのimport」があります。
Storeの実装を別のファイルに切り出したかったのですが、reversi.tsファイルからのexportをApp.svelteでimportしようとするとエラーになってしまいました。.jsファイルなら問題ないのですが...。
Svelteはググって出てくる情報がまだそんなに多くないので、調べて解決することができませんでした。(rollupの設定ファイルの記述とかまったくわからない)
なので、App.svelteにStore含めたすべての実装を詰め込んであります。
今回作った機能少なめのリバーシ程度のアプリケーションだったらなんとかなりますが、これ以上規模が大きくなると、Storeの実装を外部ファイルに切り出して隠蔽できないというのは破綻まっしぐらだと思うのでなんとかしたいです(.jsファイルにすれば問題なかったけど型を使いたかったからその選択はしませんでした)。

また、.svelteファイルのscriptタグの中の記述量が増えていくのは、再利用性の点でよくないのでDOMに依存するコードだけ(今回ならhandleClick関数)を残しておき、あとは外部のtsファイルに切り出すのが良さそうです。
Storeに対する操作の詳細の隠蔽 + 再利用というのはReact(Hooks) + Recoilでの考え方と同じですね。
Storeの実装でなんのライブラリを使うかという悩みが発生しないのはStoreを内蔵しているSvelteのいいところだと個人的には思います。

感想

Reactを多少書ける自分が触った感じ、書き方で迷うことはそんなになかったし、全体的に直感的な記述ができるUIライブラリだなという感想です。
いまやReact, Vueと並んで比較されることも増えてきたライブラリなので、キャッチアップしておく価値はあると思いました。
公式のチュートリアルも丁寧なので、もっと採用例が増えて日本語の情報も増えてくれるのを期待します。