導入
最近、React Hooksについて勉強しているのですが、どんなことができるのかなんとなく分かってもどういうメリットがあるのかイマイチ分からないということがあります
そこで今回はReact Hooksを使うとどんなメリットがあるのかをいろいろと調べてみたので学習記録という意味でもまとめてみようと思います
まず最初に:useStateとレンダリングの仕組み
useStateはstateとそのstateに値を入れるsetStateの組を生成します
const [state, setState] = useState(null);
setState('test'); // stateに'test'が入る
なぜuseStateを使うかを理解するにはレンダリングの流れを理解する必要があります。
以下のコードを例に解説します
import React, { useState } from 'react';
export const Page = () => {
const [text, setText] = useState(null);
if(!text) {
setText('test');
console.log(text);
}
console.log(text);
return (
<div className="Page">
{text}
</div>
);
}
こちらのコードを実行すると画面にはtestとだけ表示されます
以下実行の流れを見ていきます
const [text, setText] = useState(null);
まず最初のuseStateでtextとsetTextを定義します。このときtextにはnullが入っています
if(!text) {
setText('test');
console.log(text);
}
console.log(text);
textがnullならtextにtestを入れます。textは最初nullなのでここを通り、textには'test'が入ります。
ここで注意しなければならないのはsetTextはすぐに機能しないということです。setTextはtextの値を【次のレンダリングから】testに変更することを意味していますなのでこの時点ではtextの値は変わっておらずconsoleではどちらもnullが表示されます
return (
<div className="Page">
{text}
</div>
);
その状態で一度returnされます。したがって一回目のレンダリングでは何も表示されません。setTextによりtextが変更されたのでそのまますぐに2回目のレンダリングがtext=testとして実行されます
2回目のレンダリングではtextはnullではないのでsetTextは呼ばれず、そのままtext='test'が出力されます
useStateで実現できること
useStateで実現できるのは
画面内でのアクションによる変化を画面に反映させること
です
こちらも例を見てみます
export const Page = () => {
let text = null
if(!text) {
text = 'test';
console.log(text);
}
console.log(text);
return (
<div className="Page">
<button onClick={() => {text = 'clicked'}}>button</button>
<div>{text}</div>
</div>
);
}
ボタンをクリックすると表示される内容を変えるという内容ですが、これだと再レンダリングされないため、いくらボタンを押しても表示されるのはtestのまま変わりません
ボタンを押したことを画面に反映させるにはuseStateを使わなければなりません。
export const Page = () => {
const [text, setText] = useState(null);
if(!text) {
setText('test');
console.log(text);
}
console.log(text);
return (
<div className="Page">
<button onClick={() => {setText('clicked')}}>button</button>
<div>{text}</div>
</div>
);
}
こちらのコードを使うとちゃんと表示されるtextが変化します。
なお、変更内容を画面に表示しなくてよければuseStateは必要ありません。
export const Page = () => {
let text = null
if(!text) {
text = 'test';
console.log(text);
}
console.log(text);
return (
<div className="Page">
<button onClick={() => {text = 'clicked'}}>button</button>
<button onClick={() => {console.log(text)}}>display</button>
<div>{text}</div>
</div>
);
}
このコードで一度buttonを押してからdisplayを押すとconsoleにはpushedが出力されます
useEffectで実現できること
useEffectによって
一部に依存した処理の不要な呼び出しを防ぐこと
ができます
useEffectは
useEffect(
(), //関数
[] //依存する変数
)
という形で依存する変数が変化したときのみ関数処理を行うことができます
useEffectを使わないとこのようなコードになります
export const Page = () => {
const [clicked, setClicked] = useState(false);
const [text, setText] = useState(null);
if(clicked && !text) {
setText('test');
console.log(text);
}
console.log(text);
return (
<div className="Page">
<button onClick={() => {setClicked(true)}}>button</button>
<button onClick={() => {setText(null)}}>clear</button>
<div>{text}</div>
</div>
);
}
こちらのコードではbuttonを押すとtextにtestが入ります。
しかし、clearを押してtextをnullにしたときもif文の中に入ってしまい、そこでtextに値が入ってしまうためclearの方は上手く動きません。
useEffectを用いるとこのように書けます
export const Page = () => {
const [clicked, setClicked] = useState(false);
const [text, setText] = useState(null);
useEffect(
() => {
if(clicked) {
setText('test');
console.log(text);
}
},
[clicked]
)
console.log(text);
return (
<div className="Page">
<button onClick={() => {setClicked(true)}}>button</button>
<button onClick={() => {setText(null)}}>clear</button>
<div>{text}</div>
</div>
);
}
こちらではclearを押してもtextは変わっていないためuseEffectの中は呼ばれず、textの値も変わりません。
値の変化に連動してstateを変化させる処理はuseEffectを使わないと複雑になりがちなので複数の値を連動させて変化されるときはuseEffectを使うのが良いと思われます
memo, useCallbackでできること
memoは
一部に依存したコンポーネントの呼び出しを最小限にすること
useCallbackは
一部に依存したfunctionの呼び出しを最小限にすること
ができます
役割が似ていることもあり、この2つは一緒に使われることが多いです
useCallbackは
const functionA = useCallback(
(), //関数
[] //依存する変数
)
という形で書くことができ、このようにするとfunctionAは依存する変数が変化しない限り再生成されず、同じ関数を使い回すことができます。
memoも同様で
const ComponentA = memo((props) => {
// コンポーネントの内容
})
と書くことができ、propsの内容が変わらない限り再レンダリングされず、同じコンポーネントを使い回すことができます。
以下のコードを例に考えてみます
(例はこちらを参考にさせていただきました)
const Child = ({handleClick}) => {
console.log('child render')
return(
<button onClick={handleClick}>child button</button>
)
}
export const Page = () => {
const [count, setCount] = useState(0);
console.log('render');
return (
<div className="Page">
<button onClick={() => {setCount(count + 1)}}>button</button>
<Child handleClick={() => console.log('child')} />
<div>{count}</div>
</div>
);
}
こちらのコードではbuttonをクリックするとsetCountが走り、それによってPageだけでなく、変化していないChildも再レンダリングされてしまいます。memoやuseCallbackを使うことでこのような不要な再レンダリングを減らすことができます
const Child = memo((props) => {
console.log('child render')
return(
<button onClick={props.handleClick}>child button</button>
)
})
export const Page = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(
() => {
console.log('child');
},
[]
)
console.log('render');
return (
<div className="Page">
<button onClick={() => {setCount(count + 1)}}>button</button>
<Child handleClick={handleClick} />
<div>{count}</div>
</div>
);
}
このようにすることで、まずuseCallbackにより、handleClickの再生成を避けることができます。すると、memoにより、ChildはhandleClickが変化しない限り再レンダリングされないようになっているのでChildの再レンダリングも避けることができ、結果的に不要なレンダリングを減らすことができるようになります。
useCallbackはmemoと一緒に扱われることが多いので勘違いしやすいですが、効果はあくまでもfunctionの再生成を防ぐことにあるのでmemoと併用する以外にも使用される場面はあると思われます(うまい例が浮かびませんが...)
関数の再生成を防ぐならuseCallback、コンポーネントの再レンダリングを防ぐならmemoと覚えておくといいです
まとめ
useStateは__画面内で発生した変化を画面に反映させるとき__
useEffectは__一部に依存した処理の不要な呼び出しを防ぐとき__
useCallbackは__一部に依存したfunctionの再生成を防ぐとき__
memoは__一部に依存したコンポーネントの再レンダリングを防ぐとき__
に効果がある
参考
https://qiita.com/ossan-engineer/items/740425a0df937a47e093
https://qiita.com/uehaj/items/99f7cd014e2c0fa1fc4e
https://qiita.com/soarflat/items/b9d3d17b8ab1f5dbfed2