
2025.03.27
「過去1年でインシデントが増加」と回答した人は約8割 大規模化かつ高速化するDDoS攻撃から企業を守る、最新セキュリティ
Suspense(全1記事)
リンクをコピー
記事をブックマーク
koba04氏:よろしくお願いします。「Suspense」というタイトルで話していきたいと思います。Suspenseを聞いたことがある方はどれぐらいいますか?
(会場挙手)
数人ぐらいですね。ありがとうございます。
最初に自己紹介をさせていただきます。「koba04」というアカウントでやっています。
今はサイボウズで働いていて、フロントエンド周りいろいろやっています。OSS関連では、React系にコントリビュートしていることが多いです。
最初にSuspenseとはなにかという話です。2月の後半のJSConf IcelandでDan Abramovによって発表されたのが最初です。
もう1つ、(React)17の目玉の機能として「Time Slicing」というものがあります。そっちは低スペックなモバイルでもUIをブロッキングせずに更新できるようになる機能で、Suspenseは非同期の更新処理をいい感じに扱うための機能というイメージです。
非同期の更新処理というのはなにかというと、例えばHTTP Requestとか、最近だとDynamic importなど、基本的にPromiseをprimitiveとして扱うようなもの。なので、Promiseを返すようなものだったらだいたいなんでも扱えます。
非同期の更新処理って面倒くさいなと思っているかと思います。例えばReduxとかで実装すると、Start、Success、Failureとかのアクションを3つぐらい発行してloadingのstateを管理してみたいな、「あぁ、つらい……」みたいな感じになると思います。
(会場笑)
あと、そこをちゃんとしようとすると面倒くさくて。よくある場面としては、いろんなコンポーネント単位でLoadingのスピナーがバーって5〜6個出ちゃったりとか、APIが100msぐらいで返ってきてるのに「loading...」というのが一瞬だけちらっと出たりして、すごいジャンクな感じ。
このあたりをちゃんとやろうとすると面倒くさいのはけっこう同意できる点じゃないかと思います。そのあたりを解決するための機能がSuspenseです。
では、これはPromise supportかと思うと思いますが、ComponentがPromiseを返すのをsupportするというわけではありません。
ただPromiseを返すComponentだけsupportした場合でもこの問題は解決できません。例えば、子や孫のComponentが非同期の依存関係を持っているときは、全部親のComponentや、Loading出したいところまでリフトアップしてこないとダメで、それを手動でやるとするとだいぶ厳しい感じのコードになります。
例えばこのUserListPageで、その非同期の処理が全部終わるまでは「Loading...」を出したいときも、その中のComponentのPromiseの依存を全部上に持ってこないとダメになってしまうところが問題です。
では、Suspenseはどう動くのかというところです。16でError Boundaryという機能が入りました。これはなにかというと、renderやライフサイクルメソッドでエラーが起きたときに、それを親のComponentのcomponentDidCatchというライフサイクルメソッドでキャッチして、エラー用の表示をするための機能です。これと同じ仕組みでSuspenseは動きます。
Error Boundaryの場合はエラーオブジェクトをキャッチするんですが、Suspenseの場合はPromiseがthrowされるので、それをキャッチして。この場合もTimeoutというComponentでキャッチして処理するみたいなかたちになります。実際にはthenableなオブジェクトだったら大丈夫です。
なので、例えばここでUserというComponentがPromiseをthrowしたら、Timeoutでキャッチして。Timeoutは、指定されたミリ秒が経つかPromiseがresolveするかどちらかで、Timeoutが先に来たらfallbackの表示を出し、PromiseがresolveしたらUserを表示する、みたいなフローになります。
というので、もう1個要素があります。Promiseのキャッシュをうまく扱うための仕組みが必要になるんですが、そのためのsimple-cache-providerというライブラリもReactが提供しています。
これはReactのリポジトリの中に入っていて、コード自体もかなり小さく、ほかの依存もないので、Reactのリポジトリの中にあるコードの中ではかなり読みやすいほうだと思います。
最近はキャッシュにLRUの実装が入って400行ぐらいになったんですが、それでもかなり短いので、簡単に読めるコードになっています。こいつはキャッシュのコンテナみたいなイメージです。
どのように遷移するかというと、最初はなにもないのでEmptyの状態になっています。そこでreadとかpreloadをすると、非同期処理のPromiseを作成します。そうするとPendingという状態になります。
このときにreadしようとすると全部Promiseをthrowするかたちになります。Promiseがresolveしたら、それをデータをキャッシュとして持って、それ以降は同期的にデータを返すという挙動になります。
じゃあどう使うのかというところなんですが、最初になにかキャッシュを作って、あとはResourceというPromiseの依存のもの。この場合だとfetchですね。このfetchをしたものとキャッシュを組み合わせてuserFetcherを作って、それをComponentのrenderメソッドの中で読みます。
なので、最初になにもデータがないときは、このFetcher.readでPromiseがthrowされるというかたちになって、レンダリングがここで止まります。
このComponentはPlaceholderというComponentで囲まれていて、その中でさらにReact.TimeoutというComponentがキャッチします。この場合だと、200ms以上だったら「loading...」という表示をfallbackとして出す、という実装になっています。
なので、こんな感じで状態を明示的には管理せずに書けるようになったり、このTimeoutのところを柔軟に制御できるようになるのがSuspenseの機能です。
Promiseがprimitiveなので、先ほどはfetchでしたが、当然Dynamic importなども同じ感じで扱うことができます。
ということで、ここからはデモをします。ネットワークに依存するのでうまくいくかわからないですけど(参考:React Suspense Demo)。
これは、APIのwaitやTimeoutの秒数を動的に切り替えられるような実装になっています。例えば普通に表示すると……。これはユーザーのstar一覧のリポジトリを取ってくるアプリなんですけど、今見たように「loading...」がチラって一瞬出てきました。
Suspenseを使うと例えば、1秒以上経ったら「loading...」を出したいということも可能です。逆にTimeoutまでにAPIのデータがloadしたら、「loading...」という表示は出ずに普通に出るという感じです。それで先ほどの問題が解決できます。
また、キャッシュに入っている場合、同期的にデータが返ってきます。だから一瞬で表示させることができるようになります。別のユーザーだと、またloadingが走るというかたちになります。
このように、非同期の更新処理の制御が柔軟にできます。
コードは、TimeoutのComponentがあって、expireしたら「loading...」と表示し、なければリポジトリのデータを出すようになっています(参考:GitHub - koba04/react-suspense-demo)。
このあたりはただAPIをfetchしているだけです。
さっきのcreateCacheなどを使ってfetcherオブジェクトを作成して、getApiDataとしてラップして、こいつをこのComponentのrenderメソッドの中から普通に呼んでいるだけという実装だけです。
もう1個は、これは画像を表示するケースです。普通に出すと、一瞬ガタガタっと表示されます。
これに対する解決方法としては、画像のサイズを固定するという方法を取ると思います。そうすると、画像のサイズは固定されますが、ネットワークが遅い場合だと、先ほどのように画像が徐々に読み込まれます。
例えば、回線を3Gぐらいにしておくとより顕著なんですけど、こんな感じにたぶん画像の読み込みが長くなります。これはたぶんみなさん許せないと思います。
ですので、方法としてはpreloadします。preloadすると、この場合は、画像の表示が全部preloadが終わった後に行われるので、一瞬でパッといい感じに出ます。
でも、今のなにも出ないのが嫌だという人は、プレースホルダー画像の表示もできるようになっています。例えばこんな感じにすると、プレースホルダー画像を表示して、loadが終わったら出すみたいなことが先ほどと同じ感じで出ます。
コードは先ほどとほとんどかたちは一緒になっています(参考:GitHub - koba04/react-suspense-demo)。ただ、今だとTimeoutのこのComponent自体が非同期のPromiseの投げるやつになっているんですけど。
例えばこの場合は、なにか別のただのComponentのwrapperがあって、この中にPromiseを返すComponentが入っているんですけど。こういうのって普通のPromiseのComponentサポートだと対応できないんですけど、そのへんもちゃんとできるようになっています。
では、ImageWrapperというタイトルを出したいと思ったら、その外側のTimeoutのさらに中でTimeoutでキャッチすることもできて、そうするとこいつが内側で処理するので外側まで伝播しなくなります。例えばこの「ImageWrapper」というタイトルはちゃんと表示されたまま、その中で画像のpreloadの表示がされるようになります。こんな感じですね。
という感じで、更新処理の制御はすごく柔軟にできるようになるというのがSuspenseの機能です。
最初のTime Slicingもそうなんですが、基本ネットワークが不便だったり、すごい低スペックな端末とか使っている人に向けての機能が、最近はすごく強化されているという印象です。
というわけで、今のは17でデフォルトで使えるようになる予定で、最終的なAPIはたぶんまだ変わります。Reactはcanaryというタグをつけていて、ReactのcanaryとReact DOMのcanaryとsimple-cache-providerをインストールすれば使えるようになっています。今のデモもGitHubにあって、Netlifyとかでデプロイしてるので、手元でも見れます。
そのほかでは、サーバサイドでSuspenseを動かすという実験とかも行われていて。クライアント側でReact DOMとかをまったく使わずに今みたいな表示が行われています。このへんはソースが公開されていないので、どういうテイストになっているかはわかりませんが。
あとは、ReactEuropeでは、先ほどのSuspenseをApolloのキャッシュと組み合わせるデモもあったりとかって感じで、けっこうそのあたりのエコシステムと組み合わせるということも盛り上がっている段階です。
というわけでまとめです。先ほどの例は、最終的なAPIではないので変わります。けっこうさっき見たみたいに、renderメソッドの中ってこれまでって「副作用みたいのは書くな」というのが鉄則というかベストプラクティスだったんですが、それを完全に覆してfetchしちゃうみたいな感じになっているので、Componentのあり方は変わってくるんじゃないかと思います。
あとは、先ほどのものを利用することでpreloadingをやったり、hiddenというpropsを使うことで、事前に裏側でメインのページを邪魔せずprerenderしておくみたいな、そういうのもできるようになったりします。
また、ReduxやStoreとどう組み合わせるのか、そうしたエコシステムとどう組み合わせていくかは今後も注目していくという段階です。
「最高! いつ使えるの?」というところで、一応予定としては2018年中に17がリリースされるイメージです。ただ、もう実際に使える段階になっていて、今はFacebookなどで実際に試したりしているような段階です。
ということで、Suspenseに備えていきましょう。ありがとうございました。
(会場拍手)
関連タグ:
2025.03.21
マネージャーの「自分でやったほうが早い」という行動で失うもの 効率・スピード重視の職場に足りていない考え方
2025.03.25
減点を恐れてモチベ低下、果ては離職も… あらゆる“会社の害虫”を大繁殖させる「ラスボス」の正体
2025.03.17
不確実な時代だからこそ「知らないこと」を武器にする ハーバード首席卒業生の逆説的なメッセージ
2025.03.19
組織をダメにする“害虫”の正体は間違った思い込み AIやDXなど手段のみにこだわるダメ上司の見極め方
2025.03.24
最悪の場合、組織を死に至らせる“会社の害虫”とは 誤った意思決定や品質不祥事を招く要因
2025.03.19
フェデラー氏が語る「ただの1ポイント」の哲学 ウィンブルドン敗北から学んだ失敗からの立ち直り方
2025.03.24
気づけばモラル崩壊……人材育成に無頓着な企業の末路 業績アップや採用にもつながる“人への投資”の重要性
2025.03.19
OpenAIのAIエージェント「Deep research」はビジネスをどう変革するのか? サム・アルトマン氏ら4人がデモンストレーション
2025.03.19
大企業だけじゃない、「モーレツ×内向き」な組織の2つのリスク “悪気なく染み付いた文化”は個人のキャリアにも悪影響
2025.01.07
1月から始めたい「日記」を書く習慣 ビジネスパーソンにおすすめな3つの理由
2025.03.21
マネージャーの「自分でやったほうが早い」という行動で失うもの 効率・スピード重視の職場に足りていない考え方
2025.03.25
減点を恐れてモチベ低下、果ては離職も… あらゆる“会社の害虫”を大繁殖させる「ラスボス」の正体
2025.03.17
不確実な時代だからこそ「知らないこと」を武器にする ハーバード首席卒業生の逆説的なメッセージ
2025.03.19
組織をダメにする“害虫”の正体は間違った思い込み AIやDXなど手段のみにこだわるダメ上司の見極め方
2025.03.24
最悪の場合、組織を死に至らせる“会社の害虫”とは 誤った意思決定や品質不祥事を招く要因
2025.03.19
フェデラー氏が語る「ただの1ポイント」の哲学 ウィンブルドン敗北から学んだ失敗からの立ち直り方
2025.03.24
気づけばモラル崩壊……人材育成に無頓着な企業の末路 業績アップや採用にもつながる“人への投資”の重要性
2025.03.19
OpenAIのAIエージェント「Deep research」はビジネスをどう変革するのか? サム・アルトマン氏ら4人がデモンストレーション
2025.03.19
大企業だけじゃない、「モーレツ×内向き」な組織の2つのリスク “悪気なく染み付いた文化”は個人のキャリアにも悪影響
2025.01.07
1月から始めたい「日記」を書く習慣 ビジネスパーソンにおすすめな3つの理由
退職をチャンスに捉える企業文化のつくり方
2025.03.10 - 2025.03.10
青木耕平さんとザッソウ(#156〜158)
2025.02.05 - 2025.03.19
片付けパパ対談【特別編】豊かな人生を過ごすための「投資」&「交渉術」 ~チャンスを逃さず信頼関係も育むコツ~
2025.02.10 - 2025.02.10
グローバルの経営理論に学ぶ、企業アルムナイ成功への示唆〜中央大学ビジネススクール 犬飼知徳教授
2025.02.18 - 2025.02.18
【手放すTALK LIVE#046】 出版記念イベント 『大きなシステムと小さなファンタジー』 一つ一つのいのちが大切にされる社会へ
2025.02.03 - 2025.02.03