クラスコンポーネントが書ける人向けの関数コンポーネントの書き方まとめです。
基本
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しなくていいかも