useEffect 完全ガイド
というわけで読んでみる
🤔 componentDidMountをuseEffectで再現する方法は?
useEffect(fn, []) でも再現できますが、全く同じという訳ではありません。 componentDidMount とは違い、props と state をキャプチャーします。なので、callback の中でも初期 props と state を参照できます。一番最新のなにかを参照したい場合は、ref として書けます。ですが大概は ref として書かなくてもいいようコードを構成する方法があります。覚えて欲しいことは、effects と componentDidMount や他のライフサイクルメソッドのメンタルモデルは別であることです。なので、それぞれのライフサイクルメソッドの代用を探そうとすると余計に混乱してしまいます。効率的になるためには「エフェクトで考える」必要があり、そのメンタルモデルはライフサイクルイベントに反応することではなく props や state の変化を DOM にシンクロさせる、という方に近いです。
→
componentDidMount
をuseEffect
で再現する方法は?
🤔 Reactのクラスコンポーネントでは、componentDidMount
ライフサイクルメソッドは、コンポーネントがDOMにマウントされた直後に一度だけ呼び出されます。これをフックを使用する関数コンポーネントで再現する場合、useEffect
フックの第2引数に空の依存配列[]
を指定します。
クラスコンポーネントの例:
class ExampleComponent extends React.Component {
componentDidMount() {
console.log("Component did mount!");
}
render() {
return <div>Hello, world!</div>;
}
}
関数コンポーネントの例 (useEffectを使用):
function ExampleComponent() {
useEffect(() => {
console.log("Component did mount!");
}, []); // 空の依存配列を指定
return <div>Hello, world!</div>;
}
こちらのuseEffect
は、コンポーネントがマウントされた後に一度だけ実行されます。
ただし、このアプローチはcomponentDidMount
の完全な代わりというわけではありません。useEffect
は閉じ込められた変数、つまり宣言時点のprops
やstate
を"キャプチャ"します。これにより、エフェクトのコールバック内で初期のprops
やstate
を参照することができます。最新のprops
やstate
を参照する場合は、ref(useRef
)を使用するなどの追加の手段が必要となります。
また、useEffect
の考え方は、クラスコンポーネントのライフサイクルとは異なる新しいメンタルモデルを持っています。エフェクトは、コードがどのライフサイクルイベントで実行されるべきかではなく、どのprops
やstate
の変化に反応して実行されるべきかを中心に考えます。
この部分に関して、具体的な疑問や補足があればお知らせください。次のトピックに進む前に、この部分を理解していただきたいです。
キャプチャされた変数
useEffect
内で使用される変数は、そのエフェクトが定義されたレンダリング時の値を"キャプチャ"(つまり、その時点での変数の値を覚えておく)します。この挙動はクロージャとして知られるJavaScriptの特性に基づいています。
具体的な例を考えてみましょう。
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
console.log(count);
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
上記のCounter
コンポーネントでは、useEffect
内のsetInterval
が1秒ごとにcount
をログに出力します。しかし、useEffect
の依存配列が空なので、エフェクトはコンポーネントがマウントされたときに一度だけ実行されます。
この場合、setInterval
内のconsole.log(count)
は、エフェクトが定義された時点でのcount
の値(つまり、0
)を"キャプチャ"しています。そのため、ボタンをクリックしてcount
を増やしても、ログに出力されるのは常に0
となります。
最新のpropsやstateを参照する
上記の問題を解決し、最新のcount
の値を参照するためには、useRef
を使用します。
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
countRef.current = count;
useEffect(() => {
const interval = setInterval(() => {
console.log(countRef.current);
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
ここでは、countRef
というref
を作成して、そのcurrent
プロパティにcount
の最新の値を常に保持しています。このようにすることで、setInterval
内ではcountRef.current
を通じて最新のcount
の値を参照することができます。
この例を通して、useEffect
内で変数がキャプチャされる挙動と、最新のstateやpropsの値を参照する方法について理解していただけたでしょうか?
ref
を使わずに書く方法
エフェクトの依存配列にcount
を加えることで、count
が変更されるたびにエフェクトが再実行されるようにします。これにより、エフェクト内の関数が最新のcount
の値を参照することができます。
以下はその例です:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
console.log(count);
}, 1000);
return () => clearInterval(interval);
}, [count]); // countを依存配列に追加
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
この方法の問題点は、count
が変更されるたびにエフェクトが再実行され、新しいタイマーが設定される点です。そのため、実際のアプリケーションでこの方法を選択するかどうかは、具体的な要件や期待される動作によって変わる場合があります。
🤔 useEffect 内で正確に非同期処理を行う方法とは? [] ってなに?
この記事を参考にしてみると良いでしょう。最後まで読むように!この記事ほど長くはありません。[]は、エフェクトは React のデータフローに携わる値をなに一つ使用していないので、一度だけ実行しても良いということを示していてます。ですが値が実際にエフェクト内で使用されている場合はバグの根源ともなります。依存関係を解消して正しく値を省くには複数のテクニック(主に useReducer と useCallback )を用いる必要があります。
→
これ相当わかりやすいぞ。
非同期関数でデータを取得した場合の挙動例が大変参考になる
全然言語化できてないな
useRef を使うタイミング
useEffect を使うタイミングで主に使う。
↓
useRef の使用タイミング
・DOMを直接触って値を取得する
・レンダリングをする際に変更されてない値を保持する。レンダリングする際に描画されたものに関しては・・わかりませんすみません
↓
・可変オブジェクトの保持:これstateとの使い分けが全然わからないので教えて欲しいです
useEffectの使用タイミング
・画面の描画をする際、画面のライフサイクルが終わる場合
↓
コンポーネントが実行されたあとに副作用を実行するために利用される
コンポーネントのライフサイクルの特定のタイミングで実行される
そのタイミング:マウント、アップデート、アンマウント
依存関係がある場合には、その配列内の値が変更された場合に副作用が実行される
副作用(Side Effect)って何?
コンポーネントが外部の何かに作用すること
・ネットワークリクエスト
・外部データの変更
・タイマーやインターバル設定
・DOM操作
1. ネットワークリクエスト:
ネットワークリクエストは、通常、外部のサーバーからデータを取得(フェッチ)するために使用されます。これには、データを読み取るGETリクエストやデータを送信するPOSTリクエストなどが含まれます。しかし、この時点では、データは変更されていないかもしれません。あなたは単にデータを取得または送信しているだけです。
// 例: APIからデータをフェッチする
fetch('https://api.example.com/data')
.then(response => response.json())
.then(data => console.log(data));
2. 外部データの変更:
一方、外部データの変更は、データベースや外部APIなどのデータを変更するために行われます。これにはデータの更新、削除、または新しいデータの作成が含まれます。この操作は、データベースまたは外部システムの現在の状態を変更し、その結果他のユーザーに影響を与える可能性があります。
// 例: データベースに新しいデータを送信する
fetch('https://api.example.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(newData),
})
.then(response => response.json())
.then(data => console.log(data));
要するに、ネットワークリクエストはデータの取得や送信に焦点を当てているのに対し、外部データの変更はデータの実際の変更に焦点を当てています。また、外部データの変更はネットワークリクエストを通じて行われる可能性がありますが、その目的はデータの状態を変更することです。
useRef
の使用タイミングとstateの使い分けについて:
-
可変オブジェクトの保持:
-
useRef
は、可変オブジェクトの保持に使用されます。useRef
オブジェクトは.current
プロパティを持っており、このプロパティは変更されることがありますが、コンポーネントは再レンダリングされません。
-
const myRef = useRef(initialValue);
// myRef.currentの値を変更してもコンポーネントは再レンダリングされない
-
stateとの使い分け:
-
useState
は、コンポーネントの状態を管理し、その状態が変更されるたびにコンポーネントを再レンダリングします。これに対して、useRef
はコンポーネントの再レンダリングをトリガーしないため、状態の変更をトラッキングする必要がない場合や、状態の変更に対してコンポーネントを再レンダリングしたくない場合に便利です。
-
const [myState, setMyState] = useState(initialValue);
// myStateの値を変更するとコンポーネントが再レンダリングされる
useEffect
の使用タイミングについて:
-
コンポーネントのライフサイクルタイミングでの実行:
-
useEffect
は、コンポーネントがマウントされた後、アップデートされた後、および/またはアンマウントされる前に副作用を実行するために使用されます。
-
-
依存配列と副作用の実行タイミング:
-
useEffect
の第二引数として依存配列を提供することで、配列内の値が変更されたときにのみ副作用を実行することができます。依存配列が省略された場合、副作用はコンポーネントの各レンダリング後に実行されます。
-
useEffect(() => {
// 依存配列内の値が変更されたときに実行される副作用
}, [dependency]);
useState
とuseRef
の使い分けに関する具体的な例
// Filename: UseStateUseRefExample.js
import React, { useState, useRef, useEffect } from 'react';
function UseStateUseRefExample() {
const [count, setCount] = useState(0); // useStateを使用
const countRef = useRef(0); // useRefを使用
useEffect(() => {
// countの値が変更されるたびに、この副作用は実行されます
document.title = `You clicked ${count} times`;
// countRef.currentの値を更新しても、このコンポーネントは再レンダリングされません
countRef.current = count;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default UseStateUseRefExample;
この例では、useState
とuseRef
を同時に使用しています。useState
はcount
という名前の状態変数を作成し、setCount
関数を使用してその値を更新することができます。count
の値が変更されるたびに、コンポーネントは再レンダリングされます。
一方で、useRef
はcountRef
という名前の参照オブジェクトを作成します。このオブジェクトの.current
プロパティは、count
の値を保持するために使用されますが、countRef.current
の値を更新してもコンポーネントは再レンダリングされません。
useEffect
フック内で、count
の値が変更されるたびに、countRef.current
の値も更新されます。しかし、この更新によってコンポーネントの再レンダリングはトリガーされません。これが、useState
とuseRef
の主な違いの一例です。
コンポーネントのマウント と レンダリングの違い
簡単な例
-
レンダリング:
- これは、絵を描くようなものです。毎回状態やプロパティが変わるたびに、新しい絵を描きます。
- たとえば、天気アプリがあるとします。最初に晴れの絵を描きます。しかし、天気が変わって雨になったとき、新しい絵(雨の絵)を描き直します。この新しい絵を描くアクションがレンダリングです。
function WeatherApp() {
const [weather, setWeather] = useState('sunny'); // 初めは晴れ
return (
<div>
Today's weather is {weather}.
<button onClick={() => setWeather('rainy')}>Change to rainy</button> // ボタンを押すと雨に変わる
</div>
);
}
-
マウント:
- これは、最初の絵をフレームにはめ込むようなものです。一度だけ行われます。
- 例えば、天気アプリが初めて起動されるとき、最初の晴れの絵をフレームにはめ込んで壁に掛けます。このアクションがマウントです。
function WeatherApp() {
return <div>Today's weather is sunny.</div>;
}
ReactDOM.render(<WeatherApp />, document.getElementById('root')); // これがマウントです。最初の絵をフレームにはめ込む
普通の説明
-
レンダリング:
- レンダリングは、コンポーネントが仮想DOMに描画されるプロセスを指します。コンポーネントの
render
メソッドが呼び出されたり、関数コンポーネントが実行されたりすることでレンダリングが行われます。 - レンダリングは、プロパティや状態が変更されたときに、または親コンポーネントが再レンダリングされたときにトリガーされます。
- レンダリングは、コンポーネントが仮想DOMに描画されるプロセスを指します。コンポーネントの
function MyComponent() {
return <div>Hello, world!</div>;
}
// MyComponentがレンダリングされる
-
マウント:
- マウントは、コンポーネントが仮想DOMに初めて描画され、実際のDOMに追加されるプロセスを指します。
- コンポーネントは一度だけマウントされ、その後は再レンダリングされることになります。
function MyComponent() {
return <div>Hello, world!</div>;
}
// MyComponentがマウントされる
ReactDOM.render(<MyComponent />, document.getElementById('root'));
-
マウント(Mounting):
- お絵描きを始める前に、新しい白い紙を机の上に置くのが「マウント」です。
- 白い紙が置かれると、お絵描きが始まります。
class MyDrawing extends React.Component {
componentDidMount() {
console.log('New paper is on the table, ready to draw!');
}
render() {
return <div>Let's start drawing!</div>;
}
}
// 出力: "New paper is on the table, ready to draw!"
-
更新(Updating):
- お絵描きをしている途中で色を変えたり、新しい図形を描いたりするのが「更新」です。
- 色や図形を変えることで、お絵描きがどんどん進んでいきます。
class MyDrawing extends React.Component {
componentDidUpdate() {
console.log('Changed colors or added new shapes!');
}
render() {
return <div>Drawing with {this.props.color} color.</div>;
}
}
// 例えば、親コンポーネントから新しい色を受け取った時に出力: "Changed colors or added new shapes!"
-
アンマウント(Unmounting):
- お絵描きが終わって、紙を机から片付けるのが「アンマウント」です。
- 紙を片付けると、お絵描きは終了します。
class MyDrawing extends React.Component {
componentWillUnmount() {
console.log('Finished drawing and cleaned up the paper!');
}
render() {
return <div>Finished drawing!</div>;
}
}
// 例えば、親コンポーネントが子コンポーネントをDOMから削除した時に出力: "Finished drawing and cleaned up the paper!"
まReact コンポーネントのライフサイクルを理解するのは重要です。以下の3つのフェーズに分けて説明します
-
マウント(Mounting):
- コンポーネントが初めて画面に表示される時のことを指します。
- この時に、コンポーネントはDOMに挿入され、レンダリングが行われます。
class MyComponent extends React.Component {
componentDidMount() {
console.log('Component is mounted!');
}
render() {
return <div>Hello, world!</div>;
}
}
// 出力: "Component is mounted!"
-
更新(Updating):
- コンポーネントのプロパティや状態が変更された時に起こります。
- これにより、コンポーネントが再レンダリングされ、画面が更新されます。
class MyComponent extends React.Component {
componentDidUpdate() {
console.log('Component is updated!');
}
render() {
return <div>{this.props.greeting}</div>;
}
}
// 例えば、親コンポーネントから新しいプロパティを受け取った時に出力: "Component is updated!"
-
アンマウント(Unmounting):
- コンポーネントがDOMから削除される時のことを指します。
- 通常は、このフェーズでクリーンアップタスクを実行します。
class MyComponent extends React.Component {
componentWillUnmount() {
console.log('Component is unmounted!');
}
render() {
return <div>Goodbye, world!</div>;
}
}
// 例えば、親コンポーネントが子コンポーネントをDOMから削除した時に出力: "Component is unmounted!"
これらのフェーズは、コンポーネントがどのように作成、更新、そして破棄されるかを理解するのに役立ちます。また、これらのフェーズを理解することで、コードのどの部分がどのタイミングで実行されるのかを理解しやすくなります。
そもそもの
const [age, setAge] = useState(42);
これって何?
このコードはReactのuseState
というHookを使っています。👍
// ファイル名: ExampleComponent.tsx
import React, { useState } from 'react';
const ExampleComponent: React.FC = () => {
// ここが今注目している部分です!
const [age, setAge] = useState(42);
return (
<div>
<p>私の年齢は {age} 歳です。</p>
<button onClick={() => setAge(age + 1)}>誕生日を祝う</button>
</div>
);
}
export default ExampleComponent;
-
useState の呼び出し:
useState
は、状態を持たせたいコンポーネントで呼び出されます。この例では、age
という名前の状態を作成して、初期値を42
に設定しています。 -
変数の定義:
useState
は、2つの値を含む配列を返します。-
age
: これは状態の現在の値です。この例では、age
は42
で初期化されます。 -
setAge
: これは状態を更新する関数です。この関数を使って、age
の値を変更することができます。
-
-
状態の利用:
age
は、コンポーネント内で利用できます。この例では、<p>
タグ内でage
を表示しています。 -
状態の更新:
setAge
関数を使って、状態を更新することができます。この例では、<button>
がクリックされると、setAge
関数が呼び出されて、age
の値が1増えます。
このように、useState
を使うことでコンポーネントに状態を持たせ、その状態を読み取ったり更新したりすることができます。そして、状態が変更されると、コンポーネントは自動的に再レンダリングされて、新しい状態の値が反映されます。🎉
-
Hook:
- React の Hook は、関数コンポーネントに状態やライフサイクル特性を追加するための関数です。
- 一般的な Hooks には
useState
,useEffect
,useContext
,useReducer
,useRef
などがあります。 - これらは関数コンポーネント内で呼び出され、コンポーネントに状態や副作用を持たせることができます。
-
副作用 (Side Effects):
- コンポーネントに影響を与える外部の操作のことを指します。
- 副作用には、ネットワークリクエスト、タイマーの設定、DOM の操作、外部データの読み書きなどが含まれます。
具体的に言うと、useEffect
はコンポーネントのライフサイクルの特定の時点で副作用を実行するために使用されるHookです。たとえば、コンポーネントが画面に表示されたとき(マウント)、更新されたとき、または画面から消えたとき(アンマウント)に何かを実行する必要がある場合に、useEffect
を使います。
簡単に言えば、useState
は状態を管理するために、useEffect
は副作用を管理するために使われます。それぞれがコンポーネントの異なる側面を処理するために設計されています。😊