これまでVueを使ってきましたが、最近Reactを学び始めました。
その中で気になったのは、「状態管理(useState)」と「ライフサイクル(useEffect)」の違いです。

Vueも、Vue 2とVue 3では書き方がかなり変わりました。
Vue 3で導入されたComposition APIは、Reactの記述スタイルに近くなったと言われていますが、実際に触ってみると、やはり違いも多くあると感じました。

本記事では、Vue 3(Composition API)とReactの使い方の違いや、初めてReactを触った際に戸惑った点をまとめてみます。

状態管理の違い:ref vs useState

ボタンのクリック回数を表示するUIを、Vue 3 と React でそれぞれ作ってみました。

Vue ではこうだった

<script setup lang="ts">
import { ref } from 'vue'

const count = ref(0)

const increment = () => {
  count.value++
}
</script>

<template>
  <button @click="increment">{{ count }}回クリックしました</button>
</template>
  • ref(0) で状態を作り、count.value で値を読み書きしていた。
  • テンプレート内では count とだけ書けばよくて .value は不要。
  • 状態の値を直接書き換えるイメージで操作していた。

React ではこうだった

import { useState } from 'react'

export default function Counter() {
  const [count, setCount] = useState<number>(0)

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}回クリックしました
    </button>
  )
}
  • useState は配列を返していて、分割代入で [count, setCount] と書く必要がある。
  • 状態を更新するには setCount という関数を必ず使う。
  • 直接 count に代入しても状態は変わらず、必ず更新関数を呼ぶ必要がある。

感じた違い

Vue は状態を箱(ref)で包んで直接値を書き換えるイメージなのに対して、
React は状態の値と更新関数がセットで返ってきて、更新は関数を通して行うので、
「状態変数と更新関数を同時に扱う」という点が初めてだとけっこう新鮮でした。

ライフサイクルの違い:onMounted / watch vs useEffect

以下は、「コンポーネントがマウントされたときにログを出す」「状態が変わったときに別の値を更新する」処理を、Vue 3 と React で書いた例です。

Vue ではこうだった

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'

const count = ref(0)
const doubled = ref(0)

onMounted(() => {
  console.log('コンポーネントがマウントされました')
})

watch(count, (newVal) => {
  doubled.value = newVal * 2
})
</script>

<template>
  <button @click="count++">クリック: {{ count }}</button>
  <p>2倍: {{ doubled }}</p>
</template>
  • onMounted でコンポーネントのマウント時の処理を書いていた。
  • watch で特定の状態の変化を監視してリアクティブに反応できた。
  • ライフサイクルが細かく分かれていて、それぞれの役割が明確だった。

※本来は doubled のような派生値は computed を使うのが自然ですが、今回は React の useEffect に近い書き方を比較するために watch を使っています。

React ではこうだった

import { useState, useEffect } from 'react'

export default function Counter() {
  const [count, setCount] = useState(0)
  const [doubled, setDoubled] = useState(0)

  useEffect(() => {
    console.log('コンポーネントがマウントされました')
  }, [])

  useEffect(() => {
    setDoubled(count * 2)
  }, [count])

  return (
    <>
      <button onClick={() => setCount(count + 1)}>クリック: {count}</button>
      <p>2倍: {doubled}</p>
    </>
  )
}
  • すべての副作用は useEffect で書く。
  • 第2引数の依存配列によって発火タイミングを制御する。
  • 依存配列が空 [] のときは、Vueの onMounted と同じくマウント時に一度だけ実行される。
  • 依存配列に値を入れると、Vueの watch と同じくその値が変わるたびに実行される。
  • 一つのフックで複数の役割を兼ねるため、最初は慣れが必要。

感じた違い

Vue は「変数を定義し、その変数の変化に対して何かする」というスタイルで、たとえば watch を使って「この値が変わったらこの処理をする」といった書き方になります。
一方で React の useEffect は「この処理を実行したい、ただしこの値が変わったときにだけ」というように、処理を主体にして依存関係を後から指定する、という順序の違いを感じました。

つまり、Vue は「値 → 処理」の流れ、React は「処理 → 値」の流れで考える必要があり、この考え方の違いに最初は戸惑いました。

まとめ

Vue と React にはそれぞれ特徴があり、状態管理やライフサイクルの扱い方も異なります。
Vue はライフサイクルが細かく分かれていて直感的に使いやすく、
React はフックを使って一つの仕組みで柔軟に管理できる反面、慣れるまで少し時間がかかります。

私自身もまだReactを学習中の身なので、今回の違いを踏まえてさらに理解を深め、
より良いコードを書けるように引き続き勉強していきたいと思います。