【Vue2からReactへの移行Tips】 React Hooksについて

こんにちは、新人エンジニアの久々江です。

本稿では、Vue.jsのv2系+Options APIからReactへの移行のTips、特にReact Hooksに関するものを紹介します。

筆者が担当するプロダクトでは、Vue2からReactへリプレイスを進めています。 筆者自身もチームメンバーもReactの業務経験は無く、実装を進めるにあたり様々な疑問が生まれました。特に弊チームメンバーのようなVue2経験者にとってReact Hooksは特異な概念でした。

本稿は、チームメンバーからのReact Hooksとは何か?、どのように使うのか?といった疑問に対する回答としてまとめましたが、他にもVue2からReactの移行を検討している方、移行を始めた方の一助になれば幸いです。

なお、Vue.jsのバージョンは2.7.10、Reactのバージョンは18.2.0、useSWRのバージョンは2.2.2を使用しています。

TL;DR

  • React HooksはVue2のOptions/Data、Options/Lifecycle Hooksに相当するものである
  • React Hooksを使う際の留意点

    • ループ中や条件式中にuseXXX()は不可
    • Custom Hookの命名useXXX
    • Reactにおける副作用は以下を指す
      • データの取得や変更
      • DOM操作
      • イベントリスナーの設定
      • タイマーのセット
      • state, propsの変化による処理実行
  • React HooksとVue2のOptions/Data、Options/Lifecycle Hooksの対応

    • data ↔︎const, useState, useRef
    • mounted↔︎ useEffect, useSWR
    • watch↔︎ useEffect(() => {}, [watchData])
    • computed↔︎ const data = () => { return culcData }

VueのOptions/Data、Options/Lifecycle Hooksとは

Options/Data、Options/Lifecycle Hooksは、Vue2経験者であれば馴染み深いAPIです。 この二つのAPIコンポーネントの作成、更新、破棄などに関わり、Vue2でのコンポーネントに欠かすことができないものです。

React Hooksとは

React Hooksは、React Component内で状態管理や副作用を行うための関数群です。 Vue2のdatamountedなどのOptions/Data、Options/Lifecycle Hooksに相当します。

React組み込みのReact Hooksがいくつかあり、それを元に作成された関数をCustom Hookと言います。 本稿では以下の組み込みのHooksについて紹介します。

Custom Hookにはサードパーティ製のものがしばしば利用されます。 本稿では、特にfetchに関する状態管理とfetch dataのキャッシュを担うuseSWRを紹介します。

 参考:swr.vercel.app

React Hooksを利用する際の留意点

Vue2からReactにリプレイスする際に留意する点はいくつもありますが、チームメンバーからの質問を元にReact Hooksに関しては以下の3点を挙げます。

  • React Hooksはコンポーネントのトップレベルで宣言しなければならない
  • ある関数においてReact Hooksを利用する場合、その関数はCustom Hookとなり、関数名はuseXXXとしなければならない
  • Reactの副作用

React Hooksはコンポーネントのトップレベルで宣言しなければならない

React HooksとOptions/Data、Options/Lifecycle Hooksは対応するものですが、Vue2では気にする必要の無かった点です。 React Hooksは、コンポーネント内のトップレベルでしか呼び出すことができません。これは、ReactがHooksの呼び出し順序に依存してHooksの状態を管理しているためです。条件式やループによりHooksを呼び出しの順番が変動すると前回と今回で参照する状態が異なりエラーを起こす可能性があります。 例えば、初回レンダリングではuseStateuseStateuseEffect、再レンダリング時に何らかの条件の変化によりuseStateuseEffectと呼び出された場合、useEffectは前回の状態をとしてuseStateを参照してしまいます。

以下の公式ドキュメントの例が分かりやすいので、是非ご覧ください。

ja.legacy.reactjs.org

内部実装に基づいた解説は以下にまとめていただいています。

qiita.com

ある関数においてReact Hooksを利用する場合、その関数はCustom Hookとなり、関数名はuseXXXとしなければならない

React Componentを除く、React Hooksを利用する関数は、Custom Hookとなります。

Custom Hookを作成する際には、その関数名を必ずuseで始める必要があるとされています。例えば、状態管理のためのCustom Hookは、関数名をuseCustomStateのように命名することが推奨されています。この命名規則に従うことで、その関数がReact Hooksであることを識別しやすくなります。

Custom Hooksも使用する際には、組み込みのReact Hooksと同じように、トップレベルで呼び出す必要があります。

Reactにおける副作用

Reactにおける「副作用(Side Effect)」とは、コンポーネントレンダリングサイクル外で発生する処理や影響のことを指します。主に以下のような場面で副作用が発生します。

  • データの取得や変更
  • DOM操作
  • イベントリスナーの設定
  • タイマーのセット
  • state, propsの変化による処理実行

