【React18 新機能】Transitionって何？【前編】

## はじめに

前回は`Automatic Batching`について解説しましたが,今回はReact新機能の`Transition`ついて解説していきます！

今回もReact18において重要な機能になっているので、今回の記事を見て概要だけでも
理解しておいてください！

以下前回記事

https://qiita.com/kim_t0814/items/3b60c6cd04cba33a7241

## Transitionとは...?
以下公式より引用

>トランジション（transition; 段階的推移）とは React における新たな概念であり、緊急性の高い更新 (urgent update) と高くない更新 (non-urgent update) を区別するためのものです。
>
>**・緊急性の高い更新とはタイプ、クリック、プレスといったユーザ操作を直接反映するものです。**
**・トランジションによる更新は UI をある画面から別の画面に段階的に遷移させるものです。**
>
>タイプ、クリック、プレスのような緊急性の高い更新は、物理的な物体の挙動に関する我々の直観に反しないよう、即座に反応する必要があり、そうでないと「おかしい」と認識されてしまいます。一方でトランジション内では、ユーザは画面上であらゆる中間の値が見えることを期待していません。

簡単にいうと**この変更は優先度が低い**ことをReact側に伝える機能になります。
優先度が高いものをReact側に伝えれるわけではなく、あくまで低いものを伝えることによって
優先度の高い、低いを区別できるようにしてるのがポイントです。

## 実際にコードを動かしてみよう
transitionの機能を確認するために、
今回はブログの作成者をクリックしたらフィルタリングされるサンプルアプリを作成します。

React18の環境下で以下のファイルを作成して行ってください。

```tsx:src/components/Transition.tsx
import { useState, useTransition } from 'react';
import { Avatar } from "./Avatar";
import { BlogList } from './BlogList';

export type Blog = {
  id: number;
  title: string;
  author: string;
}

const member = {
  a: '田中',
  b: '山田',
  c: '佐藤',
}

const generateDummyBlogs = (): Blog[] => {
  return Array(5000).fill('').map((_, index) => {
    const addedIndex = index + 1
    return {
      id: addedIndex,
      title: `タイトル${addedIndex}`,
      author: addedIndex % 3 === 0 ? member.a : addedIndex % 3 === 1 ? member.b : member.c,
    }
  })
}

const blogs = generateDummyBlogs();

const filteringAssignee = (author: string) => {
  if (author === '') return blogs;
  return blogs.filter((Blog) => Blog.author === author);
}

export const Transition = () => {
  const [selectedAssignee, setSelectedAssignee] = useState<string>('');
  const [blogList, setBlogList] = useState<Blog[]>(blogs);

  const onClickAssignee = (author: string) => {
    console.log('clickしたよ')
    setSelectedAssignee(author);
    setBlogList(filteringAssignee(author));

  }
  return (
    <div>
      <p>ブログ一覧</p>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <Avatar isSelected={selectedAssignee === member.a} onClick={onClickAssignee}>{member.a}</Avatar>
        <Avatar isSelected={selectedAssignee === member.b} onClick={onClickAssignee}>{member.b}</Avatar>
        <Avatar isSelected={selectedAssignee === member.c} onClick={onClickAssignee}>{member.c}</Avatar>
      </div>
      <br />
      <button onClick={() => onClickAssignee('')}>リセット</button>
      <br />
      <br />
      <BlogList blogList={blogList} />
    </div >
  )
}

```

```tsx:src/components/Avatar.tsx
import type { Blog } from "./Transition"

type Props = {
  blogList: Blog[]
}

export const BlogList = ({ blogList }: Props) => {
  return (
    <>
      {
        blogList.map((blog) => (
          <div key={blog.id} style={{ width: '500px', margin: 'auto ', background: 'pink ', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', borderRadius: '10px' }}>
            <p> タイトル:{blog.title}</p>
            <p>作成者:{blog.author}</p>
          </div >
        ))
      }
    </>
  )
}
```

```tsx:src/components/BlogList.tsx
import type { Blog } from "./Transition"

type Props = {
  blogList: Blog[]
}

export const BlogList = ({ blogList }: Props) => {
  return (
    <>
      {
        blogList.map((blog) => (
          <div key={blog.id} style={{ width: '500px', margin: 'auto ', background: 'pink ', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', borderRadius: '10px' }}>
            <p> タイトル:{blog.title}</p>
            <p>作成者:{blog.author}</p>
          </div >
        ))
      }
    </>
  )
}

```

呼び出しも元のApp.tsxは下記のとおり記述してください。
```tsx:App.tsx
import './App.css';
import { Transition } from './components/Transition';

function App() {


  return (
    <div className="App">
      <Transition />
    </div>
  );
}

export default App;

```

そうすると、以下に作者の名前をクリックするとフィルタリングする
簡易的なアプリが作成されたかと思います。

![download.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636995/3073431b-3c86-e99e-f617-4b1d041fc5e5.gif)

