概要:このチュートリアルでは、ReactのuseState()
フックと、コンポーネントをより堅牢にするために効果的に使用する方法について学びます。
React useState()フック入門
Reactでは、stateは時間とともに変化するデータの一部です。コンポーネントにstate変数を追加するには、useState()
フックを使用します。
まず、useState
をreact
ライブラリの関数としてインポートします
import { useState } from 'react';
Code language: JavaScript (javascript)
次に、コンポーネント内でuseState()
関数を使用します
const [state, setState] = useState(initialValue);
Code language: JavaScript (javascript)
useState()
関数は、正確に2つの要素を持つ配列を返します
- 現在のstate値を保持する変数(
state
)。最初のレンダーでは、state
変数はinitialSate
の値を持っています。 state
変数の現在の値を新しい値に変更し、再レンダーをトリガーする関数(setState
)。
慣例として、state変数を更新する関数名は、動詞set
で始まり、その後に変数名が続きます。
たとえば、state変数がcount
の場合、count
変数を更新する関数はsetCount
です。
const [count, setCount] = useState(0);
Code language: JavaScript (javascript)
useState()
関数は、任意の有効なデータ型(文字列、数値、配列、オブジェクトなど)の初期値を受け入れます。
stateの更新
state変数を更新するためにsetState()
関数を呼び出すには、2つの方法があります
- 直接更新。
- 関数型更新。
直接更新では、前のstateを知る必要はなく、新しい値を直接setState()
関数に渡します。例:
setCount(1);
Code language: JavaScript (javascript)
この例では、setCount()
関数は、現在のstate値に関係なく、新しいcount値を1に設定します。
関数型更新では、前のstateを入力引数として受け取り、新しいstateを返す関数を渡します。例:
setCount(prevCount => prevCount + 1);
Code language: JavaScript (javascript)
この例では、setCount()
関数は、前のcount
値に基づいてcount
変数を1ずつインクリメントします。
useStateフックの例
Counter
コンポーネントでuseState
フックを使用する基本的な例を見てみましょう
import React, { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1);
};
return (
<div>
<h1>Current count: {count}</h1>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
Code language: JavaScript (javascript)
この例では、useState()
フックを使用して、初期値が0のcount
変数を宣言しています
const [count, setCount] = useState(0);
Code language: JavaScript (javascript)
Increment
ボタンがクリックされると、increment()
関数が呼び出され、setCount()
関数を使用してcount
変数を更新します。
count
変数が変化するため、Reactはコンポーネントの再レンダーをトリガーし、新しいcount値を表示します。
stateとコンポーネントの再レンダー
Reactでは、setState()
関数を呼び出すことによってstateが変化するたびに、コンポーネントが再レンダーされます。コンポーネントの再レンダーを監視するには、次のようにuseEffect()
フックを使用できます
import React, { useState, useEffect } from 'react';
const Counter = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('Component rendered or rerendered');
});
const increment = () => {
setCount(count + 1);
};
return (
<div>
<h1>Current count: {count}</h1>
<button onClick={increment}>Increment</button>
</div>
);
};
export default Counter;
Code language: JavaScript (javascript)
Increment
ボタンがクリックされるたびに、count
変数が1ずつインクリメントされ、コンポーネントの再レンダーがトリガーされます。コンソールウィンドウには、最初のレンダー時と再レンダーごとに「Component rendered or rerendered」というメッセージが表示されます
新しいstate値が前の値と同じ場合、Reactはコンポーネントを再レンダーしません。
たとえば、setCount()
関数を呼び出してもcount
変数を変更しない場合、Reactは再レンダーをトリガーしません
const increment = () => {
setCount(count);
};
Code language: JavaScript (javascript)
コンソールウィンドウには、コンポーネントが最初にレンダーされるときに「component rendered or rendered」というメッセージが1つ表示され、Increment
ボタンがクリックされたときに同じメッセージは表示されません。
更新直後にcount
値をログに記録できます。ただし、ログには更新前のcount値が表示されます
const increment = () => {
// update the count
setCount(count + 1);
// show the count before the update
console.log(count);
};
Code language: JavaScript (javascript)
increment()
関数では、count
値を1ずつインクリメントします
setCount(count + 1);
Code language: JavaScript (javascript)
Reactは、コンポーネントをすぐに再レンダーしません。代わりに、更新をスケジュールします。したがって、count
変数の値は、更新が有効になる前の現在のcount値です。
Reactは、パフォーマンス上の理由から、複数のstate更新をバッチ処理することに注意することが重要です。そのため、新しいstateがコンソールにすぐに表示されることはありません。
たとえば、setCount()
関数を複数回呼び出すことができ、Reactはこれらの更新をバッチ処理して、1回の再レンダーで済みます
const increment = () => {
// update the count multiple times
setCount(count + 1);
setCount(count + 2);
setCount(count + 3);
// show the count before the update
console.log(count);
};
Code language: JavaScript (javascript)
この例では、Reactは3つのstate更新をスケジュールし、その結果1回の再レンダーが発生します。コンソールウィンドウには、0、3、6、…のようなcount値が表示されます
その理由は、setCount()
が現在のcount
値に基づいて3回呼び出されるためです
- 最初の更新:
count + 1
– countを1増やします。 - 2回目の更新:
count + 1
– 現在のcount(更新されたcountではない)を1増やします。 - 3回目の更新:
count + 3
– 現在のcount(最新のcountではない)を1増やします。
現在のcountが0の場合、3回の呼び出しでcountは3に設定されます。現在のcountが3の場合、3回の呼び出しでcountは6に設定されます。
前のstateに基づいてstateを更新するには、前のstateを引数として受け取る関数型更新を使用する必要があります。例:
const increment = () => {
// update the count multiple times
setCount((prevCount) => prevCount + 1);
setCount((prevCount) => prevCount + 2);
setCount((prevCount) => prevCount + 3);
// show the count before the update
console.log(count);
};
Code language: JavaScript (javascript)
この例では
- 最初の更新:
prevCount
+ 1 – countを1増やします。 - 2回目の更新:
prevCount
+ 2 – 更新されたcountを2増やします。 - 3回目の更新:
prevCount
+ 3 – 最新のcountを3増やします。
Reactはこれらの更新をバッチ処理し、最終的なstateをprevCount + 6
として処理します。そのため、コンソールウィンドウには0、6、12、18などが表示されます。
複雑なstateの処理
コンポーネントに配列またはオブジェクトのstateがある場合は、不変性を確保するために慎重に処理する必要があります。
配列のstateの更新
配列のstateを処理する場合、配列を直接変更することを避けるために、更新時に変更された項目を含む元の配列のコピーを作成する必要があります。
次のFruitList
コンポーネントを見てみましょう
import React, { useState } from 'react';
const commonFruits = [
'🍎 apple',
'🍌 banana',
'🍒 cherry',
'🍈 date',
'🥭 mango',
'🍇 grape',
'🥝 honeydew',
'🥥 coconut',
'🍋 kiwi',
'🍊 lemon',
'🍉 warter melon',
'🍊 orange',
'🍑 peach',
];
const FruitList = () => {
const [fruits, setFruits] = useState([]);
const addFruit = () => {
const newFruit =
commonFruits[Math.floor(Math.random() * commonFruits.length)];
setFruits([...fruits, newFruit]);
};
return (
<div>
<h1>Fruit List</h1>
<ul>
{fruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
<button onClick={addFruit}>Add Fruit</button>
</div>
);
};
export default FruitList;
Code language: JavaScript (javascript)
FruitList
コンポーネントには、配列であるfruits stateがあります。useState
フックは、それを空の配列に初期化します
const [fruits, setFruits] = useState([]);
Code language: JavaScript (javascript)
fruits配列に新しい要素を追加するには、既存のfruitsをコピーし、次の構文を使用して新しいfruitsを追加して、新しい配列を作成します
setFruits([...fruits, newFruit]);
Code language: JavaScript (javascript)
配列に項目を追加するためにpush()
メソッドを使用すると、配列の項目数が変わっても、同じ配列参照を参照することに注意してください。この場合、Reactはコンポーネントの再レンダーをトリガーしません
// DON'T DO THIS
fruits.push(newFruit);
setFruits(fruits);
Code language: JavaScript (javascript)
オブジェクトのstateの更新
配列のstateと同様に、オブジェクトのstateを更新するときは、既存のオブジェクトを変更するのではなく、新しいオブジェクトを作成するようにする必要があります。例:
import React, { useState } from 'react';
const Person = () => {
const [person, setPerson] = useState({
name: 'John',
age: 20,
});
const handleIncrement = () => {
setPerson({ ...person, age: person.age + 1 });
};
return (
<div>
<p>Name: {person.name}</p>
<p>Age: {person.age}</p>
<button onClick={handleIncrement}>Increment Age</button>
</div>
);
};
export default Person;
Code language: JavaScript (javascript)
この例では、元のオブジェクトの既存のプロパティをコピーし、age
プロパティを更新して、新しいPerson
オブジェクトを作成しています
setPerson({ ...person, age: person.age + 1 });
stateの遅延初期化
stateの初期化にコストのかかる計算が必要になる場合があります。パフォーマンスを向上させるために、Reactでは、関数をuseState()
に渡すことで、stateを遅延的に初期化できます。useState()
は、最初のレンダー中に1回のみこの関数を呼び出します。
次のTheme
コンポーネントでは、useState()
フックはコンポーネントが再レンダーされるたびにgetTheme()
関数を呼び出しますが、これは必要ありません
import React, { useState } from 'react';
function getTheme() {
console.log('Getting the theme from the local storage');
return localStorage.getItem('theme') || 'light';
}
const Theme = () => {
const [theme, setTheme] = useState(getTheme());
const handleClick = () => {
console.log('Changing the theme');
const newTheme = theme === 'light' ? 'dark' : 'light';
setTheme(newTheme);
localStorage.setItem('theme', newTheme);
};
return (
<div>
<p>Current Theme: {theme}</p>
<button onClick={handleClick}>Swich Theme</button>
</div>
);
};
export default Theme;
Code language: JavaScript (javascript)
最初の再レンダー中にgetTheme()
関数を1回のみ呼び出すようにuseState
フックに指示するには、次のように関数を渡すことができます
const [theme, setTheme] = useState(() => getTheme());
Code language: JavaScript (javascript)
この遅延state初期化手法は、すべてのレンダーで繰り返したくない計算を必要とするstateを初期化する必要がある場合、または初期stateにlocalStorage
などの外部ソースからのデータの取得が含まれる場合に使用できます。
概要
useState()
フックを呼び出して、コンポーネントにstate変数を追加します。useState()
関数は、state変数(state
)とstateを更新するための関数(setState
)の2つの項目の配列を返します。setState
関数を使用して、常にstate変数を更新します。state
が変更された場合、setState
関数はコンポーネントの再レンダーをトリガーします。- 複雑なstateを更新する場合は、配列またはオブジェクトを直接変更するのではなく、新しいコピーを返します。
- 初期stateの計算にコストがかかる場合は、遅延初期化を使用します。