"How React 18 Improves Application Performance"を読む
Main thread and Long Tasks
- JSはシングルスレッド
- 実行に50ミリ秒以上かかるタスクは"long task"
This 50ms benchmark is based on the fact that devices must create a new frame every 16ms (60fps) to maintain a smooth visual experience.
らしい。
The 50ms benchmark allows the device to allocate resources to both rendering frames and performing other tasks, and provides an additional ~33.33ms for the device to perform other tasks while maintaining a smooth visual experience.
よくわからない...
一旦、章が終わるまでは読み進める
パフォーマンス計測に重要な2つの指標、"Total Blocking Time, and Interaction to Next Paint."
TBT
わかりやすい図
INP
To understand how the new React updates optimize for these measurements and thus improve the user experience, it's important to understand how traditional React works first.
これらの指標をSuspenseたちがどう改善していくかのStoryらしい、面白そう
パフォーマンス計測の話がよく理解できてないかもなので、linkされていた記事を読む
What are long tasks?
- long taskとは、メインスレッドを長時間独占してUIをフリーズさせる処理
- RAILモデルでは、100ms以内に目に見えるレスポンスを確実に返すために、50msでユーザー入力イベントを処理することを推奨している
- そうしないと、アクションとリアクションの間の接続が途切れる
Chrome Devtoolはlong taskを可視化できるらしい。
long taskの王道の対処法は、短いチャンクに処理を分割することらしい。のちの説明に効いてきそうなポイント。
Break all your work into small chunks (that run in < 50ms) and run these chunks at the right place and time; the right place may even be off the main thread, in a worker. Phil Walton's Idle Until Urgent is a good read on this topic. See also the optimize long tasks article for general strategies for managing and breaking up long tasks.
What is RAIL model?
RAIL is a user-centric performance model that provides a structure for thinking about performance. The model breaks down the user's experience into key actions (for example, tap, scroll, load) and helps you define performance goals for each of them.
冒頭ですでに魅力的に見える。
What RAIL stands for
レスポンスの時間とユーザーの体感が表になっていて面白い。
0.1秒くらいで動けばサクサクで、1秒かかると遅い
重要なガイドライン
- To ensure a visible response within 100 ms, process user input events within 50 ms. This applies to most inputs, such as clicking buttons, toggling form controls, or starting animations. This does not apply to touch drags or scrolls.
- Though it may sound counterintuitive, it's not always the right call to respond to user input immediately. You can use this 100 ms window to do other expensive work, but be careful not to block the user. If possible, do work in the background.
- For actions that take longer than 50 ms to complete, always provide feedback.
最初がuseTransitionっぽくて、最後がSuspenseっぽい
100ミリ秒以内に反応することが目標だが、そのうちの50ミリ秒は他のタスクに取られてしまうことがあるため、入力処理に使える時間は実際には残りの50ミリ秒だけ
一旦こんなもんでいいか。
Traditional React Rendering
A visual update in React is divided into two phases: the render phase and the commit phase.
進研ゼミでやったところだ!
In a traditional synchronous render, React would give the same priority to all elements within a component tree.
React would go ahead and render the tree in a single uninterruptible task,
A synchronous render is an “all-or-nothing” operation, where it’s guaranteed that a component that starts rendering will always finish.
従来のrenderingだと、"a single uninterruptible task" = “all-or-nothing” なので、long taskになりがちでしたよと。
提供されているDemoは"there’s a clear visual feedback delay"らしいのだが、サクサク動くのでわからない。ハイスペックなPC使ってると、「自分の環境ではサクサクだけどもユーザーの環境ではイマイチ」っていうことが往々にしてありそうなので、やはりパフォーマンス計測の仕組みを作らないとだな...。
6x遅くして手元で計測してみたけれども、long taskってこういうこと?イマイチ使い方わかってない
あ、TBT出てた
React 18 introduces a new concurrent renderer that operates behind the scenes. This renderer exposes some ways for us to mark certain renders as non-urgent.
a traditional synchronous renderとa new concurrent renderを対比させ、その差はpriorityの付け方にあると言っている。
synchronousとconcurrentの感覚の差がわからない
synchronous | ˈsiNGkrənəs |
adjective
1 existing or occurring at the same time: glaciations were approximately synchronous in both hemispheres.
2 Astronomy (of a satellite or its orbit) making or denoting an orbit around the earth or another celestial body in which one revolution is completed in the period taken for the body to rotate about its axis.
concurrent | kənˈkərənt |
adjective
existing, happening, or done at the same time: there are three concurrent art fairs around the city.
• (of two or more prison sentences) to be served at the same time.
• Mathematics (of three or more lines) meeting at or tending toward one point.
Both concurrent and synchronous refer to different types of processing in computing, but they each have a distinct meaning:
Concurrent Processing: Concurrent programming or processing refers to the ability of a system to execute multiple tasks or processes in overlapping time intervals. This doesn't necessarily mean that tasks are being performed at the exact same instant. Instead, they can be interweaved, with different tasks making progress over time. In concurrent systems, individual tasks often interact with each other and need to be coordinated in some way, such as through locks, semaphores, or other synchronization mechanisms. Concurrent execution can happen on single or multiple cores. The purpose of concurrency is to model interactions in a system and to increase the efficiency and responsiveness of software.
Synchronous Processing: Synchronous operations are those where tasks are performed in sequence, one after another. This means that a task must wait for the previous one to complete before it can start. In the context of network operations or I/O, synchronous also means that the system waits for the operation to complete before continuing with the rest of the program. This can cause blocking, which can reduce efficiency if the task being waited on takes a long time. The advantage of synchronous programming is that it's generally simpler to understand and reason about, because you don't have to worry about the complications of tasks interacting with each other in complex ways, as you do in concurrent or asynchronous programming.
synchronousが同期処理でconcurrentが並行処理っぽい(ChatGPTより)
In that case, React will yield back to the main thread every 5 milliseconds to see if there are more important tasks to handle instead,
この事実はReactのどこを見ればわかるのだろう
the concurrent renderer yields control back to the main thread at intervals of 5ms during the (re)rendering of low-priority components.
いやー、字面ではわかるんだけどこの感覚つかめない。計算機に対する感覚が足りてない...。
Additionally, the concurrent renderer is able to “concurrently” render multiple versions of the component tree in the background without immediately committing the result.
all-or-nothingの1点集中ではなくて、並行処理ができる...というより、「優先度の低いものを一時停止しておける」の方が実態に近いのだろうな。
Transitions
We can mark an update as non-urgent by using the startTransition function made available by the useTransition hook. This is a powerful new feature that allows us to mark certain state updates as “transitions”, indicating that they can lead to visual changes that could potentially disrupt user experience if they were rendered synchronously.
"indicating"のところから、transitionに関する明晰な説明。「synchronouslyに処理したらUXを損なうだろう」という意味付けであるという説明が、ここまでの文脈ときれいに接続している。
By wrapping a state update in startTransition, we can tell React that we’re okay with deferring or interrupting the rendering to prioritize more important tasks to keep the current user interface interactive.
"deferring"という言葉遣いは明らかにuseDeferredValue
と同じ文脈から来ている。こういうのほんとはPRとか追いかけられるといいんだろうな。
下記のコードにおいて、setSearchQuery(e.target.value)
がtransitionとしてマークされている。もしこれがsynchronouslyに処理されたならば、CityListの検索がinputのonChangeごとに発火してしまい、その処理が重いのでinput操作がカクついてしまう。
transtionとしてマークされていることで、setSearchQuery(e.target.value)
はdeferできる。ユーザーがinputしている間はそちらのレンダリングを優先して行い、そちらのほとぼりが冷めてからtranstionを処理してcommitする。だからカクつかない。
export default function SearchCities() {
const [text, setText] = useState("Am");
const [searchQuery, setSearchQuery] = useState(text);
const [isPending, startTransition] = useTransition();
return (
<main>
<h1><code>startTransition</code></h1>
<input
type="text"
value={text}
onChange={(e) => {
setText(e.target.value)
startTransition(() => {
setSearchQuery(e.target.value)
})
}} />
<CityList searchQuery={searchQuery} />
</main>
);
};
React Server Components
CSRやSSRでは、JSバンドルをまるごとClientに送る必要があった。しかしRSCは、シリアライズされたコンポーネントツリーをクライアントに送るのでJSバンドルをサーバーに置いておける。
これSSRのときはなんでこうしなかったのかよくわかってないんだよな...。Streamingを考えてなかったから?
This tells the bundler to add this component and its imports to the client bundle and tells React to hydrate the tree client-side to add interactivity.
'use client'でマークすることで、そのファイルとファイルの中のimportをclient bundleとする
地味に重要なポイント
Note: Framework implementations may differ. For example, Next.js will prerender Client Components to HTML on the server, similar to the traditional SSR approach. By default, however, Client Components are rendered similar to the CSR approach.
Ensuring that only the leaf-most node of the interactive component defines the "use client" directive. This may require some component decoupling.
コンポーネントの分割って split componentやとおもってたけれども、ネイティブはcomponent decouplingと呼ぶらしい。
Suspense
サーバーサイドの人はこの感じで直接DB触りたくないって言ってた。わかるけどそれは抽象化の度合いで解決でき、本質的な問題ではないだろう。
async function BlogPosts() {
const posts = await db.posts.findAll();
return '...';
}
export default function Page() {
return (
<Suspense fallback={<Skeleton />}>
<BlogPosts />
</Suspense>
)
}
When a component is suspended, for example because it’s still waiting for data to load, React doesn't just sit idle until the component has received the data. Instead, it pauses the rendering of the suspended component and shifts its focus to other tasks.
Suspendはただのidlingではなく、「優先度下げ」のマーキングでもあるということ、でいいのかな
React can also reprioritize components based on user interaction. For example, when a user interacts with a suspended component that's not currently being rendered, React suspends the ongoing render and prioritizes the component that the user is interacting with.
ほんまか?てか"user interacts with a suspended component that's not currently being rendered"てどういう状況?具体例がほしい。
まあいずれにせよSuspense大好きなので、「そうであったら嬉しい」で終わりではある。
data fetchingのところはいいや
読んでみて
めちゃくちゃ良かったな。特にSync vs Concを理解できてよかった。
自分への課題としては
- パフォーマンス計測の仕方
- 開発者ツールの使い方
- そもそもStreamingってどうなってるの
っていう基礎的なところとか、react.devでuseTransitionやuseDeferredValueのDocsを読むとかが挙げられる。