今回作成したアプリケーションは、見かけ上スペックの高いPCでは動作として問題なく見えますが、
性能の低いPCの場合は問題が浮き彫りになります。

今回は擬似的にPCの性能を下げて確認してみます！

![スクリーンショット 2023-08-18 1.01.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636995/17bc4a58-b30b-208b-2b34-1d10d7bc05ff.png)

Google Chromeの開発者ツールから`パフォーマンス→CPU`で6✖︎減速を選ぶと分かりやすいです。

`src/components/Transition.tsx`のクリックした際に発火する関数の`onClickAssignee`
**clickしたよ**という文言を発火するようにして確認します

![GIF トラン.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636995/5f4891c6-6676-9761-c0bd-f99fa975678e.gif)

上記のように、押してからフィルタリング結果が出るまでに1.5秒前後かかってしまいます。
正直言ってユーザー体験が悪いかなと思います。

理想系としては、「田中」を押した際に押したという判定に関してはユーザーにすぐわかるようにしたほうが良いと思います。
フィルタリングに関しては時間がかかるため、ステート更新が出来次第反映していくみたいな動きにすることで
ユーザーにより直感的にアプリを使ってもらえるUXを提供することができます。

それではまた、実際にコードを追加していきましょう。

```tsx:src/components/Transition.tsx
import { useState, startTransition } from 'react';
import { Avatar } from "./Avatar";
import { BlogList } from './BlogList';

export type Blog = {
  id: number;
  title: string;
  author: string;
}

const member = {
  a: '田中',
  b: '山田',
  c: '佐藤',
}

const generateDummyBlogs = (): Blog[] => {
  return Array(5000).fill('').map((_, index) => {
    const addedIndex = index + 1
    return {
      id: addedIndex,
      title: `タイトル${addedIndex}`,
      author: addedIndex % 3 === 0 ? member.a : addedIndex % 3 === 1 ? member.b : member.c,
    }
  })
}

const blogs = generateDummyBlogs();

const filteringAssignee = (author: string) => {
  if (author === '') return blogs;
  return blogs.filter((Blog) => Blog.author === author);
}

export const Transition = () => {
  const [selectedAssignee, setSelectedAssignee] = useState<string>('');
  const [blogList, setBlogList] = useState<Blog[]>(blogs);

  const onClickAssignee = (author: string) => {
    console.log('clickしたよ')
    setSelectedAssignee(author);
    startTransition(() => {
      setBlogList(filteringAssignee(author));
    });
  }
  return (
    <div>
      <p>ブログ一覧</p>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <Avatar isSelected={selectedAssignee === member.a} onClick={onClickAssignee}>{member.a}</Avatar>
        <Avatar isSelected={selectedAssignee === member.b} onClick={onClickAssignee}>{member.b}</Avatar>
        <Avatar isSelected={selectedAssignee === member.c} onClick={onClickAssignee}>{member.c}</Avatar>
      </div>
      <br />
      <button onClick={() => onClickAssignee('')}>リセット</button>
      <br />
      <br />
      <BlogList blogList={blogList} />
    </div >
  )
}

```

使い方は簡単で、中に関数をとってトランジションしたい関数を`startTransition`の中に記述します。
あくまで**緊急性の高くない**ものをするということは注意してください。

山田、田中の順にクリックしていきます。

![startTran.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636995/1b7ea2b5-1f83-64ed-cb7e-875ba0608b8d.gif)

上記を見るとわかると思いますが、時間差でボタンの枠が光った後にフィルタリングされると思います。
authorのstate更新については即座に行われていますが、ブログのフィルタリングについては時間差で
実行されています。

これで、押して反応が出た瞬間にユーザーは反映されたことがすぐにわかりユーザー体験の
向上につながると思います。

これがTransitionという機能になっていきます。

```tsx:src/components/Transition.tsx
import { useState, useTransition } from 'react';
import { Avatar } from "./Avatar";
import { BlogList } from './BlogList';

export type Blog = {
  id: number;
  title: string;
  author: string;
}

const member = {
  a: '田中',
  b: '山田',
  c: '佐藤',
}

const generateDummyBlogs = (): Blog[] => {
  return Array(5000).fill('').map((_, index) => {
    const addedIndex = index + 1
    return {
      id: addedIndex,
      title: `タイトル${addedIndex}`,
      author: addedIndex % 3 === 0 ? member.a : addedIndex % 3 === 1 ? member.b : member.c,
    }
  })
}

const blogs = generateDummyBlogs();

const filteringAssignee = (author: string) => {
  if (author === '') return blogs;
  return blogs.filter((Blog) => Blog.author === author);
}

export const Transition = () => {
  const [isPending, startTransition] = useTransition();
  const [selectedAssignee, setSelectedAssignee] = useState<string>('');
  const [blogList, setBlogList] = useState<Blog[]>(blogs);
  // const [isShowList, setIsShowList] = useState<boolean>(false);

  const onClickAssignee = (author: string) => {
    console.log('clickしたよ')
    setSelectedAssignee(author);
    startTransition(() => {
      setBlogList(filteringAssignee(author));
    });
  }
  return (
    <div>
      <p>ブログ一覧</p>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <Avatar isSelected={selectedAssignee === member.a} onClick={onClickAssignee}>{member.a}</Avatar>
        <Avatar isSelected={selectedAssignee === member.b} onClick={onClickAssignee}>{member.b}</Avatar>
        <Avatar isSelected={selectedAssignee === member.c} onClick={onClickAssignee}>{member.c}</Avatar>
      </div>
      <br />
      <button onClick={() => onClickAssignee('')}>リセット</button>
      <br />
      <br />
      {/* <button onClick={() => setIsShowList(!isShowList)}>表示/非表示</button> */}
      <BlogList blogList={blogList} />
    </div >
  )
}

```

