React Router v6, React Lazy 및 Suspense를 사용한 코드 분할(간단히 말해서)

반응 및 SPA
React 프레임워크는 별도의 구성 요소 또는 모듈에서 단일 페이지 애플리케이션(SPA)을 구축하는 것으로 알려져 있습니다. 이를 수행하는 방법은 다양한 구성 요소를 파일에서 가져와 단일 파일 또는 번들로 병합하는 '번들링' 프로세스를 통해 이루어집니다. 이 단일 파일은 웹 페이지에 추가되고 사용자의 브라우저에 애플리케이션으로 로드됩니다.

코드 분할 - 이것은 무엇을 의미합니까?
애플리케이션을 구축할 때 번들 크기를 가능한 한 작게 유지하는 것이 중요합니다. 그 이유는 특히 인터넷 연결 상태가 좋지 않은 지역에서 브라우저가 그림을 그리거나 로드하는 데 큰 파일이 꽤 오래 걸리기 때문에 web vitals 및 사용자 경험에 부정적인 영향을 미칠 수 있기 때문입니다.
소규모 애플리케이션의 경우 이는 문제가 되지 않습니다. 그러나 애플리케이션의 크기가 커지고 사용되는 라이브러리 및 프레임워크의 수가 증가함에 따라 클라이언트 측에서 번들을 분할해야 합니다. 이를 클라이언트 측 코드 분할이라고 합니다.

Webpack , Rollup , Browserify 및 기타 번들링 도구를 사용하여 코드를 분할하는 몇 가지 수동 방법이 있습니다. 그러나 React는 이를 해결하는 데 도움이 되는 기능인 React.Lazy 및 Suspense를 제공했습니다.

공식 Reactdocumentation에서 의역:

React.Lazy lets you render a dynamic import as a regular component. It takes a function that calls a dynamic import() and returns a Promise which resolves to a module with a default export containing a React Component.

This lazy component must also be rendered in a Suspense component; which provides fallback content (a React element) to show while the lazy component is loading.



클라이언트 측 라우팅에 React Router v6를 사용하는 예를 들어 보겠습니다. 코스 목록과 코스 점수를 표시하는 기본 학생 대시보드를 구축합니다.

완료되면 다음과 같이 표시됩니다.



먼저 Create-React-App 으로 새로운 반응 프로젝트를 생성합니다. TypeScript를 사용하고 있으므로 다음을 실행합니다.
npx create-react-app my-app --template typescript
npm i react-router-dom
내 App.tsx 파일은 다음과 같습니다.

import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;



그리고 내 index.tsx

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();



대시보드 페이지:

import React from "react";
import { Link, Outlet } from "react-router-dom";

const Dashboard = () => {
  return (
    <div style={{ padding: "1rem" }}>
      <h1>Dashboard Header</h1>
      <hr style={{ borderWidth: 1 }} />
      <Link to="/courses" style={{ marginBottom: "1rem" }}>
        View your courses
      </Link>
      <br />
      <Link to="/results">Check your results</Link>
      <Outlet />
    </div>
  );
};

export default Dashboard;


과정 페이지:

import React from "react";

const UserCourses = () => {
  return (
    <div style={{ padding: "1rem" }}>
      <h4>Your Courses</h4>
      <ul>
        <li>Mathematics</li>
        <li>English</li>
        <li>Physics</li>
        <li>History</li>
      </ul>
    </div>
  );
};

export default UserCourses;


결과 페이지:

import React from "react";

type resultsType = {
  course: string;
  score: number;
  comments: string;
};

