ReactによるVue.js v2プロジェクトのリプレイス〜基本API からライフサイクルまで〜

React によるVue.js v2プロジェクトのリプレイス〜基本APIからライフサイクルまで〜

はじめに

この記事はPLEX Advent Calendar 2024の 13 日目の記事です。

Vue.js v2 のサポートが 2023 年 12 月 31 日に終了した事もあり、Vue.js/Nuxt v2のプロダクトをReact/Next.jsへリプレイスする作業を行いました。

その際に Vue.js v2 と React の記法の違いについて少し戸惑うところがあったため、本記事では、Vue.js v2 の基本的な APIdatamethodscomputedなど) を React ではどのように置き換えるかについて、コード例を交えて解説します。

主要な API の置き換え方法

Vue.js v2 の主要 API を React でどのように置き換えるかについて、具体的な例を見ていきましょう。

1.data

Vue.js v2 のdataはリアクティブな状態(オブジェクトのデータ)を返す関数です。 ここで定義されたデータはリアクティブなものとして扱われます。

Vue.js v2

<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "Hello Vue!",
    };
  },
};
</script>

React

React ではuseStateを用いて置き換えます。

import React, { useState } from "react";

function App() {
  const [message, setMessage] = useState("Hello React!");

  return (
    <div>
      <p>{message}</p>
    </div>
  );
}

export default App;

2.methods

Vue.js v2 のmethodsコンポーネント内で使用されるメソッドを定義するオブジェクトです。 ユーザーのイベント(クリックなど)に対応する際や、データ操作のロジックを持つ関数などを定義します。

Vue.js v2

<template>
  <div>
    <button @click="sayHello">Click me</button>
  </div>
</template>

<script>
export default {
  methods: {
    sayHello() {
      alert("Hello Vue!");
    },
  },
};
</script>

React

React ではコンポーネント内で関数を定義します。

import React from "react";

function App() {
  const sayHello = () => {
    alert("Hello React!");
  };

  return (
    <div>
      <button onClick={sayHello}>Click me</button>
    </div>
  );
}

export default App;

3.computed

Vue.js v2 のcomputedは依存しているデータに基づいて計算されるプロパティを定義するオブジェクトです。 ここに定義したデータはキャッシュされて、依存するデータが変更された場合にのみ再計算されます。

Vue.js v2

<template>
  <div>
    <p>{{ reversedMessage }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "Hello Vue!",
    };
  },
  computed: {
    reversedMessage() {
      return this.message.split("").reverse().join("");
    },
  },
};
</script>

React

React ではuseMemoで返す値をメモ化します。

import React, { useState, useMemo } from "react";

function App() {
  const [message, setMessage] = useState("Hello React!");

  // messageが更新された時のみ再計算
  const reversedMessage = useMemo(() => {
    return message.split("").reverse().join("");
  }, [message]);

  return (
    <div>
      <p>{reversedMessage}</p>
    </div>
  );
}

export default App;

4.props

Vue.js v2 のpropsは親コンポーネントから子コンポーネントにデータを渡すためのプロパティを定義するオブジェクトです。 子コンポーネントpropsで宣言されたプロパティを通じて、親から渡されたデータにアクセス可能です。

Vue.js v2

<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  name: "Message",
  // 親からのデータ(:message="Hello Vue!")の箇所を受け取る
  props: {
    message: {
      type: String,
      required: true,
    },
  },
};
</script>
<template>
  <div>
    <Message :message="Hello Vue!" />
    <Message :message="Hello Vue v2!" />
  </div>
</template>

<script>
import Message from "./Message.vue";

export default {
  name: "App",
  components: {
    Message
  }
};
</script>

React

React ではコンポーネントの引数にpropsを渡します。

import React from "react";

// 子コンポーネント
function Message({ message }) {
  return <p>{message}</p>;
}

// 親コンポーネント
function App() {
  return (
    <div>
      <Message message="Hello React!" />
    </div>
  );
}

export default App;

5.emit

Vue.js v2

Vue.js v2 のemitは子コンポーネントが親コンポーネントにイベントを発火させたりデータを渡すための機能です。

<template>
  <div>
    <button @click="sendMessage">Click me</button>
  </div>
</template>

<script>
export default {
  methods: {
    sendMessage() {
      // イベント名と親コンポーネントに送信する値
      this.$emit("onClickMessage", "Hello from child!");
    },
  },
};
</script>
<template>
  <div>
    <!-- @onClickは子のemitのイベント名 -->
    <Button @onClickMessage="getMessage" />
    {{ message }}
  </div>
