コンポーネントとフックは純粋でなければなりません

純粋関数は計算のみを実行し、それ以上のことはしません。コードの理解とデバッグが容易になり、Reactがコンポーネントとフックを正しく自動的に最適化できるようになります。

注記

このリファレンスページでは高度なトピックを扱っており、「コンポーネントを純粋に保つ」ページで説明されている概念を理解している必要があります。

純粋性が重要な理由

ReactをReactたらしめている重要な概念の1つは、_純粋性_です。純粋なコンポーネントまたはフックとは、次のものです。

レンダリングを純粋に保つことで、Reactはユーザーにとってどの更新が最も重要かを判断し、優先順位を付けることができます。これは、レンダリングの純粋性のおかげで可能になります。コンポーネントにはレンダリング中に副作用がないため、Reactは重要度の低いコンポーネントのレンダリングを一時停止し、必要な場合にのみ後でレンダリングすることができます。

具体的には、これはReactがユーザーに快適なユーザーエクスペリエンスを提供できるように、レンダリングロジックを複数回実行できることを意味します。ただし、コンポーネントに追跡されていない副作用(レンダリング中にグローバル変数の値を変更するなど)がある場合、Reactがレンダリングコードを再度実行すると、副作用が意図しない方法でトリガーされます。これは多くの場合、予期しないバグを引き起こし、アプリのユーザーエクスペリエンスを低下させる可能性があります。 この例は、「コンポーネントを純粋に保つ」ページに掲載されています。

Reactはどのようにコードを実行しますか?

Reactは宣言型です。Reactに_何を_レンダリングするかを指示すると、Reactはユーザーに_どのように_表示するのが最適かを判断します。そのため、Reactにはコードを実行するフェーズがいくつかあります。Reactをうまく使うために、これらのフェーズすべてを知る必要はありません。ただし、大まかに言って、_レンダリング_で実行されるコードと、レンダリング外で実行されるコードについて知っておく必要があります。

_レンダリング_とは、UIの次のバージョンがどのように見えるかを計算することです。レンダリング後、エフェクトが_フラッシュ_され(つまり、なくなるまで実行され)、エフェクトがレイアウトに影響を与える場合は計算が更新される可能性があります。Reactはこの新しい計算を取得し、UIの以前のバージョンを作成するために使用された計算と比較し、最新のバージョンに追いつくためにDOM(ユーザーが実際に見ているもの)に必要な最小限の変更のみを_コミット_します。

詳細

コードがレンダリングで実行されるかどうかを確認する方法

コードがレンダリング中に実行されるかどうかを判断するための簡単なヒューリスティックは、コードがどこにあるかを調べることです。以下の例のようにトップレベルに記述されている場合、レンダリング中に実行される可能性が高くなります。

function Dropdown() {
const selectedItems = new Set(); // created during render
// ...
}

イベントハンドラとエフェクトはレンダリングでは実行されません

function Dropdown() {
const selectedItems = new Set();
const onSelect = (item) => {
// this code is in an event handler, so it's only run when the user triggers this
selectedItems.add(item);
}
}
function Dropdown() {
const selectedItems = new Set();
useEffect(() => {
// this code is inside of an Effect, so it only runs after rendering
logForAnalytics(selectedItems);
}, [selectedItems]);
}

コンポーネントとフックは冪等でなければなりません

コンポーネントは、その入力(props、state、context)に対して常に同じ出力を返さなければなりません。これは*冪等性*として知られています。冪等性は、関数型プログラミングでよく使われる用語です。これは、同じ入力でコードを実行するたびに*常に同じ結果が得られる*という考え方を指します。常に同じ結果が得られる

これは、このルールを維持するためには、*レンダリング中に実行されるすべての*コードも冪等でなければならないことを意味します。たとえば、次のコード行は冪等ではありません(したがって、コンポーネントも冪等ではありません)。

function Clock() {
const time = new Date(); // 🔴 Bad: always returns a different result!
return <span>{time.toLocaleString()}</span>
}

new Date() は、常に現在の日付を返し、呼び出されるたびに結果が変わるため、冪等ではありません。上記のコンポーネントをレンダリングすると、画面に表示される時間は、コンポーネントがレンダリングされた時間に固定されたままになります。同様に、Math.random() のような関数も、入力が同じであっても呼び出されるたびに異なる結果を返すため、冪等ではありません。

これは、new Date() のような冪等でない関数を*まったく*使用すべきではないという意味ではありません。*レンダリング中*の使用を避けるべきです。この場合、Effect を使用して、最新の日付をこのコンポーネントに*同期*させることができます。

import { useState, useEffect } from 'react';

function useTime() {
  // 1. Keep track of the current date's state. `useState` receives an initializer function as its
  //    initial state. It only runs once when the hook is called, so only the current date at the
  //    time the hook is called is set first.
  const [time, setTime] = useState(() => new Date());

  useEffect(() => {
    // 2. Update the current date every second using `setInterval`.
    const id = setInterval(() => {
      setTime(new Date()); // ✅ Good: non-idempotent code no longer runs in render
    }, 1000);
    // 3. Return a cleanup function so we don't leak the `setInterval` timer.
    return () => clearInterval(id);
  }, []);

  return time;
}

