はじめに
React Hooksの基礎について学んだことを2回に分けて記事にしました。
前回、React Hooks(導入、Props、useState、useReducer、useEffect、axios)
今回、React Hooks(useContext、useMemo、useCallback)
開発環境
React: 17.0.2
Node.js: 14.16.0
Visual Studio Code: 1.57.1
useContext
useContextを使うことでpropsを使わずに親から任意のコンポーネントに簡単にデータを渡すことができます。
最初に親コンポーネントからひ孫コンポーネントにpropsでデータを渡す場合のコードです。
import Child from "./components/Child";
function App() {
return (
<div>
<Child data="データ" />
</div>
);
}
export default App;
親コンポーネントから子コンポーネントへpropsを渡します。
import Grandchild from './Grandchild'
const Child = ({data}) => {
return (
<div>
<Grandchild data={data} />
</div>
)
}
export default Child
受け取ったpsopsのデータをさらに子コンポーネントから孫コンポーネントへ渡します。
import GreatGrandchild from "./GreatGrandchild";
const Grandchild = ({data}) => {
return (
<div>
<GreatGrandchild data={data} />
</div>
)
};
export default Grandchild;
受け取ったpsopsのデータをさらに孫コンポーネントからひ孫コンポーネントへ渡します。
const GreatGrandchild = ({data}) => {
return <div>{data}</div>;
};
export default GreatGrandchild;
ひ孫コンポーネントで受け取り、propsのデータをh1に渡して表示させています。
useContextを使った場合
まずAppContext.jsというコンポーネントを作成します。
AppContext.jsでやることは以下の3行だけです。
import { createContext } from "react";
const AppContext = createContext();
export default AppContext;
次に親コンポーネントでAppContext.jsをインポートして、データを持たせたAppContext.Providerで囲みます。
import Child from "./components/Child";
import AppContext from "./components/AppContext";
function App() {
return (
<AppContext.Provider value={"データ"}>
<div>
<Child />
</div>
</AppContext.Provider>
);
}
export default App;
ひ孫コンポーネントでuseContextを使って、AppContextを経由してデータを受け取ります。
import { useContext } from "react";
import AppContext from "./AppContext";
const GreatGrandchild = () => {
const value = useContext(AppContext);
return <div>{value}</div>;
};
export default GreatGrandchild;
たったこれだけで親からひ孫へ簡単にデータが渡すことができます。
useMemo
const foo = useMemo(() => func(), [A]);
useMemo内の処理はAが変更した時にだけ、レンダー中に実行されます。
Aを指定しない場合は最初のレンダリングだけuseMemo内の処理が実行され、その後はキャッシュした値が返されます。
useMemoが必要なシーンを説明するために極端にパフォーマンスが悪いコード例を見てみます。
下記コードではボタンを押す度にに全く依存関係のないfooの中の処理も実行されます。
それが原因でブラウザーに表示されるカウントはボタンを押した後、少し遅れてプラスされます。
import React, { useState, useMemo } from "react";
const AppMemo = () => {
const [count, setCount] = useState(0);
const bar = 0;
const foo = () => {
let i = 0;
while (i < 1000000000) i++;
return ;
};
return (
<div>
<button onClick={() => setCount((prevCount) => prevCount + 1)}>
{count}
</button>
</div>
);
};
export default AppMemo;
ボタンを押したと時にfooがの処理が走らないように、下記のようにfoo内の処理をuseMemoで囲むだけで、最初のレンダリング後はwhileは走らず、キャッシュしたbazが返されるようになります。
const foo = useMemo(() => {
let i = 0;
while (i < 1000000000) i++;
return bar;
}, []);
下記のように第二引数の配列内にオブジェクトを指定することで、指定したオブジェクトに変更があったときに処理が走るよにできます。
const foo = useMemo(() => {
let i = 0;
while (i < 1000000000) i++;
return bar;
}, [bar]);
React.memo
React.memoはpropsをキャッシュし無駄なレンダリングを無くします。
次のコードは親コンポーネントにボタンを押すたびにカウントされる2種類のボタンを作成し、2つの子コンポーネントDisplay.jsには表示名とCountButton.jsにボタンとカウント数を担当しています。
import React, { useState } from "react";
import CountButton from "./components/CountButton";
import Display from "./components/Display";
function App() {
const [like, setLike] = useState(0);
const [unlike, setUnlike] = useState(0);
const Like = () => {
setLike((prevLike) => prevLike + 1);
};
const Unlike = () => {
setUnlike((prevUnlike) => prevUnlike + 1);
};
return (
<div>
<Display name="like" value={like} />
<CountButton func={Like}>いいね</CountButton>
<Display name="unlike" value={unlike} />
<CountButton func={Unlike}>もう一息</CountButton>
</div>
);
}
export default App;
import React from "react";
const Display = ({ name, value }) => {
console.log(name);
return <div>{value}</div>;
};
export default Display;
import React from "react";
const CountButton = ({ func, children }) => {
console.log(children);
return (
<div>
<button onClick={func}>{children}</button>
</div>
);
};
export default CountButton;
上記の例ではどちらの片方のボタンを押すとlike,unlike両方がレンダリングされ、Display.js、CountButton.jsのconsole.logが走っていることが確認できます。
React.memoを使って、propsの再レンダリングを防ぐことができます。
方法は簡単で再レンダリングを防ぎたいコンポーネントをexport時にReact.memoで囲うだけです。
import React from "react";
const Display = ({ name, value }) => {
console.log(name);
return <div>{value}</div>;
};
export default React.memo(Display); // ここ
import React from "react";
const CountButton = ({ func, children }) => {
console.log("log", children);
return (
<div>
<button onClick={func}>{children}</button>
</div>
);
};
export default React.memo(CountButton); // ここ
これでDiplay.jsの方はconsole.logが走っていないことが確認できます。
ですが、まだCountButton.jsの関数funcは再レンダリングされています。
(片方のボタンを押したら両方の関数が走るということではなく、両方の関数がインスタンス化されてしまうということです)
useCallback
前項での問題、子コンポーネントに渡すコールバック関数の再レンダリングを防ぐためにuseCallbackを使用します。
際レンダリングさせたくない関数をuseCallbackで囲むだけです。
import React, { useState, useCallback } from "react"; // useCallbackインポート
import CountButton from "./components/CountButton";
import Display from "./components/Display";
function App() {
const [like, setLike] = useState(0);
const [unlike, setUnlike] = useState(0);
const Like = useCallback(() => { // useCallbackで囲む
setLike((prevLike) => prevLike + 1);
}, []); // 今回は必要ありませんが、stateによって関数内の処理に変更するような場合は第二引数の配列にstateを記入する
const Unlike = useCallback(() => {
setUnlike((prevUnlike) => prevUnlike + 1);
}, []);
return (
<div>
<Display name="like" value={like} />
<CountButton func={Like}>いいね</CountButton>
<Display name="unlike" value={unlike} />
<CountButton func={Unlike}>もう一息</CountButton>
</div>
);
}
export default App;