Closed14

React18

high-ghigh-g

https://qiita.com/Yuki_Oshima/items/b6ec2fb9f5b5d53381ad

上記によると、React18の具体的な新機能は以下

  • レンダリングのバッチ化
    • 複数の状態更新による再レンダリングをまとめて実行
    • パフォーマンス向上
  • トランジションの導入
    • 再レンダリングを非同期で処理が可能になった
    • ユーザーの操作を阻害せずに重いレンダリングを裏側で実行可能になった
    • startTransition
    • useTransitionでisPendingを取得可能
    • isPendingを利用することで、ローディング表示を切り替えることができる
  • <Suspense/>のサーバサイドレンダリング対応
high-ghigh-g
import {startTransition} from 'react';

// 同期
setInputValue(input);

// 非同期
startTransition(() => {
  setSearchQuery(input);
});

high-ghigh-g

メモ
Suspense, lazyloadが使えていないだけでなく、useMemoを雰囲気で使ってしまっている為、
Reactのパフォーマンスについての解像度が低い。

high-ghigh-g

React17とReact18の差分

React18へのバージョンアップについて

react-domのrender → react-dom/clientのcreateRoot()に変更

React 17

import { render } from 'react-dom';
const container = document.getElementById('app');
render(<App tab="home" />, container);

React 18

import { createRoot } from 'react-dom/client';
const container = document.getElementById('app');
const root = createRoot(container);
root.render(<App tab="home" />);

React周辺パッケージのアップグレード

framer-motion	4 → 6
react-dnd	15 → 16
@testing-library/react	12 → 13

型の変更

React.FC の props に暗黙的に children が含まれなくなったので、該当箇所で children を明示。

型の固定

@types/react、@types/react-dom のバージョンを固定する。

https://developers.prtimes.jp/2022/08/17/upgrade-to-react-18/

high-ghigh-g

React.lazy?Lazy Import?lazy loading?

React.lazy、lazy import、lazy loadingなどのキーワードが出てくる

React.lazyとlazy importとlazy loadingがごっちゃになっていたけど、
React.lazyをつかったdynamic importをlazy importと呼んでいて、結果的にloadingがlazyになっているということで落とし込めた。

https://qiita.com/KokiSakano/items/b6d4e6875443064032b4
https://qiita.com/kakken1988/items/9f14480c8a9f0abf7b3b

high-ghigh-g

書き方の変更点

  • rootの書き方
    • react-dom/client
    • ReactDOM.createRoot + render()
  • strictモードで2回実行
    • 18から全体のパフォーマンスが向上した為、アンマウント、再マウントが自動で行われる
    • 純粋関数で実装されていれば、特に問題ないことを示すため
  • トランジション
high-ghigh-g

StrictMode

Reactではレンダリングが2回実行
関数の冪等性を保証する実装が必要。

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
high-ghigh-g

Auto Batching

stateの更新が複数回呼ばれた際に更新をグループ化し、単一のレンダーとして処理するもの。
React17以前ではイベントハンドラの中は、これが適用されていたが、Promiseが実行されたあとのステート更新では、ステートの数だけ再レンダリングが実行されていた(バッチ処理が実行されていなかった)。

React18ではそれが改善されている。

high-ghigh-g

Transition

処理性能が低いマシンに対して有効な機能
優先度の高いステート更新を時間差で更新し、緊急性の高いステート更新を優先するためのもの
ユーザーの体験を劇的に変える

startTransitionをそのまま利用した場合

import { useState, startTransition } from 'react';

    const onClickAssignee = (assignee: string) => {
        setSelectedAssignee(assignee)

        // 緊急性が高くないステート更新(トランジションさせたいもの)をいれる
        startTransition(() => {
            setTaskList(filteringAssignee(assignee))
        })
    }

useTransitionを利用した場合

import { useState, useTransition } from 'react';

export const Component = () => {
  // 分割代入の第二引数がstartTransitionと全く同じ挙動
  // 第一引数のisPendingにtransition中かどうかのフラグが入る
  const [isPending, startTransition] = useTransition()

    const onClickAssignee = (assignee: string) => {
        setSelectedAssignee(assignee)
        startTransition(() => {
            setTaskList(filteringAssignee(assignee))
        })
    }
}
high-ghigh-g

useDeferredValue

useTransitionの場合、set関数を囲う必要があったが、
子コンポーネント側でTransition処理を扱いたい場合、set関数を囲うといったことがやりにくい。
そういった場合に useDefferedValue を利用する。

使い方は遅延をかけたいステートを一旦、useDefferedValueの引数として渡し、戻り値を利用する。

import type { Task } from './Transition'
import { useDeferredValue } from 'react'

type Props = {
    taskList: Task[];
}

export const TaskList = ({taskList}: Props) => {
    const deferredTaskList = useDeferredValue(taskList)
    return (
        <>
            {deferredTaskList.map((task) => (
                <div key={task.id} style={{width: '300px', margin: 'auto', background: 'lavender'}}>
                    <p>タイトル:{task.title}</p>
                    <p>担当:{task.assignee}</p>
                </div>
            ))}</>
    )
}
high-ghigh-g

Suspense

16.6で追加された機能だが、React.lazyと併用する必要があった。
React18からはReact.lazyの必要がない。

↓React.lazyと併用したこれまでの記述

import {lazy, Suspense} from 'react'
const OtherComponent = lazy(() => import('./OtherComponent'))

function MyComponent() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <OtherComponent />
    </Suspense>
  )
}

↓React18からのSuspense
React.lazyが不要になった。(ただし、SWR また TanstackQueryを用いる必要がある。)

import React, {Suspense} from "react";
import "./App.css";
import { ReactQuery } from "./components/ReactQuery";

function App() {
  return (
    <div className="App">
      <Suspense fallback={<div>Loading...</div>}>
        <ReactQuery />
      </Suspense>
    </div>
  );
}

export default App;

↓Suspenseで囲われているコンポーネント

import {useQuery} from '@tanstack/react-query';
import axios from 'axios';

type Album = {
    userId: number;
    id: number;
    title: string;
};

const fetchAlbums = async () => {
    const result = await axios.get<Album[]>('https://jsonplaceholder.typicode.com/albums')
    return result.data
}

export const ReactQuery = () => {
    // suspenseを利用するフラグが必要
    const { isLoading, error, data } = useQuery<Album[]>(['albums'], fetchAlbums, {suspense: true})

    // if(error) return <div>Error</div>
    // if(isLoading) return <div>Loading...</div>

    return (
        <div>
            <p>React Query</p>
            {data?.map((album) => (<p key={album.id}>{album.title}</p>))}
        </div>
    )
}
このスクラップは2ヶ月前にクローズされました