export default function Clock() {
  const time = useTime();
  return <span>{time.toLocaleString()}</span>;
}

冪等でない new Date() 呼び出しを Effect でラップすることにより、その計算は*レンダリング外*に移動します。

外部状態を React と同期させる必要がない場合は、ユーザー操作への応答としてのみ更新する必要がある場合は、イベントハンドラの使用を検討することもできます。


副作用はレンダリング外で実行する必要があります (以下略、SVG省略)

副作用は、React が可能な限り最高のユーザーエクスペリエンスを作成するためにコンポーネントを複数回レンダリングできるため、*レンダリング中*に実行しないでください。

注記

副作用とは、Effect よりも広い用語です。Effect は具体的には useEffect でラップされたコードを指しますが、副作用とは、呼び出し元に値を返すという主な結果以外の、観察可能な効果を持つコードの一般的な用語です。

副作用は通常、イベントハンドラまたは Effect 内に記述されます。ただし、レンダリング中は決して記述しません。

レンダリングは純粋に保つ必要がありますが、アプリが画面に何かを表示するなど、興味深いことを行うためには、ある時点で副作用が必要です。このルールの重要な点は、React がコンポーネントを複数回レンダリングできるため、副作用を*レンダリング中*に実行すべきではないということです。ほとんどの場合、副作用を処理するには、イベントハンドラを使用します。イベントハンドラを使用すると、React に対して、このコードはレンダリング中に実行する必要がないことを明示的に伝え、レンダリングを純粋に保ちます。すべてのオプションを使い果たした場合(そして最後の手段としてのみ)、useEffect を使用して副作用を処理することもできます。.

いつミューテーションを行ってもよいですか? (以下略、SVG省略)

ローカルミューテーション (以下略、SVG省略)

副作用の一般的な例の 1 つは、ミューテーションです。JavaScript では、これは非プリミティブ値の値を変更することを指します。一般に、ミューテーションは React では慣用的ではありませんが、*ローカル*ミューテーションはまったく問題ありません。

function FriendList({ friends }) {
const items = []; // ✅ Good: locally created
for (let i = 0; i < friends.length; i++) {
const friend = friends[i];
items.push(
<Friend key={friend.id} friend={friend} />
); // ✅ Good: local mutation is okay
}
return <section>{items}</section>;
}

ローカルミューテーションを避けるためにコードを歪める必要はありません。Array.map は簡潔にするためにここで使用することもできますが、ローカル配列を作成してから*レンダリング中*にアイテムをプッシュしても問題ありません。

items をミューテーションしているように見えますが、重要な点は、このコードが*ローカルで*のみミューテーションを行っていることです。コンポーネントが再びレンダリングされるときに、ミューテーションは「記憶」されません。言い換えれば、items は、コンポーネントが存在する限り存在します。items<FriendList /> がレンダリングされるたびに常に*再作成*されるため、コンポーネントは常に同じ結果を返します。.

一方、items がコンポーネントの外部で作成された場合、以前の値を保持し、変更を記憶します。

const items = []; // 🔴 Bad: created outside of the component
function FriendList({ friends }) {
for (let i = 0; i < friends.length; i++) {
const friend = friends[i];
items.push(
<Friend key={friend.id} friend={friend} />
); // 🔴 Bad: mutates a value created outside of render
}
return <section>{items}</section>;
}

<FriendList /> が再び実行されると、コンポーネントが実行されるたびに friendsitems に追加し続け、結果が複数回重複します。このバージョンの <FriendList /> は、*レンダリング中*に観察可能な副作用があり、**ルールに違反しています**。

遅延初期化 (以下略、SVG省略)

遅延初期化は、完全に「純粋」ではないにもかかわらず、問題ありません。

function ExpenseForm() {
SuperCalculator.initializeIfNotReady(); // ✅ Good: if it doesn't affect other components
// Continue rendering...
}

DOMの変更

ユーザーに直接見える副作用は、Reactコンポーネントのレンダリングロジックでは許可されていません。つまり、コンポーネント関数を呼び出すだけでは、画面に変化が生じるべきではありません。

function ProductDetailPage({ product }) {
document.title = product.title; // 🔴 Bad: Changes the DOM
}

レンダリングの外で`document.title`を更新するという望ましい結果を得るための1つの方法は、コンポーネントを`document`と同期させることです。

コンポーネントを複数回呼び出すことが安全であり、他のコンポーネントのレンダリングに影響を与えない限り、Reactはそれが厳密な関数型プログラミングの観点から100%純粋であるかどうかを気にしません。 コンポーネントが冪等であることの方が重要です。


PropsとStateはイミュータブルです (以下略、SVGは省略)

