はじめに
6月に未経験から開発エンジニアとして転職し、はや半年が過ぎました。
記事を書こう書こうと思い気づくと12月になっていたので、
アドベントカレンダーに参加し、転職後初の記事投稿をさせて頂きます。
業務でReactを扱う中で、hooksへの理解が足りてないと感じたので
すでにまとめられている記事もありますが、理解を深めるという意味も込めてReact hooksに関して改めてまとめました。
React hooksとは
公式ドキュメントには下記の様にあります。
フック (hook) は React 16.8 で追加された新機能です。state などの React の機能を、クラスを書かずに使えるようになります。
参考:https://ja.reactjs.org/docs/hooks-intro.html
具体的にどう変わったのかは、こちらの記事の説明が非常にわかりやすかったのですが、
要約すると、フックを使ってクラスコンポーネントではなく関数コンポーネントで書くことで、
簡潔に、再利用性の高いコードを書けるようになったということです。
今回は基本のフック3つを含む代表的なフックを紹介します。
useState
まずはサンプルのコードです。
下記はボタンをクリックするとカウンターの値が増えていくというコードです。
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
useStateでは、const [state, stateを更新する関数] = useState(初期値);
のように宣言します。
上記ではstateにcount
、更新する関数にsetCount
初期値に0
をそれぞれ宣言しており、
return内で、ボタンをクリックするとcountの値が一つずつ増えるという処理を実行しています。
このように、useStateではstateの保持と管理を行うことができます。
useEffect
次にuseEffectです。
下記のコードでは、ReactがDOMを更新した後に、console.log
でメッセージを出力します。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`You clicked ${count} times`);
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
動作は先程と同じですが、DOMが更新されるたび(クリックが押されてcountの値が変化するたび)に、
useEffect内のconsole.log
が実行されていることがわかります。
このように、useEffectに書いた処理はレンダリングが行われたタイミングで実行することができます。
言い換えると、レンダリングの後まで実行タイミングを遅らせることができます。
useEffectを使用することで、レンダリングが行われた後に何かの操作をすることなしに
変数の更新や、データの取得を行うことができます。
先ほどの例ではuseEffectに第一引数のみ渡していましたが、
第二引数に配列を渡すことで、実行のタイミングを指定することができます。
// 空の配列を渡すと初回の画面レンダリング時のみconsole.logが実行される
useEffect(() => {
console.log(`You clicked ${count} times`);
}, []);
// 「count」を渡すと最初のレンダリング時と、countの値が変更されたときのみconsole.logが実行される
useEffect(() => {
console.log(`You clicked ${count} times`);
}, [count]);
useContext
useContextを説明するために、まずはReactにおけるPropsの渡し方について理解する必要があります。
以下のコードでは Button コンポーネントのthemeをスタイルする為に、
Appでtheme="dark"
という値をToolbarコンポーネントに渡し、
ToolbarコンポーネントからThemeButtonコンポーネントへthemeをバケツリレーのように渡しています。
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// Toolbar コンポーネントは外部から "theme" プロパティを受け取り、
// プロパティを ThemedButton へ渡します。
// アプリ内の各ボタンがテーマを知る必要がある場合、
// プロパティは全てのコンポーネントを通して渡される為、面倒になります。
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
class ThemedButton extends React.Component {
render() {
return <Button theme={this.props.theme} />;
}
}
useContextを使用することで、バケツリレーを避けることができます。
// コンテクストを使用すると、全てのコンポーネントを明示的に通すことなしに
// コンポーネントツリーの深くまで値を渡すことができます。
// 現在のテーマ(デフォルトは "light")の為のコンテクストを作成します。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 以下のツリーへ現在のテーマを渡すためにプロバイダを使用します。
// どんな深さでも、どのようなコンポーネントでも読み取ることができます。
// このサンプルでは、"dark" を現在の値として渡しています。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中間のコンポーネントはもう明示的にテーマを
// 下に渡す必要はありません。
function Toolbar() {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// React は上位の最も近いテーマプロバイダを見つけ、その値を使用します。
// この例では、現在のテーマは "dark" です。
const theme = useContext(ThemeContext);
render() {
return <Button theme={this.theme} />;
}
}
このように、useContextを使いたいときは、
①React.createContext
でContextの器を作成する。
②作成したContextのProviderで、Stateを渡したいコンポーネントを囲む
③Stateを参照したいコンポーネントでReat.useContext
を使う
といった流れになります。
useContextを使用するとシンプルな実装でグローバルなState管理を行うことができますが、
コンテクストはコンポーネントの再利用をより難しくする為、慎重に利用する必要があります。
useCallback
まずはサンプルコードです。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallbackでは、useEffectと同様第二引数に依存配列をとり、メモ化したコールバック関数を返します。
メモ化とは、前回の処理結果を保持しておくことで処理を高速化する技術で、キャッシュのようなイメージです。
上記の例では、memoizedCallback
はdoSomething(a, b)
を実行しますが、
依存配列の要素であるaかbが変化しない限り、メモ化したdoSomething(a, b)
を返します。
このようにuseCallbackを使用し不要な再描画を減らすことで、パフォーマンスの向上が期待できます。
useMemo
最後にuseMemoについて説明します。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemoでもuseCallbackと同様、第二引数に依存配列を取りますが、
useCallbackではメモ化したコールバック関数そのものを返すのに対し、
useMemoでは関数の結果をメモして返します。
上記の例では、aもしくはbの値が変化したときのみ、computeExpensiveValue(a, b)
を再実行し、
それ以外のときは前回のcomputeExpensiveValue(a, b)
の結果をそのまま返します。
useMemoの第一引数にはコンポーネントを渡すこともでき、
その場合は依存配列の値が変化したときのみコンポーネントを再レンダリングします。
useMemoもuseCallbackと同様、不要な再描画を減らすことで、
パフォーマンスの向上が期待できます。
useCallbackとuseMemoの違いについて
useCallbackとuseMemoはどちらもパフォーマンス最適化のために使うものであり、
逆にいうと使わなくても動作はします。
また、上でも書いたようにuseCallbackは関数そのもの
を返すのに対し、
useMemoでは関数の結果
を返します。
これは、useCallback(fn, deps) は useMemo(() => fn, deps) と等価であるということを意味します。
関数コンポーネントにおいて、レンダリングが発生するとコンポーネント内で定義されたアロー関数は再作成されます。
そのため、useMemoでメモ化したコンポーネントにコールバック関数を渡している場合は注意が必要です。
アロー関数がレンダリングによって再作成された結果、依存配列のコールバック関数が変化したとみなされ、
メモ化したコンポーネントは再利用されません。
そのような関数に対しuseCallbackを使うことで関数の再生成をスキップでき、
コンポーネントのメモ化を実現することができます。
まとめ
今回は主要なReact hooksについてまとめましたが、hooksを利用することでコードをシンプルにかけたり、
パフォーマンス向上を図ることができるとわかりました。
ここでは基本的なhooksのみをとりあげましたが、今後はカスタムフックなども学習し
Reactへの理解を深めていきたいと思います。
参考