React を Vue や Nuxt の中で使う

公開日:

GW の技術チャレンジ

大型連休は何か一つ技術的に新しいことに取り組むことにしています。2025年の GW は React に取り組みました。

私は今まで Vue / Nuxt がメインで、あまり React を触って来ませんでした。大型連休はある程度まとまった時間も取れることだし、集中して触れるだろうと思ったので React に取り組みました。

まずゴールを決めました。

  1. チュートリアルを一通り終える
  2. Vue / Nuxt プロジェクトの中で React を使う方法を確立する

チュートリアルは本も検討しましたが、公式サイトの三目並べを作るチュートリアルが分かりやすそうだったのでそれに取り組むことにしました。

React を Vue の中で使う

もう一つのゴールである、「Vue / Nuxt プロジェクトの中で React を使う」ですが、私個人のフロントエンドのコードベースはほとんど Nuxt です。

このままだと連休が終わった後に React のコードを書く機会が作りづらいです。私はこれを機に React を本格的に勉強しようと思っています。

既存の Nuxt プロジェクトの中に React を組み込むことで、日常的に React のコードを書く場所を作る狙いがありました。

この記事の趣旨

前置きが長くなってしまいましたがこの記事では、「Vue / Nuxt プロジェクトの中で React を使う方法」を紹介します。

完成したコード

以下、完成したコードです。

ReactInVue.vue

<template>
  <div ref="mountPoint" />
</template>

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, watchEffect, useAttrs } from 'vue'
import { createElement, type ComponentType } from 'react'
import { createRoot, type Root } from 'react-dom/client'

// Props を受け取る
const props = defineProps<{
  component: ComponentType<Record<string, unknown>>
}>()

// Vue の DOM 参照と属性
const mountPoint = ref<HTMLElement | null>(null)
const attrs = useAttrs()
let reactRoot: Root | null = null

// React をレンダリングする関数
function renderReactComponent() {
  if (reactRoot && props.component) {
    reactRoot.render(createElement(props.component, attrs))
  }
}

onMounted(() => {
  if (mountPoint.value) {
    reactRoot = createRoot(mountPoint.value)
    renderReactComponent()
  }
})

onBeforeUnmount(() => {
  reactRoot?.unmount()
})

// Vue 側からの props 変更を検知して React 側を再描画
watchEffect(() => {
  renderReactComponent()
})
</script>

概要

  • Vue 3 の Composition API を使って書かれたコンポーネントです。
  • Vue から渡された React コンポーネントを、Vue のテンプレート内の div に React でレンダリングします。
  • react-dom/clientcreateRoot を使って React のレンダリングを行います。

解説

テンプレート

<template>
  <div ref="mountPoint" />
</template>
  • この div 要素は React コンポーネントをマウントする「場所」です。
  • ref="mountPoint" によって、Vue からこの DOM 要素を参照できるようにしています。

インポート部分

import { ref, onMounted, onBeforeUnmount, watchEffect, useAttrs } from 'vue'
import { createElement, type ComponentType } from 'react'
import { createRoot, type Root } from 'react-dom/client'
  • ref, onMounted, onBeforeUnmount, watchEffect: Vueのライフサイクルなどを使うためのAPI。
  • useAttrs: Vue の親から渡された属性(propsで定義されていないものも含む)を取得。
  • createElement: React の要素を生成。
  • createRoot: React 18 以降の新しいレンダリング API。
  • ComponentType<Record<string, unknown>>: React コンポーネントの型指定。

Props の受け取り

const props = defineProps<{
  component: ComponentType<Record<string, unknown>>
}>()
  • このコンポーネントは、component という名前の React コンポーネントを props として受け取ります。
  • Record<string, unknown> は props の型を柔軟に受け取るための汎用的な書き方です。

DOM 参照と属性取得

const mountPoint = ref<HTMLElement | null>(null)
const attrs = useAttrs()
let reactRoot: Root | null = null
  • mountPoint: React コンポーネントをマウントするターゲットの DOM。
  • attrs: Vue 側から渡された props やイベントハンドラなどを取得。
  • reactRoot: createRoot() で作成される React のルートオブジェクト。

React をレンダリングする関数

function renderReactComponent() {
  if (reactRoot && props.component) {
    reactRoot.render(createElement(props.component, attrs))
  }
}
  • createElement を使って React のコンポーネントを作成。
  • attrs をそのまま props として渡すことで、Vue 側からのデータやイベントを React に引き継げます。

マウント時の処理

onMounted(() => {
  if (mountPoint.value) {
    reactRoot = createRoot(mountPoint.value)
    renderReactComponent()
  }
})
  • Vue コンポーネントがマウントされたタイミングで、React コンポーネントも mountPoint にマウントされます。

アンマウント時のクリーンアップ

onBeforeUnmount(() => {
  reactRoot?.unmount()
})
  • Vue コンポーネントが破棄されるときに、React 側もアンマウントしてメモリリークを防ぎます。

props 変更の監視と再描画

watchEffect(() => {
  renderReactComponent()
})
  • Vue の propsattrs が変更されたら React コンポーネントを再描画します。
  • watchEffect は依存関係を自動で追跡するため、変更があれば renderReactComponent() が自動で呼ばれます。

使い方の例

Vue 側でこのように使います:

<ReactInVue :component="MyReactComponent" someProp="value" />

すると、someProp などは attrs 経由で React に渡されて、MyReactComponent にレンダリングされます。

さいごに

イベント処理など、対応できてない部分もありますが、とりあえずレンダリングすることはできました。これから少しずつ改良していこうと思います。

ちなみに React に取り組んでみて、もっと早くやればよかったと思いました。Vue と対比しながら理解していっているので、すんなり理解できています。コンポーネントを JS / TS の関数としてまとめられるところが気に入っています。

この記事を書いた人

かい (@takasqr)

ソフトウェアエンジニア。個人的にアプリを作って得た知見をブログに書いています。