298
217

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 5 years have passed since last update.

Reactの次期機能のSuspenseが凄くって、非同期処理がどんどん簡単になってた!

Last updated at Posted at 2018-09-18

React17の次期新機能のSuspenseが凄い! と思ったので少し学習していました!
Suspense自体の説明は下の動画がわかりやすいかも。

image.png

13:15ぐらいからプログラムのDemo
29:30ぐらいからSuspenseとはなんぞや、という説明をしてくれています。

Githubにdemoもあったので、実際に動かしてみたい方はこちらも是非是非

つまりSuspenseって何?

Suspenseっていう機能があるわけじゃないんです、すみません
概念というかなんといいますか:thinking:
自分の意訳・解釈なので間違っていたら土下座しに行きます:bow:

外部APIからデータを取得・表示するような処理で使えるんですね。
読み込みを開始したらLoadingを出して、APIが戻ってきたらデータを表示してローディングを消して……っていう、reduxやsagaを使って今まで頑張ってきているものがあると思うんですね。

API実行中はLoading中のコンポーネントを表示して、非同期処理が終わったら指定のコンポーネントを表示するという機能がReact本体に組み込まれる予定なので、こういった処理の部分でSuspeseを使うと非常に楽ができます!

renderだけで非同期処理が書ける時代が来てしまった:relaxed:

という訳で早速動かしてみる

demoを動かしてみれば一番なんですが、微妙に読まないといけないソースの数が大きくって:joy:
超シンプルなのを自分の方で作ってみました。

■git
https://github.com/fumihiko-hidaka/react-hello-suspense

■demo
https://neko-tech-test-storage.storage.googleapis.com/hello-suspense/index.html?v2

index.jsx
import React, { Timeout } from 'react';
import ReactDOM from 'react-dom';

let cache = null;

const sleep = sec => new Promise(resolve => setTimeout(resolve, sec * 1000));

const HelloSuspenseWorld = () => {
  if (cache) {
    return cache;
  }

  throw new Promise(async (resolve) => {
    await sleep(5);
    cache = 'hello, suspense world!';
    resolve();
  });
};

ReactDOM.render(
  <Timeout>
    {didExpire => didExpire ? 'loading' : <HelloSuspenseWorld />}
  </Timeout>,
  document.getElementById('app'),
);

みんな大好き1ファイル構成なので説明がしやすい:relaxed:

実装内容はただ一つ。5sたったら表示が切り替わる、それだけです。
そんなjsでやりたいことは下の3つです。

  • Timeoutコンポーネントの使用
  • renderだけでstateの更新もdispatchも何もしていない! 
  • けれども、表示がloadingから hello, suspense world! に切り替わる!

Timeoutコンポーネントの挙動!

上のjsファイルを見てもらえればわかるんですが、割と今までにないような実装になっていると思います。
throwされるPromiseとか、最初見た時はついていけませんでした。

処理の流れを画像に起こすとこんな感じかな?

suspense.png

まぁなんてわかりにくい図でしょう:skull:

ThrowされるPromise!

図のわかりやすさはともかくとして……
そう、TimeoutコンポーネントはThrowされたPromiseを受け取ることで、表示を切り替えているのです。
Error以外のオブジェクトをThrowするという発想はなかった。

そして、受け取ったPromiseの実行が終わった段階で再描画が走るんですね。
ここで実行結果を保存しておいてあげることで、再度呼ばれた場合に本来表示したかったものを出すことができる。という構成になっています。

throwする処理とかキャッシュする処理とか自分で作るのめんどくさい:alien:

おっしゃる通りです。
なので、そのあたりについては動画でのdemo用ではあるんですが、simple-cache-providerというPromiseを上手い具合に処理してくれるライブラリが提供されていたりします。

Promiseの実行結果の保存もThrowも対応してくれるので、あんまり考慮する必要はありません。
というか、このあたりのライブラリを使わずに自分で実装するのは、学習目的以外では大変なだけでした:sob:

このsimple-cache-providerを用いた場合の実装については、また別記事にわけて書く……予定:ghost:

感想

eslintとかでも指摘されるんですが、renderの中でデータの更新とかって普通、やっちゃいけないはずなんですよね。
なんですが、今回そこに踏み込んできた感があります。:open_mouth:
reduxやsagaも、今後どういう風に折り合いをつけて設計・提供されていくのか……ということを考えると、おらワクワクすっぞ:monkey:

正式な機能として提供されるのが楽しみです:v:

追記1 new Promiseした中にasyncって処理は冗長じゃない?

という編集リクエストが来たので、確かに一理あるなと。

async.jsx
  throw (async () => {
    await sleep(5);
    cache = 'hello, suspense world!';
  })();

という形で修正しました! これでPromiseの中にPromiseを入れるという冗長さは解決!
編集リクエスト本当にありがとうございます:sob:

298
217
2

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
298
217

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?