2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Recoil公式ドキュメント 翻訳⑧ ガイド-非同期データクエリ

Last updated at Posted at 2020-10-21

Recoilの公式ドキュメントをgoogle翻訳するとコードまで翻訳されてしまうのが面倒なのでQiitaにまとめてみます。

追々追加していきます。

公式ドキュメント

目次

全目次は一番下にあります


非同期データクエリ

Recoilは、stateと派生stateをデータフローグラフを介してReactコンポーネントにマップする方法を提供します。
非常に強力なのは、グラフ中の関数が非同期であることです。これにより、同期Reactコンポーネントのレンダリング関数で非同期関数を簡単に使用できるようになります。
Recoilを使用すると、selectorのデータフローグラフに同期関数と非同期関数をシームレスに混在させることができます。selectorのgetコールバックから値自体ではなくPromiseを値に返すだけで、インタフェースはまったく同じままです。これらは単なるselectorなので、他のselectorもデータをさらに変換するためにこれらに依存することができます。

selectorは、Recoilデータフローグラフに非同期データを組み込む1つの方法として使用できます。
selectorは純粋な関数を表していることに注意してください。
与えられた入力の集合に対して、selectorは常に同じ結果を生成します(少なくともアプリケーションの存続期間中は)。selector評価は1回以上実行され、再起動され、キャッシュされる可能性があるため、これは重要です。このため、selectorは、クエリを繰り返すことで一貫したデータが得られる読み取り専用のDBクエリをモデル化するのに適しています。
ローカルとサーバーのstateを同期させる場合は、非同期状態・同期状態またはStateの永続性を参照してください。

同期の例

ユーザー名を取得するための単純な同期atomselectorを次に示します。

const currentUserIDState = atom({
  key: 'CurrentUserID',
  default: 1,
});

const currentUserNameState = selector({
  key: 'CurrentUserName',
  get: ({get}) => {
    return tableOfUsers[get(currentUserIDState)].name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameState);
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <CurrentUserInfo />
    </RecoilRoot>
  );
}

非同期の例

ユーザ名が何らかのデータベースに格納されている場合は、Promiseを返すか、非同期関数を使用するだけで済みます。依存関係が変更されると、selectorが再評価され、新しいクエリが実行されます。結果はキャッシュされるため、クエリは一意の入力ごとに1回だけ実行されます。

