React Concurrent Mode の概要をおさえる

はじめに

この記事は, Introducing Concurrent Mode (Experimental) – React を,和訳しつつ自分の解釈や図を少しだけ足したものになります. 筆者は,そこまで英語が得意ではないので,一部適切でない表現があるかもしれませんがご了承ください.

Introducing Concurrent Mode (Experimental)

Blocking vs Interruptible Rendering

Concurrent Mode は,バージョンコントロール を使って説明します. ここで言うバージョン管理というのは,gitのようなバージョン管理システムです. ブランチを切って作業を行い,他の人が pull できるように作業ブランチをマスターにマージします.

バージョン管理の概念が存在する前と後では,開発ワークフローは大きく異なっていました. ブランチの概念がなく,一部のファイルを編集する場合は,作業が完了するまで そのファイルに触れられないようにしておく必要がありました. 作業をまさにしている人と同時に作業を開始することもできませんでした. まさしく,文字通りブロックされていました.

従来のReactでも,ブランチが存在しない状況のように,仮想DOMのツリーを変更しているときは, 並行して別の変更を予め行うことはできませんでした. 例えば,新しいDOMノードの作成や,コンポーネント内でのコードの実行など,更新のレンダリングを 開始するとこれを中断することができませんでした.これを 「blocking rendering」 と言います

Concurrent Mode ではブロックされません.これによりUXが向上されることが期待できます. 次の章で具体的な例を見る前にそれの概要を説明します.

Interruptible Rendering

例えとして,フィルター処理できるような product list を作るとします. フィルタを絞り込むクエリを入力するフォームに文字を入力するたびに, 音が途切れて聞こえるような,画面のカクつきっぽい不安感を感じることはありませんか?

f:id:maxmellon:20191103180037g:plain

記事中では Stutter と表現されている UI のどもり. 自分はカクつき と表現した

新しいDOMノードの作成やLayoutingなど,更新する作業には一部裂けられない場合があります. それをいつどのように実行するかを決めるのが大きな役割で根幹となります.

UIがカクついたり,更新が詰まったりするのを回避する一般的な手法として多くあるのが, 入力を Debounce することです.

f:id:maxmellon:20191103174445p:plain

これは,上の図を見ればわかるように 入力がUIに反映されない瞬間 があります. それは,ユーザーのイラつきにつながることもあるでしょう. それの回避策として,特定の頻度で強制的に更新させることもできます. ただそれは,結局低スペックなデバイスでは依然としてカクつきが発生してしまいます. Debounce とその周期の調整により,最適とは言えないUXができてしまいます.

カクつきの理由は単純です. レンダリングが開始させると,それを中断させることはできません. つまり,レンダリングが開始してしまうと,その最中に入力をしてもUIは更新できません.

Concurrent Modeでは,レンダリングを中断可能にすることにより,ブロッキングしてしまうという 基本的な仕組みを修正します.つまり,ユーザーが別のキーを押してもテキスト入力を更新するときにブロックする必要がなくなります. 代わりにブラウザから入力された更新をペイントし,更新されたリストをメモリにレンダリングすることが可能になります. レンダリングが完了すると,ReactはDOMを更新し変更が画面に反映されます.

概念的には,これは「branch上で」すべての更新を準備する状態が作れるようになったReactと考えることができます.ブランチでの作業を放棄したり,checkout のようにブランチを切り替えたりするように,React with Concurrent Mode は進行中の更新を中断してより重要なことを行い,それが終われば以前の作業に戻ることができます.ゲームのダブルバッファリングを彷彿とさせるかもしれません.

Concurrent Mode の手法により,UIでの debounce / throttle の必要性が減少します. renderingは中断可能であるため,カクつきを避けるために作業を人為的に遅らせる必要はありません. レンダリングの開始はすぐにできます,もし描画の必要がなければ中断し,アプリの応答性を維持できます.

Intentional Loading Sequences

Concurrent Mode は,Reactが「Branch上で」動作するものとこれまで説明してきました. branchは短期的な修正だけでなく,長時間実行される機能にも役立ちます. 実際に,ソフトウェア開発においても あるブランチがマージされるまで一週間かかったりすることもあります. それと同じように,長時間実行される機能にもバージョンコントロールに例えて説明できます.

例えば,アプリの2つの画面間を遷移しているとします. 時々,新しい画面でユーザーにローディングを表すインディケーターを出すほど十分な時間のローディングが行われない場合が会います. 空の画面だったり,大きなインディケーターやスピナーへの移動は,不快な体験につながる場合があります. 必要なデータの取得にそれほど時間がかからないことも最近ではよくあります. そんなときに,Reactが遷移する際に 少しの間だけ古い画面でとどまり,新しい画面が用意できてから遷移して,ローディング画面をスキップできると素敵だとは思いませんか?

Concurrency

今まで触れたきた例を2つを要約して,Concurrent Mode がこれらを統合するのかを見てみましょう. Concurrent Mode では,React は複数の状態の更新を同時に実行できます. ブランチ同様に,様々なチームメンバーが独立して作業できます.

  • CPUバウンドのための更新 (DOMノードの作成やコンポーネントコードの実行) の場合,同時実行とは,緊急度の高いレンダリング処置を開始しようとしたとき,すでに開始している処理を中断できることを意味します
  • IOバウンドのための更新 (ネットワークからのコードやデータの取得など) の場合,同時実行とは,Reactがすべてのデータの取得が完了する前でもメモリ内でレンダリングを開始し,空のロード状態を表示しないことを意味します.

重要なのは,Reactの使い方自体は今までと同じということです. コンポーネントのpropsやstateの概念は今まで同じように使うことができます. 画面を更新しようとするときに状態をセットします.(今までと同じ)

Reactは,ヒューリスティックによって更新の「緊急度」を判断し,数行のコードで更新を調整できるようになる. なので,様々なインタラクションに合わせてベストなUXを実現できます.

Putting Research into Production

Concurrent Mode 機能には,共通のテーマがあります. その使命は,human computer interaction の分野の研究結果を実際のUIに反映させすることです.

例えば,調査では,画面間を遷移するときに中間の読み込み状態が多すぎると,遷移が遅く感じられる という結果がでています. これが Concurrent Mode が つねにローディング状態を出さず,必要に応じて古い画面を描画し,不要な更新を避ける理由です.

同時に,ホバーやテキスト入力などのインタラクションは非常に短い時間内に処理する必要がありますが, クリックやページ遷移は,少し待ち時間が生まれたことによって,遅延を大きく感じるないということがわかっています. Concurrent Mode で内部的に使用されるさまざまな「優先度」は,人間の知覚研究における装画症のカテゴリにほぼ対応しています.

Concurrent Mode の目標は,研究結果を抽象的な概念に落とし込み,それらを使用する慣用的な方法を提供することです. Reactは,まさに UIライブラリとして それを実現する位置づけに当たります.

おわりに

この記事では, Introducing Concurrent Mode で終わりましたが,

  • Suspense for Data Fetching
  • Concurrent Mode

も後日 公開したいと考えています.