クラスコンポーネントが書ける人向けの関数コンポーネントの書き方まとめです。
基本
JSXを戻り値として返す関数を作成する。その関数がコンポーネントになる。
const SampleComponent = () => {
return (<div>Hello!!</div>);
}
export default SampleComponent;
プロパティ
プロパティは、引数からオブジェクトとして渡ってくる。
const SampleComponent = ({name}) => {
return (<div>Hello!! {name}!!</div>);
}
export default SampleComponent;
State
State
はuseState
で利用できる。引数は初期値。
戻り値として配列が返る。配列の一つ目にStateの値が、二つ目にStateを設定するための関数が入っている。
const SampleComponent = () => {
const [name, setName] = useState('hoge');
setName('sfjwr');
return (<div>Hello!! {name}!!</div>);
}
export default SampleComponent;
関数(コンポーネント)が呼ばれるタイミング
DOMの描画が必要なタイミングにて呼び出される。
呼ばれるタイミングの具体例
- 初期描画時
- 親から渡されているプロパティが更新された時
- 自身で持っているStateが更新された時
- 参照しているReduxの値が更新された時
など。
呼び出しの依存関係
コンポーネントに階層があるため、以下のような呼び出しが発生する。
- 親コンポーネント(自身を埋め込んでいるコンポーネント)の呼び出し時、自分も呼びだされる
- 自コンポーネントの関数が呼ばれた時、自分の子コンポーネントの関数も呼びだされる
コンポーネント階層の部分的な更新
とあるコンポーネントが自身のStateを変更した時など、当然自分自身の関数は呼びだされるが、親の関数は呼びだされない。変更のあったコンポーネントより下の階層の関数は呼び出される。
親が呼ばれた時、自分を呼んで欲しくない
プロパティなど、何も状態が変わっていないなら、パフォーマンスの都合で呼んで欲しくない。そのような場合はReact.memo
を利用する。
const SampleComponent = React.memo(({message}) => {
return (<div>Hello!! {message}</div>);
});
export default SampleComponent;
React.memo
で関数をラップすることで、プロパティの変化を監視し、プロパティに変更がなければ関数の呼び出しが省略される。
上記例ではプロパティmessage
が変更されない限り、関数は呼び出されない。
React.memo
したが、プロパティにコールバック関数を受け取っている時
コールバック関数をプロパティに受け取るコンポーネントに対して、ローカル関数をそのまま渡していると、そのプロパティは毎回変化することになってしまい、React.memo
した意味がなくなる。
const App = () => {
const click = () => {
console.log('Hello');
}
return (<SampleComponent onClick={click} />);
}
export default SampleComponent;
上記例では、コンポーネントApp
からSampleComponent
を利用しており、onClick
にローカルの関数を渡している。
しかし、渡している関数click
はApp
の呼び出しの度に生成されているため、onClick
には毎回異なるclick
が渡されている。そのため、App
の呼び出しの都度SampleComponent
も呼び出されてしまう。
これを回避するためには、関数click
をuseCallback
でラップする
const App = () => {
const click = useCallback(() => {
console.log('Hello');
});
return (<SampleComponent onClick={click} />);
}
export default SampleComponent;
useCallback
でラップすることによって、初回呼び出し時に作成された関数が保存され、以降はそれがuseCallback
から戻されるようになる。
2回目以降も関数は作成されているが、useCallback
が無視して捨ててくれる。
ただし、もしそのコールバック関数からState
などを参照している場合は注意が必要である。
なぜなら、
const App = () => {
const [message, setMessage] = useState('Hello');
const click = useCallback(() => {
console.log(message);
});
return (<SampleComponent onClick={click} />);
}
export default SampleComponent;
このような例の場合、useCallback
初回呼び出し時にはmessage
には'Hello'
が入っている。
しかし以降のタイミングでsetMessage
されると、message
の内容は変化することになる。
が、useCallback
は初回の関数を保持しており、初回の関数は初回のmessage
を参照しているため、console.log
される内容が変化しなくなってしまう。
そのため、message
が変化した時にはclick
関数を作り直す必要がある。
これはuseCallback
の第二引数で可能であり、
const App = () => {
const [message, setMessage] = useState('Hello');
const click = useCallback(() => {
console.log(message);
}, [message]);
return (<SampleComponent onClick={click} />);
}
export default SampleComponent;
としておけばuseCallback
がmessage
の変更を監視し、変更があった時には保持している古い関数を破棄し、引数から渡ってきている新しい関数で置き換えてくれる。
こうすることでmessage
の変化時のみclick
関数が入れ替えられるようになり、またSampleComponent
のonClick
プロパティもその時のみ変化するようになり、うまく動作する。
useEffect
について
ネットワークからのデータ取得など、タイミングをずらして処理しなくてはならないものを記述する。
useCallback
と同様に、変数の変化をトリガーに実行できる。
const SampleComponent = ({userId}) => {
const [showName, setShowName] = useState('');
useEffect(() => {
const load = async () => {
const name = await getNameFromUserId(userId);
setShowName(name);
}
load();
}, [userId]);
return (<div>Hello!! {showName}</div>);
}
export default SampleComponent;
このようにすることで、プロパティから渡ってくるuserId
に応じてネットワークからユーザ名を取得して画面に反映させる、というようなことができる。
その他
- コンポーネントが再描画されても更新されるのは仮想DOMなので、本当に再描画されるわけではない(はず)
- 変化のあったところのみ本当に描画される
- パフォーマンスに問題がなければ、無理に
React.memo
しなくていいかも