</template>

<script>
import Button from "./Button.vue";

export default {
  name: "App",
  components: {
    Button,
  },
  data() {
    return {
      message: "",
    };
  },
  methods: {
    // value = 子コンポーネントから受け取る値(Hello from child!)
    getMessage(value) {
      this.message = value;
    },
  },
};
</script>

React

React では親コンポーネントから子コンポーネントpropsとしてイベントハンドラを渡すことで親にイベントを発火できます。

import React from "react";

function Button({ onClickMessage }) {
  const sendMessage = () => {
    onClickMessage("Hello from child!");
  };

  return <button onClick={sendMessage}>Click me</button>;
}

function App() {
  const handleMessage = (message) => {
    console.log(message); // "Hello from child!"
  };

  return (
    <div>
      <Button onClickMessage={handleMessage} />
    </div>
  );
}

export default App;

6.watch

Vue.js v2 のwatchは特定のデータの変更を監視し、変更時に特定の関数を実行するオブジェクトです。 データが変更されたときに別の処理を実行したい場合に役立ちます。

Vue.js v2

<template>
  <div>
    <!-- 入力値とVueインスタンスのデータを同期 -->
    <input v-model="message" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "",
    };
  },
  watch: {
    // 監視したいデータ名(message)
    message(newVal, oldVal) {
      console.log(`Message changed from ${oldVal} to ${newVal}`);
    },
  },
};
</script>

React

React ではuseEffectの引数の依存配列箇所に監視する state(ここでは message)を指定します。

import React, { useState, useEffect } from "react";

function App() {
  const [message, setMessage] = useState("");

  useEffect(() => {
    console.log(`Message changed to ${message}`);
  }, [message]);

  return (
    <div>
      <input value={message} onChange={(e) => setMessage(e.target.value)} />
    </div>
  );
}

export default App;

7.ref

Vue.js v2 のrefコンポーネント内の DOM 要素や子コンポーネントに直接アクセスするための機能です。

Vue.js v2

<template>
  <div>
    <!-- refの定義 -->
    <input ref="myInput" />
    <button @click="focusInput">Focus Input</button>
  </div>
</template>

<script>
export default {
  methods: {
    focusInput() {
      // inputのDOMにアクセス
      this.$refs.myInput.focus();
    },
  },
};
</script>

React

React ではuseRef を用います。

import React, { useRef } from "react";

function App() {
  const inputRef = useRef(null);

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}

export default App;

ライフサイクルフックの置き換え方法

Vue.js v2 のライフサイクルフックは、Vue インスタンスの生成から破棄までの一連の間に実行される関数のことです。

React では主にuseEffectを使用することで同等の機能が実現可能です。

1.mounted

インスタンスが DOM 要素にマウント(描画)された後に呼ばれる処理です。

Vue.js v2

<script>
export default {
  mounted() {
    console.log("mounted");
  },
};
</script>

React

React ではuseEffectを使用して依存配列を空にすることで、マウント後に一度だけ実行されるようになります。

import React, { useState, useEffect } from "react";

function App() {

  useEffect(() => {
    console.log("mounted");
  }, []);

  return (
    // 略
  );
}

export default App;

2.updated

データが変更・更新され DOM が再レンダリングされた後に毎回呼ばれる処理です。

Vue.js v2

<script>
export default {
  updated() {
    console.log("updated");
  },
};
</script>

React

React ではuseEffectを使用して第二引数の依存配列を無しにすることでstateが変更された後に毎回実行されます。

import React, { useState, useEffect } from "react";

function App() {
  const [message, setMessage] = useState("Hello React!");

  useEffect(() => {
    console.log("updated");
  });

  return (
    <div>
      <p>{message}</p>
      <button onClick={() => setMessage("Updated message!")}>
        Update Message
      </button>
    </div>
  );
}

export default App;

3.destroyed

インスタンスが破棄された後に呼び出されるフックです。

Vue.js v2

<script>
export default {
  destroyed() {
    console.log("destroyed");
  },
};
</script>

React

React ではuseEffectのクリーンアップ関数を使用します。

import React, { useState, useEffect } from "react";

function App() {
  const [message, setMessage] = useState("Hello React!");

  useEffect(() => {
    // クリーンアップ
    return () => {
      console.log("destroyed");
    };
  }, []);

  return (
    <div>
      <p>{message}</p>
    </div>
  );
}