const currentUserNameQuery = selector({
  key: 'CurrentUserName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}

selectorのインターフェースは同じなので、このselectorを使用するコンポーネントは、それが同期atom state、派生selector state、または非同期クエリでバックされたかどうかを気にする必要はありません!

しかし、Reactのレンダリング機能は同期であるため、約束が解決する前に何をレンダリングするのだろうか?
Recoilは、React Suspenseと連動して保留中のデータを処理するように設計されている。Suspense境界でコンポーネントをラップすると、保留中のすべての子孫がキャッチされ、フォールバックUIがレンダリングされます。

function MyApp() {
  return (
    <RecoilRoot>
      <React.Suspense fallback={<div>Loading...</div>}>
        <CurrentUserInfo />
      </React.Suspense>
    </RecoilRoot>
  );
}

エラー処理

しかし、要求にエラーがある場合はどうなるでしょうか。
Recoilのselectorは、コンポーネントがその値を使おうとした場合に投げられるエラーを投げることができます。これはReact<ErrorBoundary>でキャッチできます。たとえば、次のようになります。

const currentUserNameQuery = selector({
  key: 'CurrentUserName',
  get: async ({get}) => {
    const response = await myDBQuery({
      userID: get(currentUserIDState),
    });
    if (response.error) {
      throw response.error;
    }
    return response.name;
  },
});

function CurrentUserInfo() {
  const userName = useRecoilValue(currentUserNameQuery);
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <CurrentUserInfo />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

パラメータを使用したクエリ

派生stateだけでなく、パラメータに基づいてクエリを実行できるようにしたい場合があります。たとえば、コンポーネントプロパティに基づいてクエリを実行できます。これを行うには、selectorFamilyヘルパーを使用します。

const userNameQuery = selectorFamily({
  key: 'UserName',
  get: userID => async () => {
    const response = await myDBQuery({userID});
    if (response.error) {
      throw response.error;
    }
    return response.name;
  },
});

function UserInfo({userID}) {
  const userName = useRecoilValue(userNameQuery(userID));
  return <div>{userName}</div>;
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <UserInfo userID={1}/>
          <UserInfo userID={2}/>
          <UserInfo userID={3}/>
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

データフローグラフ

クエリをselectorとしてモデリングすることによって、データ・フロー・グラフの混合state、派生state、クエリを作成できることを忘れないでください。stateが更新されると、このグラフはReactコンポーネントを自動的に更新して再レンダリングします。

次の例は、現在のユーザの名前と友人のリストを表示します。友達の名前をクリックすると、その友達が現在のユーザーになり、名前とリストが自動的に更新される。

const currentUserIDState = atom({
  key: 'CurrentUserID',
  default: null,
});

const userInfoQuery = selectorFamily({
  key: 'UserInfoQuery',
  get: userID => async () => {
    const response = await myDBQuery({userID});
    if (response.error) {
      throw response.error;
    }
    return response;
  },
});

const currentUserInfoQuery = selector({
  key: 'CurrentUserInfoQuery',
  get: ({get}) => get(userInfoQuery(get(currentUserIDState))),
});

const friendsInfoQuery = selector({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    return friendList.map(friendID => get(userInfoQuery(friendID)));
  },
});

function CurrentUserInfo() {
  const currentUser = useRecoilValue(currentUserInfoQuery);
  const friends = useRecoilValue(friendsInfoQuery);
  const setCurrentUserID = useSetRecoilState(currentUserIDState);
  return (
    <div>
      <h1>{currentUser.name}</h1>
      <ul>
        {friends.map(friend =>
          <li key={friend.id} onClick={() => setCurrentUserID(friend.id)}>
            {friend.name}
          </li>
        )}
      </ul>
    </div>
  );
}

function MyApp() {
  return (
    <RecoilRoot>
      <ErrorBoundary>
        <React.Suspense fallback={<div>Loading...</div>}>
          <CurrentUserInfo />
        </React.Suspense>
      </ErrorBoundary>
    </RecoilRoot>
  );
}

同時要求

上記の例を見るとわかるように、friendsInfoQueryはクエリーを使用して、各友人の情報を取得します。しかし、これをループで行うことで、本質的にシリアライズされます。検索が早ければ、それでいいかもしれません。情報量が多い場合は、waitForAllなどの並行処理ヘルパーを使用して並行処理を実行できます。このヘルパーは、依存関係の配列と名前付きオブジェクトの両方を受け入れます。

const friendsInfoQuery = selector({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    const friends = get(waitForAll(
      friendList.map(friendID => userInfoQuery(friendID))
    ));
    return friends;
  },
});

waitForNoneを使用して、部分的なデータを含むUIへの増分更新を処理できます。

const friendsInfoQuery = selector({
  key: 'FriendsInfoQuery',
  get: ({get}) => {
    const {friendList} = get(currentUserInfoQuery);
    const friendLoadables = get(waitForNone(
      friendList.map(friendID => userInfoQuery(friendID))
    ));
    return friendLoadables
      .filter(({state}) => state === 'hasValue')
      .map(({contents}) => contents);
  },
});

プリフェッチ*

パフォーマンス上の理由から、レンダリングの前にフェッチを開始することをお勧めします。これにより、レンダリングの開始時にクエリーを実行できます。Reactのドキュメントには、いくつかの例が記載されています。このパターンはRecoilでも有効です。

*プリフェッチ

上の例を変更して、ユーザーがボタンをクリックしてユーザーを変更するとすぐに次のユーザー情報のフェッチを開始するようにします。

function CurrentUserInfo() {
  const currentUser = useRecoilValue(currentUserInfoQuery);
  const friends = useRecoilValue(friendsInfoQuery);

  const changeUser = useRecoilCallback(({snapshot, set}) => userID => {
    snapshot.getLoadable(userInfoQuery(userID)); // pre-fetch user info
    set(currentUserIDState, userID); // change current user to start new render
  });

  return (
    <div>
      <h1>{currentUser.name}</h1>
      <ul>
        {friends.map(friend =>
          <li key={friend.id} onClick={() => changeUser(friend.id)}>
            {friend.name}
          </li>
        )}
      </ul>
    </div>
  );
}

React Suspense を使わない

保留中の非同期セレクタを処理するためにReact Suspenseを使用する必要はありません。
useRecoilValueLoadable()hookを使用して、レンダリング中のステータスを決定することもできます。

function UserInfo({userID}) {
  const userNameLoadable = useRecoilValueLoadable(userNameQuery(userID));
  switch (userNameLoadable.state) {
    case 'hasValue':
      return <div>{userNameLoadable.contents}</div>;
    case 'loading':
      return <div>Loading...</div>;
    case 'hasError':
      throw userNameLoadable.contents;
  }
}

参考サイト

公式ドキュメント
IT用語辞典 シームレス
React Suspense
ErrorBoundary
IT用語辞典 シリアライズ
Reactドキュメント Start Fetching Early
IT用語辞典 プリロード(フェッチ)
みらい翻訳


全目次

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?