### useTransitionについて
`useTransition`とはトランジション中にユーザーにインタラクションを返すを返すためのフックになります。
下記のようにコードを変更して`isPending`を使用するようにしてください。

```tsx:src/components/Transition.tsx
import { useState, useTransition } from 'react';
import { Avatar } from "./Avatar";
import { BlogList } from './BlogList';

export type Blog = {
  id: number;
  title: string;
  author: string;
}

const member = {
  a: '田中',
  b: '山田',
  c: '佐藤',
}

const generateDummyBlogs = (): Blog[] => {
  return Array(5000).fill('').map((_, index) => {
    const addedIndex = index + 1
    return {
      id: addedIndex,
      title: `タイトル${addedIndex}`,
      author: addedIndex % 3 === 0 ? member.a : addedIndex % 3 === 1 ? member.b : member.c,
    }
  })
}

const blogs = generateDummyBlogs();

const filteringAssignee = (author: string) => {
  if (author === '') return blogs;
  return blogs.filter((Blog) => Blog.author === author);
}

export const Transition = () => {
  const [isPending, startTransition] = useTransition();
  const [selectedAssignee, setSelectedAssignee] = useState<string>('');
  const [blogList, setBlogList] = useState<Blog[]>(blogs);

  const onClickAssignee = (author: string) => {
    console.log('clickしたよ')
    setSelectedAssignee(author);
    startTransition(() => {
      setBlogList(filteringAssignee(author));
    });
  }
  return (
    <div>
      <p>ブログ一覧</p>
      <div style={{ display: 'flex', justifyContent: 'center' }}>
        <Avatar isSelected={selectedAssignee === member.a} onClick={onClickAssignee}>{member.a}</Avatar>
        <Avatar isSelected={selectedAssignee === member.b} onClick={onClickAssignee}>{member.b}</Avatar>
        <Avatar isSelected={selectedAssignee === member.c} onClick={onClickAssignee}>{member.c}</Avatar>
      </div>
      <br />
      <button onClick={() => onClickAssignee('')}>リセット</button>
      <br />
      <br />
      <BlogList blogList={blogList} isPending={isPending} />
    </div >
  )
}

```

`isPending`はstartTransision内でset関数のstateが更新中かどうかをフラグをbooleanとして返してくれます。
このフラグを用いてユーザーに対してインタラクションを提供することができます。

```tsx:src/components/BlogList.tsx
import type { Blog } from "./Transition"

type Props = {
  blogList: Blog[]
  isPending: boolean
}

export const BlogList = ({ blogList, isPending }: Props) => {
  return (
    <>
      {
        blogList.map((blog) => (
          <div key={blog.id} style={{ width: '500px', margin: 'auto ', background: 'pink ', boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)', borderRadius: '10px', opacity: isPending ? 0.5 : 1 }}> 
            <p> タイトル:{blog.title}</p>
            <p>作成者:{blog.author}</p>
          </div >
        ))
      }
    </>
  )
}

```
`opacity: isPending ? 0.5 : 1 }}`として、更新中のものは半透明にします。

そうすると...

![ispending.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/636995/bd6fe08b-b83e-c007-2e94-b9aa4818d95a.gif)

「山田」を押すとフィルタリングされる前にユーザーに直感的にわかるようになるかと思います。
ここについては、ローディング中用のコンポーネントを表示したり今回のようにCSSを変更したりと
自由に見せることだができます！！

## 最後に
いかがでしたでしょうか。
今回はtransitionの中の`useTransition`について解説させていただきました。

実際にドキュメントを見てスッと頭に入ってこないことも実コードを用いて、プレビューを表示することによって
理解出来ることも多々あります。

次回はおまけとして、Transitionのhookの一つである`useDeferredValue`をまとめていこうと思います💪
## 参考
https://ja.legacy.reactjs.org/blog/2022/03/29/react-v18.html

https://www.udemy.com/course/react_v18/

https://react.dev/blog/2022/03/08/react-18-upgrade-guide