export default App;

ディレクティブの置き換え方法

Vue.js v2 のディレクティブはv-から始まる独自の HTML 属性です。 値には JavaScript の式や data、メソッドなどを定義して、属性値の変化による副作用をリアクティブに DOM に適用します。

1. v-if, v-else-if, v-else

Vue.js v2 のv-if, v-else-if, v-elseは条件付きで要素のレンダリングを制御します。

Vue.js v2

<template>
  <div>
    <p v-if="isLoggedIn">ログイン済みです。</p>
    <p v-else-if="isGuest">ゲストとして閲覧中です。</p>
    <p v-else>ログインしてください。</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isLoggedIn: false,
      isGuest: true,
    };
  },
};
</script>

React

React では三項演算子を JSX 内で使用します。

import React from "react";

function App() {
  const isLoggedIn = false;
  const isGuest = true;

  return (
    <div>
      {isLoggedIn ? (
        <p>ログイン済みです。</p>
      ) : isGuest ? (
        <p>ゲストとして閲覧中です。</p>
      ) : (
        <p>ログインしてください。</p>
      )}
    </div>
  );
}

export default App;

2. v-show

v-showCSSdisplayプロパティで要素の表示/非表示を制御します。

Vue.js v2

<template>
  <div>
    <p v-show="isVisible">この要素は表示されています。</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isVisible: true,
    };
  },
};
</script>

React

React では、条件によってstyle属性を変更します。

import React from "react";

function App() {
  const isVisible = true;

  return (
    <div>
      <p style={{ display: isVisible ? "block" : "none" }}>
        この要素は表示されています。
      </p>
    </div>
  );
}

export default App;

3. v-for

v-forは配列やオブジェクトをループして、要素を繰り返し描画します。

Vue.js v2

<template>
  <ul>
    <li v-for="(item, index) in items" :key="index">{{ item }}</li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: ["リンゴ", "バナナ", "オレンジ"],
    };
  },
};
</script>

React

React では、mapメソッドを使用します。

import React from "react";

function App() {
  const items = ["リンゴ", "バナナ", "オレンジ"];

  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

export default App;

4. v-model

v-modelはフォーム要素とデータの双方向バインディングを実現します。 下記では入力に連動して data の message に入力内容が追加されます。

Vue.js v2

<template>
  <input v-model="message" />
</template>

<script>
export default {
  data() {
    return {
      message: "",
    };
  },
};
</script>

React

React ではuseStateを使い、value属性とonChangeイベントを連携させます。

import React, { useState } from "react";

function App() {
  const [message, setMessage] = useState("");

  return <input value={message} onChange={(e) => setMessage(e.target.value)} />;
}

export default App;

5. v-bind

v-bindは様々な HTML の属性やコンポーネントのプロパティにデータを動的にバインド出来ます。

Vue.js v2

<template>
  <img v-bind:src="imageSrc" />
  <img :src="imageSrc" /><!-- 「:属性名」で省略記法となる -->
</template>

<script>
export default {
  data() {
    return {
      imageSrc: "https://picsum.photos/id/237/200/300",
    };
  },
};
</script>

React

React では、プロパティをそのまま JSX で指定します。

import React from "react";

function App() {
  const imageSrc = "https://picsum.photos/id/237/200/300";

  return <img src={imageSrc} />;
}

export default App;

6. v-on

v-on はイベントリスナーを登録する際に使用されます。

Vue.js v2

<template>
  <button v-on:click="handleClick">クリック</button>
  <button @click="handleClick">クリック</button
  ><!-- 「@イベント名」で省略記法となる -->
</template>

<script>
export default {
  methods: {
    handleClick() {
      alert("クリックされました");
    },
  },
};
</script>

React

React では、イベントハンドラーを直接指定します。

import React from "react";

function App() {
  const handleClick = () => {
    alert("クリックされました");
  };

  return <button onClick={handleClick}>クリック</button>;
}

export default App;

おわりに

今回はVue.js v2 の基礎機能をReactで書き直す方法についてご紹介しました。

実際にはリプレイスする規模感によって両者の記法を参考にしただけでは事足りなくなるかと思いますが、 同じような状況に立つ方の第一歩として参考にしていただければ幸いです。

dev.plex.co.jp

参考文献

v2.vuejs.org

ja.react.dev