React HooksとVue2のOptions/Data、Options/Lifecycle Hooksの対応

前項で述べた3種のReact HooksとCustom Hookを用いてVue2の実装を実現します。

Vue2でコンポーネントを作成する際、主に以下の手順が必要になります。

  • 管理するを値をdataに定義する
  • レンダリング直後に実行する処理をmountedに定義する
  • 何らかの値を監視し、変更を検知した際に実行する処理をwatchに定義する
  • 依存する値が変更されるたびに再計算される値をcomputedに定義する

次にそれらの手順をVue2で実装する例とReactで実装する例を示します。

管理するを値をdataに定義する

Vue2

Vue2ではコンポーネント内で共通して利用できる値を以下のように定義します。

data

値の宣言

    data () {
      return {
        isShown: false,
      }
    }

値の更新:

mounted () {
  this.isShown = true
}

React

Reactにおけるコンポーネント内で共通して利用できる値の管理方法は、以下の3種類があります。

  • 更新は不可能である(const)
  • 更新可能であり、値の更新による再レンダリングがトリガーされている(useState)
  • 更新可能であり、値の更新による再レンダリングがトリガーされていない(useRef)

それぞれの実装を以下に示します。

const

定数として利用、更新はしない

const isShown = true
useState

DOMの値を動的に変更したい時

// 定義
const [isShown, setIsShown] = useState<boolean>(false)

// 更新
setIsShown(true)
useRef

DOMの値を変更する必要はないが、何らかのイベントにより変数を変更したい時

// 定義
const isShown = useRef<boolean>(false)

// 更新
isShown.current = true

レンダリング直後に実行する処理をmountedに定義する

Vue2

Vue2ではレンダリング直後に実行する処理をmountedに定義します。以下の例のように外部APIの取得に使用されることがあります。

なお、外部APIの取得をレンダリング前(beforeMount)に行った場合、ページアクセスしてからAPI取得にかかる時間分のレンダリングされない状態が続きます。何も表示されない状態を短くするため、レンダリング直後に外部APIを取得しています。

mounted
  data() {
    return {
      data: null,
      loading: true,
      error: null,
    };
  },
  methods: {
    async fetchData() {
      try {
        const response = await fetch('https://api.example.com/data');
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        this.data = await response.json();
      } catch (err) {
        this.error = err.message;
      } finally {
        this.loading = false;
      }
    }
  },
  mounted() {
    this.fetchData();
  }

React

Reactではレンダリング直後に実行する処理をuseEffectに定義します。 データ取得であれば、useSWRも有用です。

useEffect
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await fetch('https://api.example.com/data')
      if (!response.ok) {
        throw new Error(`HTTP error! Status: ${response.status}`)
      }
      const result = await response.json()
      setData(result)
    } catch (err) {
      setError(err.message)
    } finally {
      setLoading(false)
    }
  }

  fetchData()
}, [])
useSWR
const fetcher = async (url) => {
  const response = await fetch(url)
  if (!response.ok) {
    throw new Error(`HTTP error! Status: ${response.status}`)
  }
  return response.json()
};

const { data, error, isLoading } = useSWR('https://api.example.com/data', fetcher)

何らかの値を監視し、変更を検知した際に実行する処理をwatchに定義する

Vue2

Vue2において何らかの値を監視し、変更を検知した際に何らかの処理を実行する場合、watchを利用します。

watch
data: {
  message: 'Hello Vue!'
},
watch: {
  message: function (value) {
    console.log('message changed from', value)
  }
}

React

Reactにおいて値の変更を検知し、処理を実行する際にもuseEffectを利用します。 ただし、第二引数に変更検知の対象となる変数をまとめた依存配列を宣言します。

useEffect
const [message, setMessage] = useState('Hello React!')

useEffect(() => {
  console.log('message changed to', message)
}, [message])

依存する値が変更される度に再計算される値をcomputedに定義する

Vue2

Vue2ではcomputedを用いることで依存する値の変更に伴い、その値も再計算されます。

compouted
data: {
  firstName: 'John',
},
computed: {
  fullName: function() {
    return this.firstName + ' Doe'
  }
}

React

Reactにおいて値の再計算にはいくつか手法がありますが、useStateで管理している値が変更された場合、依存する値も再計算されます。

const [firstName, setFirstName] = useState('John')

const fullName = firstName + ' Doe'

まとめ

本稿ではVue2での開発経験者を対象にReact HooksのTipsをまとめました。React HooksはVue2のOptions/Data、Options/Lifecycle Hooksに相当するものであり、Vue2での実装をReactで代替可能であること、いくつかのルールがあることがわかりました。

至らぬ点があれば、ご指摘いただけますと幸いです。

参考文献