React18の新機能(Transition)を使用したスムーズなトランジション効果を確認
はじめに
今回は React18 の新機能Transition
について確認していきたいと思います。
Automatic Batchig についてはこちらの記事をご覧ください。
動作確認用リポジトリ
Transition とは?
トランジション(transition; 段階的推移)とは React における新たな概念であり、緊急性の高い更新 (urgent update) と高くない更新 (non-urgent update) を区別するためのものです。
緊急性の高い更新とはタイプ、クリック、プレスといったユーザ操作を直接反映するものです。
トランジションによる更新は UI をある画面から別の画面に段階的に遷移させるものです。
タイプ、クリック、プレスのような緊急性の高い更新は、物理的な物体の挙動に関する我々の直観に反しないよう、即座に反応する必要があり、そうでないと「おかしい」と認識されてしまいます。一方でトランジション内では、ユーザは画面上であらゆる中間の値が見えることを期待していません。
引用元:https://ja.react.dev/blog/2022/03/29/react-v18#new-feature-transitions
トランジションは優先度が高い変更と低い変更を区別して、優先度の高い変更を優先的に行う機能です。
例えば、ボタンをクリックしたときに、以下二つの変更を行う場合を考えてみます。
- ボタンの色を変更する(高優先度)
- メッセージを表示する(低優先度)
この場合、ボタンの色を変更することは、ユーザーの操作を直接反映する部分なので、優先度は高いと考えます。
一方で、メッセージを表示する変更は、ユーザーの操作に直接関わる部分ではないため、優先度は低いとします。
トランジション機能を使用することで、高優先度の変更である「ボタンの色を変更する」変更が、低優先度の変更である「メッセージを表示する」変更よりも優先的に実行されます。
Transition を使用したレンダリングコストの高いコンポーネントの改善と動作確認
10000 件のデータ表示をフィルタリングしている、レンダリングコストが高いコンポーネントを作成して動作確認をしてみます。
import { useState } from "react";
import MemberButton from "./MemberButton";
type Task = {
id: number;
title: string;
name: string;
description: string;
};
const member = {
a: "A",
b: "B",
c: "C",
};
const createTaskArray = (): Task[] => {
return Array(10000)
.fill("")
.map((_, index) => {
const addIndex = index + 1;
return {
id: addIndex,
title: `title${addIndex}`,
description: `description${addIndex}`,
name:
addIndex % 3 === 0
? member.a
: addIndex % 2 === 0
? member.b
: member.c,
};
});
};
const tasksArray = createTaskArray();
const filteringMember = (member: string) => {
if (member === "") return tasksArray;
return tasksArray.filter((task) => task.name === member);
};
const TransitionTest = () => {
const [selectMember, setSelectMember] = useState<string>("");
const [tasks, setTasks] = useState<Task[]>(tasksArray);
const clickMember = (member: string) => {
console.log("クリックされました!!");
setSelectMember(member);
setTasks(filteringMember(member));
};
return (
<>
<p className="text-3xl text-center mt-20">TransitionTest</p>
<div className="flex justify-center gap-5 mt-8">
<MemberButton
onClick={clickMember}
isActive={selectMember === member.a}
>
{member.a}
</MemberButton>
<MemberButton
onClick={clickMember}
isActive={selectMember === member.b}
>
{member.b}
</MemberButton>
<MemberButton
onClick={clickMember}
isActive={selectMember === member.c}
>
{member.c}
</MemberButton>
</div>
<div className="text-center mt-8">
<button
className="bg-gray-600 text-white py-2 px-4 m-auto inline-block"
onClick={() => clickMember("")}
>
リセット
</button>
</div>
<ul className={`grid gap-4 mt-8`}>
{tasks?.map((task) => (
<li key={task.id} className="bg-gray-300 w-80 m-auto p-3">
<p>タイトル: {task.title}</p>
<p>説明: {task.description}</p>
<p>名前: {task.name}</p>
</li>
))}
</ul>
</>
);
};
export default TransitionTest;
改善前
タブをクリックしたタイミングでコンソールが出力されていますが、背景色が変わるまでに時間がかかっていることが分かります。
操作している側からしたら使いづらいですね・・
useTransition を使用した改善
useTransition を import して、配列の分割代入でisPending
とstartTransition
を定義します。
優先度が低く、Transition をさせたい set 関数をstartTransition
の中に入れます。
またisPending
を使用することで startTransision
内で set 関数の state が更新中かどうかのフラグを boolean として返してくれます。
これを利用して、更新中は「更新中です 😌」というテキストを表示させてみることとします。
+ import { useState, useTransition } from "react";
import MemberButton from "./MemberButton";
type Task = {
id: number;
title: string;
name: string;
description: string;
};
const member = {
a: "A",
b: "B",
c: "C",
};
const createTaskArray = (): Task[] => {
return Array(10000)
.fill("")
.map((_, index) => {
const addIndex = index + 1;
return {
id: addIndex,
title: `title${addIndex}`,
description: `description${addIndex}`,
name:
addIndex % 3 === 0
? member.a
: addIndex % 2 === 0
? member.b
: member.c,
};
});
};
const tasksArray = createTaskArray();
const filteringMember = (member: string) => {
if (member === "") return tasksArray;
return tasksArray.filter((task) => task.name === member);
};
const TransitionTest = () => {
+ const [isPending, startTransition] = useTransition();
const [selectMember, setSelectMember] = useState<string>("");
const [tasks, setTasks] = useState<Task[]>(tasksArray);
const clickMember = (member: string) => {
console.log("クリックされました!!");
setSelectMember(member);
+ startTransition(() => {
+ setTasks(filteringMember(member));
+ });
};
return (
<>
<p className="text-3xl text-center mt-20">TransitionTest</p>
<div className="flex justify-center gap-5 mt-8">
<MemberButton
onClick={clickMember}
isActive={selectMember === member.a}
>
{member.a}
</MemberButton>
<MemberButton
onClick={clickMember}
isActive={selectMember === member.b}
>
{member.b}
</MemberButton>
<MemberButton
onClick={clickMember}
isActive={selectMember === member.c}
>
{member.c}
</MemberButton>
</div>
<div className="text-center mt-8">
<button
className="bg-gray-600 text-white py-2 px-4 m-auto inline-block"
onClick={() => clickMember("")}
>
リセット
</button>
</div>
+ {isPending ? (
+ <p className="text-center mt-12 text-xl">更新中です😌</p>
+ ) : (
+ <ul className={`grid gap-4 mt-8`}>
+ {tasks?.map((task) => (
+ <li key={task.id} className="bg-gray-300 w-80 m-auto p-3">
+ <p>タイトル: {task.title}</p>
+ <p>説明: {task.description}</p>
+ <p>名前: {task.name}</p>
+ </li>
+ ))}
+ </ul>
+ )}
</>
);
};
export default TransitionTest;
改善後
タブをクリックしてから、タグの背景色が変わるまでの時間が短くなり、とても使いやすくなりました!
また優先度の低い、絞り込み結果側の表示は少し遅れていることが分かります。
まとめ
今回は React18 の新機能Transition
の動作確認をしてみました。
実際にコードを動かしてみることで、優先度の高い変更と低い変更を区別して、優先度の高い変更を優先的に行うことができることが分かりました。
今後は Suspense などの機能についても確認していきたいです。
参考文献
Discussion