Webフロントエンド技術は急速に変化しています。かつてのjQueryから、いまはVue.jsやReactといったシングルページアプリケーション(SPA)が主流の時代。
これらのベースはいずれも、HTMLやJavaScript。でもその見た目は、同じ言語とは思えないほどの変化。一体なにが違うのでしょう?
そのカギが、リアクティブプログラミングです。
ユーザーインターフェース実装の難しさ
Webフロントエンドが担うのは、ユーザーインターフェース。画面の表示を行ったり、ユーザーの入力を受け付けたり。
ユーザーインターフェースには、開発に特有の難しさがあります。
プレゼンテーションとロジック
ユーザーインターフェースには、大きく分けて2つの要素があります。
- プレゼンテーション部:画面のレイアウトや配色といった表示部分
- ロジック部分:ユーザー操作に応じて各種処理を行う部分
この2つ、修正される理由が異なるので、できればきれいに分離したい。
Webフロントエンドでは、この2つが別々の技術で実現されています。
- プレゼンテーション部 HTML,CSS
- ロジック部 JavaScript
実装方法も違うんだから普通に分離できるのでは?と思いきや、そう簡単にいきません。この2つ、お互いに深く関連しあっているのです。
- プレゼンテーションからロジック キーボードやマウス等によるユーザーの入力
- ロジックからプレゼンテーション 処理結果の表示
この双方向の依存性が錯綜するため、きれいな分離が難しくなります。
双方向依存のサンプルコード
実際にサンプルコードを見てみましょう。
簡単なTODO管理アプリを作ってみます。タスク入力して追加ボタンでリストに追加、削除ボタンでリストから削除。飾り気も何もないシンプルアプリです。
jQueryを使って実装します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODOアプリ</title>
</head>
<body>
<div id="app">
<div>TODOアプリ(jQuery)</div>
<input type="text" id="todoInput" placeholder="新しいタスクを入力" />
<button id="addButton">追加</button>
<ul id="todoList"></ul>
</div>
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script>
// TODOリスト
let todos = [];
/**
* TODO追加
*/
function addTodo() {
// todoInputの入力内容を取得
const todoText = $('#todoInput').val();
// TODOリストに追加
todos.push(todoText);
// 入力リセット
$('#todoInput').val('');
// 描画
renderTodos();
}
/**
* TODO削除
*/
function removeTodo(index) {
// TODOリストから指定インデックスのTODOを削除
todos.splice(index, 1);
// 描画
renderTodos();
}
/**
* 描画
*/
function renderTodos() {
// todoListエレメント取得
const todoList = $('#todoList');
// todoList内にTODOを描画
todoList.empty();
todos.forEach((todo, index) => {
todoList.append(`
<li class="todo-item">
${todo} <button onclick="removeTodo(${index})">削除</button>
</li>
`);
});
}
// 追加ボタンハンドラを設定
$(document).ready(function(){
$('#addButton').on('click', addTodo);
});
</script>
</body>
</html>
問題となるのは、この部分。
/**
* 描画
*/
function renderTodos() {
// todoListエレメント取得
const todoList = $('#todoList');
// todoList内にTODOを描画
todoList.empty();
todos.forEach((todo, index) => {
todoList.append(`
<li class="todo-item">
${todo} <button onclick="removeTodo(${index})">削除</button>
</li>
`);
});
}
JavaScriptの中でHTMLを出力しています。プレゼンテーションとロジックが混じりあってしまうのです。
リアクティブな解決方法
プレゼンテーションとロジックを切り離す。そのアイデアがリアクティブプログラミングです。
データバインディングで独立化
まずプレゼンテーションとロジックの依存を断ちます、そのための仲介者となるのがデータです。
両者の間にデータを介在させるという考え方が、データバインディング。
データバインディングとは、画面の入力や出力の項目、処理で使うデータ、この両者を紐づけて同期させる仕掛けです。
- プレゼンテーションでの入力内容を、自動的にデータに反映
- ロジックで出力したデータを、自動的にプレゼンテーションに反映
これにより、プレゼンテーションとロジックのどちらも、データに依存する形にできます。直接の関連性が外れるわけですね。
リアクティブにデータ同期
データに依存する、といっても、プログラムは何かしらのきっかけがないと動きません。
データ変更のタイミングで処理を動かす。それがリアクティブプログラミングです。
- プレゼンテーション入力でデータが変更されたら、自動的にロジックが動く
- ロジックでデータが変更されたら、自動的にプレゼンテーションが動く
この方法には、処理を発動する手続きを、明示的に書く必要がないというメリットがあります。プログラムで書くのは、自動的に同期させるデータ定義だけ。そうすると、データが変更されれば自動的にイベントが発生するようになります
すべてがデータに同期して動くようになることで、処理をシンプルにすることができます。
リアクティブ特性を持つVue.jsやReact
近年のトレンドであるSPA(シングルページアプリケーション)で人気を二分する、Vue.jsとReact。
Vue.jsとReact、どちらもリアクティブプログラミングの考え方を持つ技術です。ちなみにReactの名前は、リアクティブから来ています。
次の章から、Vue.jsとReactのサンプルコードを見ていきます。
以降のコードは、CDNを使っていたり、HTMLとJavaScriptをまとめて書いたりしています。あくまで手軽に動かせるお試し題材として書いたもので、実際には褒められたやり方ではないです。実際にはちゃんと環境構築して開発しましょう。
TODO管理アプリ(Vue.js版)
Vue.jsでTODO管理アプリを作ります。動きは、さきほど出てきたjQuery版と全く一緒です。
コード全体
jQueryと比べると、Vue.jsは非常にすっきりとした印象。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODOアプリ</title>
<script src="https://unpkg.com/vue@3.2.37/dist/vue.global.prod.js"></script>
</head>
<body>
<div id="app">
<div>TODOアプリ(Vue.js)</div>
<input type="text" placeholder="新しいタスクを入力" v-model="todoInput" />
<button @click="addTodo">追加</button>
<ul>
<li v-for="(todo, index) in todos" :key="index">
{{ todo }}
<button @click="removeTodo(index)">削除</button>
</li>
</ul>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
// TODO入力
const todoInput = ref("");
// TODOリスト
const todos = ref([]);
/**
* TODO追加
*/
const addTodo = () => {
// TODOリストに追加
todos.value.push(todoInput.value);
// 入力リセット
todoInput.value = "";
}
/**
* TODO削除
*/
const removeTodo = (index) => {
// TODOリストから指定インデックスのTODOを削除
todos.value.splice(index, 1);
}
return {
todoInput,
todos,
addTodo,
removeTodo
};
}
}).mount('#app');
</script>
</body>
</html>
プレゼンテーション(HTML)は自動的に描画される
注目すべきは、jQueryのときに出てきた、renderTodosといった描画関数が、Vue.jsでは出てきません。プレゼンテーション(HTML)に、todoInput, todos といったデータを使った表示の定義があるだけです。
Vue.jsは、データ変更を検知したら自動的に再描画します。
<div id="app">
<div>TODOアプリ(Vue.js)</div>
<input type="text" placeholder="新しいタスクを入力" v-model="todoInput" />
<button @click="addTodo">追加</button>
<ul>
<li v-for="(todo, index) in todos" :key="index">
{{ todo }}
<button @click="removeTodo(index)">削除</button>
</li>
</ul>
</div>
ロジック(JavaScript)とHTMLを分離する
ロジック(JavaScript)では、todoInput や todos を使って入出力しているだけです。
Vue.jsでのJavaScript側には、HTMLを操作している処理がありません。きれいに分離されていることが分かりますね。
createApp({
setup() {
// TODO入力
const todoInput = ref("");
// TODOリスト
const todos = ref([]);
//...中略
return {
todoInput,
todos,
addTodo,
removeTodo
};
}
}).mount('#app');
TODO管理アプリ(React版)
こんどは、Reactで書いたTODO管理アプリです。これもまた動きはjQuery版と全く一緒。
コード全体
Reactは、Vue.jsとはだいぶ見た目が異なります。JavaScriptのコードが増えた印象ですね。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TODOアプリ</title>
<script src="https://unpkg.com/react@18/umd/react.production.min.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js" crossorigin></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
/**
* Appコンポーネント
*/
const App = () => {
const useState = React.useState;
// TODO入力
const [todoInput, setTodoInput] = useState("");
// TODOリスト
const [todos, setTodos] = useState([]);
/**
* TODO入力変更
*/
const handleChange = (event) => {
setTodoInput(event.target.value)
}
/**
* TODO追加
*/
const addTodo = () => {
// TODOリストに追加
const newTodos = todos.slice();
newTodos.push(todoInput);
setTodos(newTodos);
// 入力リセット
setTodoInput("");
}
/**
* TODO削除
*/
const removeTodo = (index) => {
// TODOリストから指定インデックスのTODOを削除
const newTodos = todos.slice()
newTodos.splice(index, 1);
setTodos(newTodos);
}
// JSX返却
return (
<div>
<div>TODOアプリ(React)</div>
<input type="text" placeholder="新しいタスクを入力" value={todoInput} onChange={handleChange}/>
<button onClick={addTodo}>追加</button>
<ul>
{todos.map((todo, index) => (
<li>
{todo}<button onClick={() => removeTodo(index)}>削除</button>
</li>
))}
</ul>
</div>
);
}
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
root.render(<App />);
</script>
</body>
</html>
HTML側にほぼ何も書かない
Reactが特徴的なのは、プレゼンテーション(HTML)側に描画要素がほぼないこと。<body>タグの中にあるのは、id=rootのdivタグだけです。Reactでは、描画内容はすべてJavaScript側に記載します。
<body>
<div id="root"></div>
<script type="text/babel">
...
</script>
</body>
JavaScriptでHTMLもどきを返却する
JavaScriptのロジック側を見ていきましょう。不思議なのは、retrunの部分。
const App = () => {
//..中略
// JSX返却
return (
<div>
<div>TODOアプリ(React)</div>
<input type="text" placeholder="新しいタスクを入力" value={todoInput} onChange={handleChange}/>
<button onClick={addTodo}>追加</button>
<ul>
{todos.map((todo, index) => (
<li>
{todo}<button onClick={() => removeTodo(index)}>削除</button>
</li>
))}
</ul>
</div>
);
}
returnで、HTMLのようなものを返却しています。(正式には、JSXと呼ばれる構文)。
Reactは、この返却されるHTML(のようなもの)をもとに、画面を描画します。
自動的に関数が呼び出されて描画される
jQueryのときに出てきた、renderTodosのような描画関数は、Reactではありません。
データが変更されると、Reactが変更を検知して自動的に関数を呼び出します。その結果で再描画が起こります。
Vue.jsとReactの違い
リアクティブプログラミングを採用するVue.jsとReact。でもその思想には、実は色々違いがあります。
レイヤー分割するVue.js、コンポーネント分割するReact
- ▼Vue.js プレゼンテーション(HTML)とロジック(JavaScript)を、きれいに分ける
- ★React プレゼンテーションとロジックをまとめた部品にする
Vue.jsは、HTMLとJavaScriptが、割ときれいに分かれます。jQueryなどに親しんできた人にはとっつきやすいでしょう。デザイン(HTML+CSS)とロジック(JavaSxcript)の開発担当が別、といった場合に、Vue.jsだと分担がしやすくなります。
一方Reactは、HTML+JavaScriptの関連部分をコンポーネントとしてまとめます。部品として独立させやすく、コンポーネントを疎結合とできるメリットがあります。再利用性も高まります。この特性から、Reactは大規模開発に適用しても破綻しにくい、と言われます。
双方向バインディングのVue.js、単方向バインディングのReact
- ▼Vue.js 画面入力をデータへ反映、データを画面出力へ反映、のどちらも自動
- ★React データを画面出力へ反映するのは自動、画面入力をデータへ反映するのは手動、
画面とデータを結びつけるのがバインディング。反映方向は、画面からデータ/データから画面、の両方があります。
Vue.jsは、双方向に自動でバインディングします。なので反映処理をいちいち書く必要がなくなります。
Reactは、データを画面出力へ反映する方向だけ自動バインディングします。両方自動でよさそうなのになぜ?と思うかもしれませんが、Reactはあえてそうしています。
問題となるのは、扱うデータが複雑化したとき。例えば画面に入力項目が100個あり、100個のイベントが自動的に起こるとすると、予期しないタイミングで処理が動いてしまったり。
なので、一概に双方向バインディングがよいとも言えないのです。このことからも、Vue.jsは小/中規模開発向き、Reactは大規模開発向き、と言われます。
フレームワークなVue.js、ライブラリなReact
- ▼Vue.js フレームワークに乗っかることで、簡単に開発できる
- ★React 画面描画のライブラリなので、一部だけ採用したり他と組み合わせたりできる
フレームワークとライブラリの違いは、制御フローの管理方法です。
- フレームワーク:フレームワーク側がプログラムの流れを定義、ユーザープログラムがそれに乗っかる
- ライブラリ:ユーザープログラム側がプログラムの流れを定義し、ライブラリを呼び出る
Vue.jsはフレームワークに分類されます。Vue.jsが全体の流れを統一化するので、明解なプログラムになります。
Reactはライブラリに分類されます。プログラムの流れをユーザー側が作らなければいけない分、大変にはなります。ただ、Reactと他のライブラリと組みあわせたり、一部だけReact使ったり、といった柔軟な使いかたが可能になります。
学習曲線が緩やかなVue.js、急激なReact
- ▼Vue.js 学習がしやすい。HTMLになじんでいる人にはとっつきやすい
- ★React 学習が難しい。身に付けると生産性が急激にあがる
Vue.js は特に初心者にとって学びやすいと言われます。Vue の構文が HTML によく似ていることもあり、すでに HTML に慣れている人には直観的に分かりやすいでしょう。
Reactは、高度なJavaScript構文を使うこともあり、学ぶのが難しいです。ただ、他のライブラリと組み合わせやすいことやコンポーネント化しやすいといった特徴により、Reactは理解が進むと開発効率が急激にあがると言われます。
EasyなVue.js、SimpleなReact
- ▼Vue.js Easy(イージー)
- ★React Simple(シンプル)
両者の比較を総括してこう言われます。「簡単なVue.js、シンプルなReact」。
Vueは、Webフロントエンドを簡単に素早く開発できます。Reactは、シンプルなぶん手間はかかりますが拡張性が高いです。
まとめ
SPAの代表格、Vue.jsとReact。その中心となる考え方が、リアクティブプログラミングです。
きちんと理解して使えば、プログラムの開発効率や保守性が劇的に向上します。
しかしながら、これまでのjQuery型開発を行ってきたかたには、リアクティブ型への発想の転換(パラダイムシフト)が必要になってきます。ぜひこれまでの固定概念から脱却して、リアクティブプログラミングを身に着けましょう。
コメント