「nullish coalescing」と「更新関数」
nullish coalescing
は、??
という形で使われます。
この演算子は、左の値がnull
またはundefined
の場合に、右の値を評価して返すことができるものです。もし左の値がnull
やundefined
以外であれば、左の値がそのまま返されます。
例えば、以下のコードを考えてみてください。
const character = {
name: "Mario"
// heightが省略されている
};
console.log(character.height ?? '???'); // 出力: '???'
この場合、character
オブジェクトにはheight
プロパティがないため、character.height
はundefined
と評価されます。したがって、nullish coalescing
演算子を使用すると、右の値である'???'
が返されます。
三項演算子を使用した場合、このように書くこともできます。
console.log(character.height !== null && character.height !== undefined ? character.height : '???');
しかし、この場合のコードは長くなりますし、null
やundefined
のチェックを明示的に行う必要があります。そのため、オプショナルなプロパティが省略されているケースなどには、nullish coalescing
演算子を使用する方が読みやすく、効率的です。
親コンポーネントから子コンポーネントへのデータの受け渡しと、子コンポーネントから親コンポーネントへのフィードバックについてのコード
import type { FC } from 'react';
import { useState } from 'react';
import { Box, Button, ButtonGroup, Stat, StatLabel, StatNumber } from '@chakra-ui/react';
const IncrementButton: FC<{ onClick: () => void }> = ({ onClick }) => (
<Button w="xs" colorScheme="green" variant="solid" onClick={onClick}>
+1
</Button>
);
const ResetButton: FC<{ onClick: () => void }> = ({ onClick }) => (
<Button w="xs" colorScheme="red" variant="solid" onClick={onClick}>
Reset
</Button>
);
const Counter: FC = () => {
const [count, setCount] = useState(0);
const increment = () => setCount((c) => c + 1);
const reset = () => setCount(0);
return (
<Box p={5} w="sm" borderWidth="1px" borderRadius="lg" boxShadow="base">
<Stat mb={2}>
<StatLabel fontSize={18}>Count</StatLabel>
<StatNumber fontSize={42}>{count}</StatNumber>
</Stat>
<ButtonGroup maxW="xs" m={2} variant="contained" isAttached>
<IncrementButton onClick={increment} />
<ResetButton onClick={reset} />
</ButtonGroup>
</Box>
);
};
export default Counter;
更新関数について
- 初めの状態:
const increment = () => setCount((c) => c + 1);
この行は、count
の現在の値を参照せずに、以前の値を関数で受け取り、その値に1を加えた新しい値で更新する方法を示しています。
- 秋谷さんの予想:
const increment = () => setCount(count + 1);
この書き方は、現在のcount
の値に直接アクセスして、1を加える方法を示しています。しかし、この方法はある問題に直面する可能性があります。
- 新しい試み:
const plusThree = () => [...Array(3).keys()].forEach(() => setCount(count + 1));
ここで、setCount(count + 1)
を3回連続で呼び出していると予想されるコードを導入しています。これにより、ボタンをクリックするとcount
が3増加することを期待するかもしれませんが、実際にはそれが起こらない理由があります。
-
結果:
ボタンをクリックしても、count
は1しか増加しない。これはReactの特性によるものです。 -
解決策の導入:
const plusThree = () => [...Array(3).keys()].forEach(() => setCount((c) => c + 1));
ここで、直接count
の現在の値にアクセスするのではなく、関数を使用して前の値を取得してから更新します。これにより、count
が正しく3ずつ増加するようになります。
-
理由:
Reactの再レンダリングと状態管理の仕組みにより、setCount
が3回連続で呼び出されると、それぞれの呼び出しにおいてcount
の値は一定となります。したがって、setCount(count + 1)
を使用すると、3回の更新がすべて同じ値に基づいて行われることになります。一方、関数を渡す方法を使用すると、各更新が前回の値に基づいて行われるため、期待される結果が得られます。
この挙動は、Reactの再結合とコンポーネントの差分更新のしくみ、特にコンポーネント関数が実行される際の状態の変更方法に起因しています。
関数内でstateの定義をするな
理由は後ほど
コンポーネントの副作用について
コンポーネントの『副作用』とは、コンポーネントのレンダリングに影響を及ぼす外部の操作や変更のこと
を指します。これは、コンポーネントの状態やpropsによって引き起こされるだけでなく、外部データの取得やDOMの手動操作などによっても引き起こされる可能性があります。
以下に具体的な例を示します。
1. ネットワークを介したデータの取得:
ReactのコンポーネントでAPIからデータを取得する場合、その操作は副作用として扱われます。なぜなら、データの取得は非同期であり、その結果に基づいてコンポーネントの状態が変わる可能性があるからです。
import React, { useState, useEffect } from 'react';
function UserComponent() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then(response => response.json())
.then(data => setUser(data));
}, []); // 空の依存配列を指定して、コンポーネントのマウント時のみ実行する
return <div>{user ? user.name : 'Loading...'}</div>;
}
上記のコードでは、useEffect
フックを使ってAPIからデータを取得しています。このデータ取得は副作用です。
2. ログの記録:
アプリケーションの動作をログに記録する場合、その操作は副作用として扱われます。
useEffect(() => {
console.log('Component has been rendered.');
}, []);
3. リアルDOMの手動での書き換え:
直接DOMを操作する場合、その操作は副作用として扱われます。しかし、Reactでは直接DOMを操作するのは推奨されません。
useEffect(() => {
const titleElement = document.getElementById('title');
titleElement.style.color = 'blue';
}, []);
以上のように、副作用とはコンポーネントのレンダリングに外部から影響を及ぼす操作のことを指し、useEffect
フックを使ってこれらの操作を制御することができます。
副作用を扱う関数の一つ、useEffectについて
import React, { useState, useEffect, FC } from 'react';
const SampleComponent: FC = () => {
// 1. Stateの定義
const [data, setData] = useState<any>(null);
// その他のロジック...
// 2. useEffectの使用
useEffect(() => {
// 2.1 データのフェッチや設定
fetchDataFromAPI().then(fetchedData => {
setData(fetchedData);
});
// 2.2 コンポーネントのアンマウント時に実行されるクリーンアップ関数
return () => {
clearSomething();
};
}, [someDeps]); // 2.3 依存配列
// レンダリングのロジック...
return (
<div>
{data}
</div>
);
}
// 仮のAPIデータのフェッチ関数
const fetchDataFromAPI = async () => {
// ここで実際にAPIからデータを取得するロジックを書きます。
return "Sample Data";
}
// 何かのクリーンアップ関数
const clearSomething = () => {
console.log("Cleanup logic here");
}
export default SampleComponent;
段階的な解説:
-
Stateの定義:
-
useState
フックを使用して、data
という名前のstateを定義しています。setData
は、このdata
の値を更新するための関数です。
-
-
useEffectの使用:
-
useEffect
は、コンポーネントのライフサイクルに関連した副作用(side effects)を実行するためのフックです。 - 2.1: ここでは仮のAPIデータ取得関数を使用してデータを取得し、それをstateに設定しています。
- 2.2:
useEffect
内でreturnされる関数は、クリーンアップ関数として知られています。この関数は、コンポーネントがアンマウントされる際、または依存配列内の値が変更されたときに実行されます。これは例えばイベントリスナの解除やタイマーのクリアなど、不要な副作用をクリーンアップするために使用します。 - 2.3: 依存配列
[someDeps]
は、指定された値に変更があった場合にのみuseEffect
内のロジックを再実行するためのものです。もし空の配列[]
が渡されると、useEffect
内のロジックはコンポーネントのマウント時とアンマウント時にのみ実行されます。
-
以上のように、このサンプルコンポーネントはuseState
とuseEffect
フックを中心に、データの取得と表示、そして何らかのクリーンアップロジックを実装しています。