こんにちは。フロントエンドフレームワーク使ってますか?react とか vue とかいうやつのことです。既にフロントエンド開発ではほぼ必須の技術になっています。
ではなぜフロントエンドフレームワークがこんなにも使われているのでしょうか?私はその理由があまり分かっておらず、なんとなく流行っているから使っていました。
この記事ではまず、フロントエンドフレームワークなし(jquery)で tic-tac-toe アプリを作成します。その後 vue を使って同じアプリを作成します。両者を比較して、何が楽になっているのかを説明します。これを読んだあなたが、フロントエンドフレームワークとは何かを一言で説明できるようになることを目標にします。
Tic-Tac-Toe with no framework
フロントエンドでよく起きる問題について理解するために、 tic-tac-toe というゲームを作っていきます。
tic-tac-toe は有名なゲームなのでぜひ一度は友達とやってみてください。友達のいない方は AI と遊ぶことができます。3x3 の盤面に交互に o と x を書いていき、縦横斜めのどれか一列に同じマークを揃えた方の勝利です。日本ではマルバツゲームや3目並べとも呼ばれています。
完成図はこんな感じです。盤面とゲームのリセットボタンもつけましょう。勝利条件が揃った時の演出があった方が楽しいですが、今回は割愛します。

最初に dom で全体の構造を決め、css で見た目を整えます。これだけでもう見た目は完璧ですね。今回は本筋ではないので解説しませんが、この辺の作業が一番楽しいです。
<div class="app">
<h1>Tic-Tac-Toe</h1>
<button>Reset</button>
<div class="board">
<div class="cell">o</div>
<div class="cell">o</div>
<div class="cell">o</div>
<div class="cell">x</div>
<div class="cell">x</div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
</div>
.board {
display: grid;
grid-template-rows: repeat(3, 1fr);
grid-template-columns: repeat(3, 1fr);
width: fit-content;
}
.cell {
width: 50px;
height: 50px;
border: 1px solid black;
align-content: center;
text-align: center;
}
javascript を使ってゲームのロジックを作っていきます。盤面の状態を二次元配列にしてみます。現在のプレイヤーマークを表す変数も用意します。
const data = {
board: [
["o", "o", "o"],
["x", "x", ""],
["", "", ""],
],
player: "o",
};
こうすると、少し問題が発生します。data と dom の 2 箇所に同じような内容を書いてしまっていますね。DRY 原則という言葉もありますから、どちらか一方に集約させたい所です。そこで、data の方にゲームの情報を集約させて、data の状態を dom の状態に反映させる関数 updateDOM を作ることにします。
function updateDOM() {
const app = document.querySelector(".app");
const board = document.createElement("div");
board.setAttribute("class", "board");
const h1 = document.createElement("h1");
h1.textContent = "Tic-Tac-Toe";
const buttonReset = document.createElement("button");
buttonReset.textContent = "Reset";
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
const cell = document.createElement("div");
cell.setAttribute("class", "cell");
cell.textContent = data.board[i][j];
board.append(cell);
}
}
app.innerHTML = "";
app.append(h1, buttonReset, board);
}
なんというめんどくささでしょうか!基本的に javascript で dom を操作するのはめんどくさいということを覚えておいてください。そのため dom 操作を簡単にするためのライブラリjqueryが開発され大流行しました。jquery で書き直してみます。
function updateDOM() {
const board = $("<div>").attr({ class: "board" });
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
board.append($("<div>").attr({ class: "cell" }).text(data.board[i][j]));
}
}
$(".app")
.empty()
.append($("<h1>").text("Tic-Tac-Toe"), $("<button>").text("Reset"), board);
}
どうでしょうか?大分すっきりしましたね。document.createElement("div")が$("<div>")になるのは革命です。jquery は dom 操作の省略記法を提供してくれるライブラリと考えていいと思います。
script タグの最後で updateDOM を呼び出すと data の状態が画面に反映されます。
updateDOM();
いよいよ盤面をクリックした時の関数 mark(i,j)を作っていきます。mark(i,j)は board[i][j]が指すセルをクリックした時の処理であり、プレイヤーがマークを記入することを表します。既に記入済みの場合は記入できないようにしましょう。記入できたらプレイヤーを交代します。
function mark(i, j) {
if (data.board[i][j]) return;
data.board[i][j] = data.player;
data.player = data.player === "o" ? "x" : "o";
}
updateDOM を変更し、div.cell のクリック時に mark が呼び出されるようにします。
board.append(
$("<div>")
.attr({ class: "cell" })
.text(data.board[i][j])
.on("click", () => mark(i, j)) // Add
);
ではクリックしてください。...何も画面に変化がありません。どこか間違ったでしょうか?
実は mark の最後で updateDOM を呼び出す必要があります。 data に変更を加えた時は必ず updateDOM を呼び出さなければならないのです。
function mark(i, j) {
if (data.board[i][j]) return;
data.board[i][j] = data.player;
data.player = data.player === "o" ? "x" : "o";
updateDOM(); // DON'T FORGET!
}
リセットボタンの処理も同様に追加してみましょうか。
function reset() {
data.board = [
["", "", ""],
["", "", ""],
["", "", ""],
];
data.player = "o";
updateDOM(); // DON'T FORGET!
}
$("<button>").text("Reset").on("click", reset);
data を変更する処理の最後で忘れずに updateDOM を実行しなければならないことが分かりました。忘れるなと言われても私たちは人間ですから確実に忘れてバグを作り出してしまいます。この問題を解決するのがフロントエンドフレームワークです。彼らの主な仕事はdata の変更を監視して自動的に dom の更新 をすることです。
Tic-Tac-Toe with Vue.js
満を持した Vue.js を使ってさっきと同様のプログラムを書いてみます。dom のテンプレート部分です。
<div class="app">
<h1>Tic-Tac-Toe</h1>
<button @click="reset">Reset</button>
<div class="board">
<template v-for="(row, i) in board">
<div v-for="(m, j) in row" class="cell" @click="mark(i, j)">{{m}}</div>
</template>
</div>
</div>
そしてロジックの部分です。
const { createApp } = Vue;
createApp({
data() {
return {
board: [
["", "", ""],
["", "", ""],
["", "", ""],
],
player: "o",
};
},
methods: {
mark(i, j) {
if (this.board[i][j]) return;
this.board[i][j] = this.player;
this.player = this.player === "o" ? "x" : "o";
},
reset() {
this.board = [
["", "", ""],
["", "", ""],
["", "", ""],
];
this.player = "o";
},
},
}).mount(".app");
細かい所まで読む必要はありません。mark と reset の最後で dom の更新を行なっていないということが伝われば十分です。vue は dom のテンプレート部分を参照し、data の変更を監視して 実際の dom を更新してくれます。
また、dom はテンプレートで書いた方が dom の構造が分かりやすくありませんか?dom の形を宣言的に書く宣言的 UIになったためです。jquery で実装した方は、手続き的なコードで順々に dom を追加してくため命令的 UI と呼ばれます。フロントエンドフレームワークを使うと自然と宣言的 UI にすることができます。
まとめ
フロントエンドフレームワークを使うと、dom の管理を自動化することができ、宣言的な UI を実現できます。私が長文で言いたかったことです。意見などはコメント欄でお待ちしています!
補足: フレームワークとライブラリ
フレームワークとは何かをもう少し真剣に考えようと思います。一般的にフレームワークと聞くと、5w1h、swot、pdca などを思い浮かべます。これらは思考のフレームワークと呼ばれます。
たった今人が目の前で倒れて、救急車を呼ばなければならないとします。その瞬間、私たちは色々なことを考えます。思考は自由過ぎることに欠陥があります。私たちはすぐ思考を発散してしまうのです。人が倒れている、助からなかったらどうしよう、誰か他の人はいないのか...色々なことを考え過ぎた結果、結局何を伝えるべきなのか分からなくなってしまいます。「今人が倒れていて死にそうです。助けてください。」
フレームワークは文字通り枠(ワーク w)であり、思考のフレームワークは思考の発散を抑える枠の役割を果たします。5w1h を知っていれば救急隊員に伝えるべきこともいつ、どこで、誰が、何を、なぜ、どのようにを考えるだけでいいのです。「たった今、⚪︎⚪︎ 公園で、知らない人が、倒れました、おそらく出血が原因で、ピクリとも動きません」
フレームワークソフトウェアも同じで、プログラミングのスタイルを制限する枠の役割を果たします。プログラミングは自由過ぎるので、ある程度組み方の方針を統一しておかないと、迷走してしまいやすいのです。また、フレームワークが統一されていると、他の人が読みやすいコードになるメリットもあります。
フロントエンド(ブラウザで動作するプログラムを作る際に使用する)フレームワーク(組み方を制限する外部ソフトウェア)ということです。そのため、フレームワークはライブラリとは異なり「こういう処理をしたい場合はここにこう書いてくれ」という制約が付随してきます。ライブラリはプログラミングを手助けするイメージであり、フレームワークはプログラミングを制限するイメージです。
今回触れた jquery は dom の操作を短く書けるようになるだけであり、こちらのスタイルを制限してくる訳ではないので、ライブラリの性質が強いです。一方 vue は data に書け、methods に書けとこちらのスタイルを制限してくるのでフレームワークでしょう。言い換えると、ライブラリは低依存、フレームワークは高依存になります。
Vue.js: The Progressive JavaScript Framework
react は自身のことをライブラリであると言っていますが、事実上のフレームワークだと思います。
React: The library for web and native user interfaces