const UserResults = () => {
  const results: resultsType[] = [
    {
      course: "Mathematics",
      score: 50,
      comments: "Pass",
    },
    {
      course: "English",
      score: 67,
      comments: "Good",
    },
    {
      course: "Physics",
      score: 75,
      comments: "Good",
    },
    {
      course: "History",
      score: 85,
      comments: "Excellent",
    },
  ];

  return (
    <div style={{ padding: "1rem" }}>
      <h4>Your Results</h4>
      <table>
        <thead>
          <tr>
            <th style={{ textAlign: "start" }}>Course</th>
            <th style={{ padding: "0.5rem 1rem" }}>Score</th>
            <th>Comments</th>
          </tr>
        </thead>
        <tbody>
          {results.map((person: resultsType, id: number) => {
            const { course, score, comments } = person;

            return (
              <tr key={id}>
                <td>{course}</td>
                <td style={{ padding: "0.5rem 1rem" }}>{score} 
                </td>
                <td>{comments}</td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
};

export default UserResults;


이제 React Router를 구현합니다.
여기 index.tsx에 '브라우저 라우터'를 추가했습니다.

...
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>


그런 다음 해당 페이지를 App.tsx로 가져올 수 있습니다.

...
    <Routes>
      <Route path="/" element={<Dashboard />}>
        <Route path="/courses" element={<UserCourses />} />
        <Route path="/results" element={<UserResults />} />
      </Route>
      <Route
        path="*"
        element={
          <div style={{ padding: "1rem" }}>
            <h3>Page Not Found!</h3>
          </div>
        }
      />
    </Routes>


현재 1단계가 완료되었습니다. 이것은 필요에 따라 라우팅되는 기본 페이지이지만 아직 지연 로딩이 없습니다.

React.lazy() 및 Suspense를 활용하려면 페이지를 동적으로 가져와야 합니다.

// import dynamically
const UserCourses = React.lazy(() => import("./pages/UserCourses"));
const UserResults = React.lazy(() => import("./pages/UserResults"));


그리고 폴백과 함께 Suspense 구성 요소를 추가하겠습니다.

<Suspense
  fallback={
   <div className="loader-container">
    <div className="loader-container-inner">
     <RollingLoader />
    </div>
   </div>
   }
  >
  <UserCourses />
</Suspense>


App.tsx는 다음과 같이 되었습니다.

...
     <Routes>
      <Route path="/" element={<Dashboard />}>
        <Route
          path="/courses"
          element={
            <Suspense
              fallback={
                <div className="loader-container">
                  <div className="loader-container-inner">
                    <RollingLoader />
                  </div>
                </div>
              }
            >
              <UserCourses />
            </Suspense>
          }
        />
        <Route
          path="/results"
          element={
            <Suspense
              fallback={
                <div className="loader-container">
                  <div className="loader-container-inner">
                    <RollingLoader />
                  </div>
                </div>
              }
            >
              <UserResults />
            </Suspense>
          }
        />

        {/* <Route path="/courses" element={<UserCourses />} />
        <Route path="/results" element={<UserResults />} /> */}
      </Route>
      <Route
        path="*"
        element={
          <div style={{ padding: "1rem" }}>
            <h3>Page Not Found!</h3>
          </div>
        }
      />
    </Routes>


이것은 초기 페인트에서 사용자가 링크를 클릭할 때까지 브라우저가 해당 페이지를 로드하지 않음을 의미합니다. 사용자에게는 페이지가 로드되는 동안에만 로드 아이콘이 표시되며 이는 대체 콘텐츠입니다. 완료되면 페이지의 콘텐츠가 표시됩니다. 이것은 초기 페인트에서만 발생하며 다시는 발생하지 않습니다.

이제 느리게 로드되는 구성 요소가 있습니다. 그러나 이 코드는 매우 반복적이며 페이지의 경로를 소품으로 허용하는 Suspense Wrapper를 빌드하여 더욱 최적화할 수 있습니다.

서스펜스 래퍼:

import React, { Suspense } from "react";

import { ReactComponent as RollingLoader } from "../assets/icons/rolling.svg";

interface SuspenseWrapperProps {
  path: string;
}

const SuspenseWrapper = (props: SuspenseWrapperProps) => {
  const LazyComponent = React.lazy(() => import(`../${props.path}`));

  return (
    <Suspense
      fallback={
        <div className="loader-container">
          <div className="loader-container-inner">
            <RollingLoader />
          </div>
        </div>
      }
    >
      <LazyComponent />
    </Suspense>
  );
};

export default SuspenseWrapper;


마지막으로 App.tsx는 다음과 같습니다.

import React from "react";
import { Route, Routes } from "react-router-dom";

import "./App.css";
import Dashboard from "./pages/Dashboard";
import SuspenseWrapper from "./components/SuspenseWrapper";

function App() {
  return (
    <Routes>
      <Route path="/" element={<Dashboard />}>
        <Route
          path="/courses"
          element={<SuspenseWrapper path="pages/UserCourses" />}
        />
        <Route
          path="/results"
          element={<SuspenseWrapper path="pages/UserResults" />}
        />

        {/* <Route path="/courses" element={<UserCourses />} />
        <Route path="/results" element={<UserResults />} /> */}
      </Route>
      <Route
        path="*"
        element={
          <div style={{ padding: "1rem" }}>
            <h3>Page Not Found!</h3>
          </div>
        }
      />
    </Routes>
  );
}

export default App;




대체 구성 요소는 로드하는 동안 표시되는 녹색 롤링 아이콘입니다.

전체 저장소here를 찾을 수 있습니다.
읽고 행복한 코딩에 감사드립니다!

P.S.: 의견이나 제안이 있으시면 주저하지 마시고 아래에 공유해 주십시오. 읽고 싶습니다.

좋은 웹페이지 즐겨찾기