Skip to content

Latest commit

 

History

History
462 lines (348 loc) · 18.8 KB

File metadata and controls

462 lines (348 loc) · 18.8 KB
title use

useプロミス (Promise)コンテクストなどのリソースから値を読み取るための React API です。

const value = use(resource);

リファレンス {/reference/}

use(resource) {/use/}

コンポーネント内で use を呼び出し、プロミスコンテクストなどのリソースから値を読み取ります。

import { use } from 'react';

function MessageComponent({ messagePromise }) {
  const message = use(messagePromise);
  const theme = use(ThemeContext);
  // ...

React フックとは異なり、useif のようなループや条件文内でも呼び出すことができます。ただし React フックと同様に、use を呼び出す関数はコンポーネントまたはフック内でなければなりません。

プロミスを引数にして呼び出した場合、use API は Suspenseエラーバウンダリ (Error Boundary) と協調して動作します。use を呼び出すコンポーネントは、use に渡されたプロミスが保留中 (pending) である間、サスペンド (suspend) します。use を呼び出すコンポーネントがサスペンスバウンダリでラップされている場合、フォールバックが表示されます。プロミスが解決 (resolve) された時点で、サスペンスフォールバックは、use API から返されたデータを使用してレンダーされたコンポーネントの内容に置き換わります。use に渡されたプロミスが拒否 (reject) されると、最も近いエラーバウンダリのフォールバックが表示されます。

さらに例を見る

引数 {/parameters/}

返り値 {/returns/}

use API は、プロミスの解決された値やコンテクストなど、リソースから読み取った値を返します。

注意点 {/caveats/}

  • use API は、コンポーネントまたは他のフック内で呼び出す必要があります。
  • サーバコンポーネントでデータをフェッチする際は、use よりも asyncawait を優先して使用してください。asyncawaitawait が呼び出された地点からレンダーを再開しますが、use はデータが解決した後にコンポーネントを最初からレンダーします。
  • クライアントコンポーネントでプロミスを作成するよりも、なるべくサーバコンポーネントでプロミスを作成してそれをクライアントコンポーネントに渡すようにしてください。クライアントコンポーネントで作成されたプロミスは、レンダーごとに再作成されます。サーバコンポーネントからクライアントコンポーネントに渡されたプロミスは、再レンダー間で不変です。こちらの例を参照してください

使用法 {/usage/}

use でコンテクストを読み取る {/reading-context-with-use/}

コンテクストuse に渡された場合、useContext と同様に動作します。useContext はコンポーネントのトップレベルで呼び出す必要がありますが、useiffor などの条件式の中でも呼び出すことができます。use はより柔軟であるため、useContext よりも優先的に使用してください。

import { use } from 'react';

function Button() {
  const theme = use(ThemeContext);
  // ...

use は、渡したコンテクストの値を返します。コンテクストの値を決定するために、React はコンポーネントツリーを上方向に検索し、当該コンテクストに対応する最も近いコンテクストプロバイダ (context provider) を見つけます。

Button にコンテクストを渡すには、それまたはその親コンポーネントのいずれかを、対応するコンテクストプロバイダでラップします。

function MyPage() {
  return (
    <ThemeContext value="dark">
      <Form />
    </ThemeContext>
  );
}

function Form() {
  // ... renders buttons inside ...
}

プロバイダと Button の間に何層のコンポーネントがあっても問題ありません。Form の内部のどこかButtonuse(ThemeContext) を呼び出すと、値として "dark" を受け取ることになります。

useContext とは異なり、useif などの条件式やループの中で呼び出すことができます。

function HorizontalRule({ show }) {
  if (show) {
    const theme = use(ThemeContext);
    return <hr className={theme} />;
  }
  return false;
}

useif 文の中から呼び出さているため、条件付きでコンテクストから値を読み取ることができます。

useContext と同様に、use(context) は常にそれを呼び出しているコンポーネントの上側にある最も近いコンテクストプロバイダを探します。上方向に検索するため、use(context) を呼び出しているコンポーネント自体にあるコンテクストプロバイダは考慮されません

import { createContext, use } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
  return (
    <ThemeContext value="dark">
      <Form />
    </ThemeContext>
  )
}

function Form() {
  return (
    <Panel title="Welcome">
      <Button show={true}>Sign up</Button>
      <Button show={false}>Log in</Button>
    </Panel>
  );
}

function Panel({ title, children }) {
  const theme = use(ThemeContext);
  const className = 'panel-' + theme;
  return (
    <section className={className}>
      <h1>{title}</h1>
      {children}
    </section>
  )
}

function Button({ show, children }) {
  if (show) {
    const theme = use(ThemeContext);
    const className = 'button-' + theme;
    return (
      <button className={className}>
        {children}
      </button>
    );
  }
  return false
}
.panel-light,
.panel-dark {
  border: 1px solid black;
  border-radius: 4px;
  padding: 20px;
}
.panel-light {
  color: #222;
  background: #fff;
}

.panel-dark {
  color: #fff;
  background: rgb(23, 32, 42);
}

.button-light,
.button-dark {
  border: 1px solid #777;
  padding: 5px;
  margin-right: 10px;
  margin-top: 10px;
}

.button-dark {
  background: #222;
  color: #fff;
}

.button-light {
  background: #fff;
  color: #222;
}

サーバからクライアントへのデータストリーミング {/streaming-data-from-server-to-client/}

サーバコンポーネントからクライアントコンポーネント に props としてプロミスを渡すことで、サーバからクライアントにデータをストリーミングすることができます。

import { fetchMessage } from './lib.js';
import { Message } from './message.js';

export default function App() {
  const messagePromise = fetchMessage();
  return (
    <Suspense fallback={<p>waiting for message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

クライアントコンポーネント は、受け取ったプロミス を use API に渡します。これによりクライアントコンポーネントは、サーバコンポーネントが最初に作成したプロミスから値を読み取ることができます。

// message.js
'use client';

import { use } from 'react';

export function Message({ messagePromise }) {
  const messageContent = use(messagePromise);
  return <p>Here is the message: {messageContent}</p>;
}

MessageSuspense でラップされているため、プロミスが解決されるまでフォールバックが表示されます。プロミスが解決されると、その値が use API によって読み取られ、Message コンポーネントがサスペンスフォールバックを置き換えます。

"use client";

import { use, Suspense } from "react";

function Message({ messagePromise }) {
  const messageContent = use(messagePromise);
  return <p>Here is the message: {messageContent}</p>;
}

export function MessageContainer({ messagePromise }) {
  return (
    <Suspense fallback={<p>⌛Downloading message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}
import { useState } from "react";
import { MessageContainer } from "./message.js";

function fetchMessage() {
  return new Promise((resolve) => setTimeout(resolve, 1000, "⚛️"));
}

export default function App() {
  const [messagePromise, setMessagePromise] = useState(null);
  const [show, setShow] = useState(false);
  function download() {
    setMessagePromise(fetchMessage());
    setShow(true);
  }

  if (show) {
    return <MessageContainer messagePromise={messagePromise} />;
  } else {
    return <button onClick={download}>Download message</button>;
  }
}
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

// TODO: update this example to use
// the Codesandbox Server Component
// demo environment once it is created
import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

サーバコンポーネントからクライアントコンポーネントにプロミスを渡す場合、その解決値は、サーバとクライアント間でやりとり可能になるよう、シリアライズ可能でなければなりません。関数のようなデータ型はシリアライズ可能ではないため、プロミスの解決値として利用できません。

プロミスをサーバコンポーネントで解決するか、クライアントコンポーネントで解決するか? {/resolve-promise-in-server-or-client-component/}

プロミスはサーバコンポーネントからクライアントコンポーネントに渡し、use API を使ってクライアントコンポーネントで解決することができます。また、await を使ってサーバコンポーネント側でプロミスを解決し、必要なデータを props としてクライアントコンポーネントに渡すことも可能でしょう。

export default async function App() {
  const messageContent = await fetchMessage();
  return <Message messageContent={messageContent} />
}

しかしサーバコンポーネントawait を使用すると、await 文が終了するまでそのレンダーがブロックされます。サーバコンポーネントからクライアントコンポーネントにプロミスを渡すことで、プロミスがサーバコンポーネントのレンダーをブロックすることを防ぐことができます。

拒否されたプロミスの取り扱い {/dealing-with-rejected-promises/}

場合によっては、use に渡されたプロミスが拒否されることがあります。プロミスが拒否された場合にそれを処理する方法は以下の 2 つです。

  1. エラーバウンダリを使ってユーザにエラーを表示する
  2. Promise.catch で代替値を提供する
`use` は try-catch ブロック内で呼び出すことはできません。try-catch ブロックを使う代わりに、[コンポーネントをエラーバウンダリでラップする](#displaying-an-error-to-users-with-error-boundary)か、または[プロミスの `.catch` メソッドで代替値を提供](#providing-an-alternative-value-with-promise-catch)してください。

エラーバウンダリを使ってユーザにエラーを表示する {/displaying-an-error-to-users-with-error-boundary/}

プロミスが拒否されたときにユーザにエラーを表示したい場合は、エラーバウンダリ を使用できます。エラーバウンダリを使用するには、use API を呼び出しているコンポーネントをエラーバウンダリでラップします。use に渡されたプロミスが拒否されると、エラーバウンダリに書かれたフォールバックが表示されます。

"use client";

import { use, Suspense } from "react";
import { ErrorBoundary } from "react-error-boundary";

export function MessageContainer({ messagePromise }) {
  return (
    <ErrorBoundary fallback={<p>⚠️Something went wrong</p>}>
      <Suspense fallback={<p>⌛Downloading message...</p>}>
        <Message messagePromise={messagePromise} />
      </Suspense>
    </ErrorBoundary>
  );
}

function Message({ messagePromise }) {
  const content = use(messagePromise);
  return <p>Here is the message: {content}</p>;
}
import { useState } from "react";
import { MessageContainer } from "./message.js";

function fetchMessage() {
  return new Promise((resolve, reject) => setTimeout(reject, 1000));
}

export default function App() {
  const [messagePromise, setMessagePromise] = useState(null);
  const [show, setShow] = useState(false);
  function download() {
    setMessagePromise(fetchMessage());
    setShow(true);
  }

  if (show) {
    return <MessageContainer messagePromise={messagePromise} />;
  } else {
    return <button onClick={download}>Download message</button>;
  }
}
import React, { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

// TODO: update this example to use
// the Codesandbox Server Component
// demo environment once it is created
import App from './App';

const root = createRoot(document.getElementById('root'));
root.render(
  <StrictMode>
    <App />
  </StrictMode>
);
{
  "dependencies": {
    "react": "19.0.0",
    "react-dom": "19.0.0",
    "react-scripts": "^5.0.0",
    "react-error-boundary": "4.0.3"
  },
  "main": "/index.js"
}

Promise.catch で代替値を提供する {/providing-an-alternative-value-with-promise-catch/}

use に渡されたプロミスが拒否されたときに代替値を提供したい場合、プロミスの catch メソッドを使用できます。

import { Message } from './message.js';

export default function App() {
  const messagePromise = new Promise((resolve, reject) => {
    reject();
  }).catch(() => {
    return "no new message found.";
  });

  return (
    <Suspense fallback={<p>waiting for message...</p>}>
      <Message messagePromise={messagePromise} />
    </Suspense>
  );
}

プロミスの catch メソッドを使用するには、プロミスオブジェクトの catch を呼び出します。catch はエラーメッセージを引数とする関数を唯一の関数として受け取ります。catch に渡された関数によって返された任意の値が、プロミスの解決値として使用されます。


トラブルシューティング {/troubleshooting/}

"Suspense Exception: This is not a real error!" {/suspense-exception-error/}

あなたは React コンポーネントまたはフック関数の外部で use を呼び出しているか、または try-catch ブロック内で use を呼び出しています。try-catch ブロック内で use を呼び出している場合は、コンポーネントをエラーバウンダリでラップするか、プロミスの catch を呼び出してエラーをキャッチし、別の値でプロミスを解決します。こちらの例を参照してください

React コンポーネントまたはフック関数の外部で use を呼び出している場合は、use の呼び出しを React コンポーネントまたはフック関数に移動します。

function MessageComponent({messagePromise}) {
  function download() {
    // ❌ the function calling `use` is not a Component or Hook
    const message = use(messagePromise);
    // ...

上記の場合、コンポーネントのクロージャの外で use を呼び出すようにすることで、コンポーネントまたはフックから use を呼び出すという条件を満たすようになります。

function MessageComponent({messagePromise}) {
  // ✅ `use` is being called from a component.
  const message = use(messagePromise);
  // ...