コンポーネントのPropsとStateはイミュータブルなスナップショットです。それらを直接変更しないでください。代わりに、新しいPropsを渡し、`useState`からセッター関数を使用します。

PropsとStateの値は、レンダリング後に更新されるスナップショットと考えることができます。このため、PropsまたはState変数を直接変更するのではなく、新しいPropsを渡すか、提供されたセッター関数を使用して、次回コンポーネントがレンダリングされる際にStateを更新する必要があることをReactに伝えます。

Propsを変更しないでください (以下略、SVGは省略)

Propsはイミュータブルです。変更すると、アプリケーションは一貫性のない出力を生成し、状況によっては動作したりしなかったりする可能性があるため、デバッグが困難になる可能性があります。

function Post({ item }) {
item.url = new Url(item.url, base); // 🔴 Bad: never mutate props directly
return <Link url={item.url}>{item.title}</Link>;
}
function Post({ item }) {
const url = new Url(item.url, base); // ✅ Good: make a copy instead
return <Link url={url}>{item.title}</Link>;
}

Stateを変更しないでください (以下略、SVGは省略)

`useState`は、State変数と、そのStateを更新するためのセッターを返します。

const [stateVariable, setter] = useState(0);

State変数をその場で更新するのではなく、`useState`によって返されるセッター関数を使用して更新する必要があります。State変数の値を変更しても、コンポーネントは更新されず、ユーザーには古いUIが表示されたままになります。セッター関数を使用すると、Stateが変更されたこと、UIを更新するために再レンダリングをキューに入れる必要があることがReactに通知されます。

function Counter() {
const [count, setCount] = useState(0);

function handleClick() {
count = count + 1; // 🔴 Bad: never mutate state directly
}

return (
<button onClick={handleClick}>
You pressed me {count} times
</button>
);
}
function Counter() {
const [count, setCount] = useState(0);

function handleClick() {
setCount(count + 1); // ✅ Good: use the setter function returned by useState
}

return (
<button onClick={handleClick}>
You pressed me {count} times
</button>
);
}

フックの戻り値と引数はイミュータブルです (以下略、SVGは省略)

値がフックに渡されたら、それらを変更しないでください。JSXのPropsと同様に、値はフックに渡されるとイミュータブルになります。

function useIconStyle(icon) {
const theme = useContext(ThemeContext);
if (icon.enabled) {
icon.className = computeStyle(icon, theme); // 🔴 Bad: never mutate hook arguments directly
}
return icon;
}
function useIconStyle(icon) {
const theme = useContext(ThemeContext);
const newIcon = { ...icon }; // ✅ Good: make a copy instead
if (icon.enabled) {
newIcon.className = computeStyle(icon, theme);
}
return newIcon;
}

Reactの重要な原則の1つは、*ローカル推論*です。これは、コンポーネントまたはフックのコードを個別に見て、その動作を理解する能力です。フックは、呼び出されたときに「ブラックボックス」として扱う必要があります。たとえば、カスタムフックは、引数を依存関係として使用して、内部の値をメモ化している可能性があります。

function useIconStyle(icon) {
const theme = useContext(ThemeContext);

return useMemo(() => {
const newIcon = { ...icon };
if (icon.enabled) {
newIcon.className = computeStyle(icon, theme);
}
return newIcon;
}, [icon, theme]);
}

フックの引数を変更すると、カスタムフックのメモ化が正しくなくなるため、変更しないことが重要です。

style = useIconStyle(icon); // `style` is memoized based on `icon`
icon.enabled = false; // Bad: 🔴 never mutate hook arguments directly
style = useIconStyle(icon); // previously memoized result is returned
style = useIconStyle(icon); // `style` is memoized based on `icon`
icon = { ...icon, enabled: false }; // Good: ✅ make a copy instead
style = useIconStyle(icon); // new value of `style` is calculated

同様に、フックの戻り値はメモ化されている可能性があるため、変更しないことが重要です。


JSXに渡された後、値はイミュータブルになります (以下略、SVGは省略)

JSXで使用された後、値を変更しないでください。変更はJSXが作成される前に行ってください。

式でJSXを使用する場合、Reactはコンポーネントのレンダリングが完了する前にJSXを積極的に評価することがあります。これは、JSXに渡された後に値を変更すると、Reactがコンポーネントの出力を更新する必要があることを認識しないため、古いUIにつながる可能性があることを意味します。

function Page({ colour }) {
const styles = { colour, size: "large" };
const header = <Header styles={styles} />;
styles.size = "small"; // 🔴 Bad: styles was already used in the JSX above
const footer = <Footer styles={styles} />;
return (
<>
{header}
<Content />
{footer}
</>
);
}
function Page({ colour }) {
const headerStyles = { colour, size: "large" };
const header = <Header styles={headerStyles} />;
const footerStyles = { colour, size: "small" }; // ✅ Good: we created a new value
const footer = <Footer styles={footerStyles} />;
return (
<>
{header}
<Content />
{footer}
</>
);
}