こんにちは、新人エンジニアの久々江です。
本稿では、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
VueのOptions/Data、Options/Lifecycle Hooksとは
Options/Data、Options/Lifecycle Hooksは、Vue2経験者であれば馴染み深いAPIです。
この二つのAPIはコンポーネントの作成、更新、破棄などに関わり、Vue2でのコンポーネントに欠かすことができないものです。
React Hooksとは
React Hooksは、React Component内で状態管理や副作用を行うための関数群です。
Vue2のdata
やmounted
などのOptions/Data、Options/Lifecycle Hooksに相当します。
React組み込みのReact Hooksがいくつかあり、それを元に作成された関数をCustom Hookと言います。
本稿では以下の組み込みのHooksについて紹介します。
- useState: 値を管理する関数(値の更新により再レンダリングされる)
- useRef: 値を管理する関数(値を更新しても再レンダリングされない)
- useEffect: 副作用を処理する関数
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を呼び出しの順番が変動すると前回と今回で参照する状態が異なりエラーを起こす可能性があります。
例えば、初回レンダリングではuseState
→useState
→useEffect
、再レンダリング時に何らかの条件の変化によりuseState
→useEffect
と呼び出された場合、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
を利用します。
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で代替可能であること、いくつかのルールがあることがわかりました。
至らぬ点があれば、ご指摘いただけますと幸いです。
参考文献