React Router 6.4 코드 분할
13847 단어 routerjavascriptreacttutorial
6.4
에서 새로운 "데이터 라우터"를 출시했습니다. 이 업데이트를 통해 마이그레이션할 수 없는 단일 페이지 응용 프로그램은 Remix가 제공하는 많은 멋진 기능을 활용할 수 있습니다.지금까지 최고의 기능은 병렬 데이터 가져오기, "Spinnageddon"네트워크 워터폴 제거 및 페이지 로드 개선입니다.
이 평행한 녹색 막대는 멋져 보입니다! 사용자도 기뻐할 것입니다.
모든 것이 훌륭하게 들리는데 무엇이 빠졌습니까?
새로운 데이터 라우터와 데이터가 필요한 경로는 애플리케이션의 최상위 수준에서 로더와 구성 요소를 정의해야 하며 전체 앱을 하나의 대규모 번들에 넣습니다 🤔
페이지에 도착했을 때 수백 MB의 JavaScript는 성능 면에서 절대 안 됩니다. 그렇다면 병렬 데이터 가져오기 및 콘텐츠 레이아웃 이동 없음의 이점을 유지하면서 데이터 라우터 애플리케이션을 코드 분할하려면 어떻게 해야 할까요?
옵션 1
동일한 파일에 로더 및 구성 요소가 있는 경로 모듈:
이 옵션은 모든 경로가 동일한 파일에서
Component
및 Loader
를 모두 내보낸 다음 Route Module
파일을 사용하여 지연 로드하고 이러한 파일을 동적으로 가져와서 코드 분할을 활성화하는 Remix 파일 규칙을 따릅니다.export async function loader(args) {
let actualLoader = await import("./actualModule").loader;
return actualLoader(args);
}
export const Component = React.lazy(() => import("./actualModule").default);
import * as Tasks from "./tasks.route";
// ...
<Route
path="/tasks"
element={
<React.Suspense fallback="loading...">
<Tasks.Component />
</React.Suspense>
}
loader={Tasks.loader}
/>;
이 결과 네트워크 폭포:
장점:
단점:
fetch
가 지연됩니다. 옵션 2
로더를 별도의 파일로 이동
구성 요소 파일에서 로더를 분리하면 데이터가 로드되는 동시에 구성 요소를 코드 분할하고 로더에서 가져올 수 있으므로 순차적 다운로드가 제거되고 병렬화를 최대한 활용할 수 있습니다.
actual-loader
를 별도의 파일로 이동합니다.export async function loader() {
// import the Component here but don't await it
import("./actualModule");
return await fetch('/api');
}
React.lazy가 실제로 자신을 마운트하고 호출할 때
import('./actualModule')
기존 다운로드에 래치되어야 합니다.Disclaimer: Most modern bundlers should support the theory above, but it is not guaranteed.
장점:
단점:
콘텐츠 레이아웃 이동 제거
두 옵션 모두 장점이 있지만 둘 다 콘텐츠 레이아웃 변경 문제가 있습니다. CLS를 제거하는 것은 React Router 및 Remix의 주요 성능 이점 중 하나이지만 이제 코드 분할을 도입했기 때문에 CLS를 피할 수 없게 만듭니다... 아니면 그렇습니까?
Remix 팀에서 코드 분할 시 CLS를 제거하는 방법에 대한 정말 멋진 요령을 제시했으며 가장 좋은 점은 위에서 언급한 두 솔루션 모두에 대해 작동한다는 것입니다.
// Assume you want to do this in your routes, which are in the critical path JS bundle
<Route path="lazy" loader={lazyLoader} element={<Lazy />} />
// And assume you have two files containing your actual load and component:
// lazy-loader.ts -> exports the loader
// lazy-component.ts -> exports the component
// We'll render the component via React.lazy()
let LazyActual = React.lazy(() => import("./lazy-component"));
function Lazy() {
return (
<React.Suspense fallback={<p>Loading component...</p>}>
<LazyActual />
</React.Suspense>
);
}
// The loader is where things get interesting, we need to load the JS chunk
// containing our actual loader code - BUT we also want to get a head start
// on downloading the component chunk instead of waiting for React.lazy() to
// kick it off. Waterfalls are bad! This opens up the possibility of the
// component chunk finishing _before_ the loader chunk + execution. If that
// happens we don't want to see a small CLS flicker from Suspense since the
// component _is already downloaded_!
export async function lazyLoader(...args) {
let controller = new AbortController();
/*
* Kick off our component chunk load but don't await it
* This allows us to parallelize the component download with loader
* download and execution.
*
* Normal React.lazy()
*
* load loader.ts execute loader() load component.ts
* -----------------> -----------------> ----------------->
*
* Kicking off the component load _in_ your loader()
*
* load loader.ts execute loader()
* -----------------> ----------------->
* load component.ts
* ----------------->
*
* Kicking off the component load _alongside_ your loader.ts chunk load
*
* load loader.ts execute loader()
* -----------------> ----------------->
* load component.ts
* ----------------->
*/
import("./lazy-component").then(
(componentModule) => {
if (!controller.signal.aborted) {
// We loaded the component _before_ our loader finished, so we can
// blow away React.lazy and just use the component directly. This
// avoids the flicker we'd otherwise get since React.lazy would need
// to throw the already-resolved promise up to the Suspense boundary
// one time to get the resolved value
LazyActual = componentModule.default;
}
},
() => {}
);
try {
// Load our loader chunk
let { default: loader } = await import("./lazy-loader");
// Call the loader
return await loader(...args);
} finally {
// Abort the controller when our loader finishes. If we finish before the
// component chunk loads, this will ensure we still use React.lazy to
// render the component since it's not yet available. If the component
// chunk finishes first, it will have overwritten Lazy with the legit
// component so we'll never see the suspense fallback
controller.abort();
}
}
Source Code
이 문제를 해결하는 데 도움을 준 Remix에 다시 한 번 감사드립니다! 모든 신용은 그에게 돌아갑니다.
Reference
이 문제에 관하여(React Router 6.4 코드 분할), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/infoxicator/data-routers-code-splitting-23no텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)