【jQueryからVue,Reactへ】リアクティブ型へパラダイムシフトしよう

reactive-programming

Webフロントエンド技術は急速に変化しています。かつてのjQueryから、いまはVue.jsReactといったシングルページアプリケーション(SPA)が主流の時代。

これらのベースはいずれも、HTMLやJavaScript。でもその見た目は、同じ言語とは思えないほどの変化。一体なにが違うのでしょう?

そのカギが、リアクティブプログラミングです。

目次

ユーザーインターフェース実装の難しさ

Webフロントエンドが担うのは、ユーザーインターフェース。画面の表示を行ったり、ユーザーの入力を受け付けたり。

ユーザーインターフェースには、開発に特有の難しさがあります。

プレゼンテーションとロジック

ユーザーインターフェースには、大きく分けて2つの要素があります。

  • プレゼンテーション部:画面のレイアウトや配色といった表示部分
  • ロジック部分:ユーザー操作に応じて各種処理を行う部分

この2つ、修正される理由が異なるので、できればきれいに分離したい

Webフロントエンドでは、この2つが別々の技術で実現されています。

ユーザーインターフェースのコンポーネント分割
  • プレゼンテーション部 HTML,CSS
  • ロジック部 JavaScript

実装方法も違うんだから普通に分離できるのでは?と思いきや、そう簡単にいきません。この2つ、お互いに深く関連しあっているのです。

双方向依存性
  • プレゼンテーションからロジック キーボードやマウス等によるユーザーの入力 
  • ロジックからプレゼンテーション 処理結果の表示

この双方向の依存性が錯綜するため、きれいな分離が難しくなります。

双方向依存のサンプルコード

実際にサンプルコードを見てみましょう。

簡単なTODO管理アプリを作ってみます。タスク入力して追加ボタンでリストに追加、削除ボタンでリストから削除。飾り気も何もないシンプルアプリです。

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.jsReact

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は、データ変更を検知したら自動的に再描画します

HTML
  <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を操作している処理がありません。きれいに分離されていることが分かりますね。

JavaScript
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側に記載します

HTML
<body>
  <div id="root"></div>
  <script type="text/babel">
  ...
  </script>
</body>

JavaScriptでHTMLもどきを返却する

JavaScriptのロジック側を見ていきましょう。不思議なのは、retrunの部分。

Appコンポーネント
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

Easy/Simple
  • Vue.js Easy(イージー)
  • React Simple(シンプル)

両者の比較を総括してこう言われます。「簡単なVue.jsシンプルなReact」。

Vueは、Webフロントエンドを簡単に素早く開発できます。Reactは、シンプルなぶん手間はかかりますが拡張性が高いです。

まとめ

SPAの代表格、Vue.jsとReact。その中心となる考え方が、リアクティブプログラミングです。

きちんと理解して使えば、プログラムの開発効率や保守性が劇的に向上します。

しかしながら、これまでのjQuery型開発を行ってきたかたには、リアクティブ型への発想の転換(パラダイムシフト)が必要になってきます。ぜひこれまでの固定概念から脱却して、リアクティブプログラミングを身に着けましょう。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次