性能优化利器!React Router 6.4 中激动人心的特性

React Router 6.4 引入了“Data Router”这一概念,专注于将数据获取与渲染分离,以消除渲染+获取导致请求瀑布流问题以及随之而来的旋转加载图标,极大地提升了页面的 LCP 性能指标。

旋转加载图标

渲染+获取导致请求瀑布流问题

当路由不知道你的数据需求时,会发生请求瀑布流问题,只有子组件被渲染才能发现后续的数据需求:

将数据获取耦合到组件会导致渲染+获取的请求瀑布流问题

使用 React Router 6.4 “Data Router”允许你并行化获取所有数据:

路由获取并行化请求,消除缓慢的渲染+请求瀑布流

为了实现这一点,“Data Router”将你的路由定义移到渲染周期外,以便可以提前识别嵌套的数据需求。

// app.jsx


import Layout, { getUser } from `./layout`;

import Home from `./home`;

import Projects, { getProjects } from `./projects`;
import Project, { getProject } from `./project`;

const routes = [{
  path: '/',
  loader: () => getUser(),
  element: <Layout />,
  children: [{
    index: true,
    element: <Home />,
  }, {
    path: 'projects',
    loader: () => getProjects(),
    element: <Projects />,
    children: [{
      path: ':projectId',
      loader: ({ params }) => getProject(params.projectId),
      element: <Project />,
    }],
  }],
}]

但是这也有缺点。到目前为止,我们讨论了如何优化数据获取,但是我们也必须考虑如何优化 Javascript 包获取!通过上面的路由定义,虽然我们可以并行化请求所有数据,但会下载包含所有 loader 和组件的 Javascript 包会阻塞数据获取的开始。

考虑一个用户通过 / 路径访问你的站点:

JS包会阻塞数据获取

用户仍然需要下载 projects 和 :projectId 路由的 loader 和组件,即使用户不需要它们!在最坏的情况下,用户不访问这些路由,他们将永远不会用到它们。这对我们的用户体验来说不是理想的。

使用 React.lazy

React.lazy 提供了一个一流的原语来拆分组件树,但是它产生类似的渲染+获取导致的请求瀑布流问题。这是因为当你使用 React.lazy() 时,你为你的组件创建了一个异步 chunk,但是 React 直到它渲染该组件时才会开始获取该 chunk。

// app.jsx


const LazyComponent = React.lazy(() => import("./component"));

function App() {
  return (
    <React.Suspense fallback={<p>Loading lazy chunk...</p>}>
      <LazyComponent />
    </React.Suspense>
  );
}

React.lazy() 产生类似的渲染+获取的请求瀑布流问题

使用 Route.lazy

如果希望 React.lazy 能够与“Data Router”配合使用,我们需要在渲染周期之外引入。就像我们从渲染周期中提取数据获取一样,我们也希望从渲染周期中提取 chunk 获取。

“Data Router” 的路由的属性可以分为3类:

  • 路径匹配属性,例如pathindex 和 children
  • 数组加载或提交属性,例如 loader 和 action
  • 渲染属性,例如 elementerrorElement

下面是使用路由新的属性 lazy() 方法的示例:

// app.jsx


import Layout, { getUser } from `./layout`;

import Home from `./home`;


const routes = [{
  path: '/',
  loader: () => getUser(),
  element: <Layout />,
  children: [{
    index: true,
    element: <Home />,
  }, {
    path: 'projects',
    lazy: () => import("./projects"), // ? 惰性加载!
    children: [{
      path: ':projectId',
      lazy: () => import("./project"), // ? 惰性加载!
    }],
  }],
}]

// projects.jsx
export function loader = () => { ... };

export function Component() { ... }

// project.jsx
export function loader = () => { ... };

export function Component() { ... }

在初始加载时生成的网络图看起来像这样:

lazy() 方法允许我们拆分路由 chunk

现在我们的路由只包括那些我们认为对我们网站的初始入口必要的路由模块。然后,当用户单击指向 /projects/123 的链接时,我们通过 lazy() 方法并行获取这些路由并执行它们返回的 loader 方法:

我们在导航时惰性并行加载路由

高级使用和优化

这是最佳网络图吗?事实证明,不是!

在上面的这个例子中,我们的路由模块包括我们的 loader 和组件,这意味着我们在使用 loader 获取数据之前下载两者的内容。实际上, loader 通常非常小,触及大多数业务逻辑所在的外部 API。而组件定义了整个用户交互界面,包括随之而来的所有用户交互活动——而且它们可能会变得相当大。

巨大的路由文件阻止组件下载后面的数据获取

下载大型组件树的 JS 阻塞 loader(可能会对某些 API 进行 fetch())似乎并不明智?如果我们可以把这个?变成这个?呢?

我们可以通过将组件解压到它自己的文件来解除屏蔽数据获取

好消息是,通过很少的代码更改就可以实现!如果 loaderaction 是在路由上静态定义的,那么它将与 lazy() 并行执行。这允许我们通过将 loader 和组件分离到单独的文件中来将 loader 数据获取与组件下载分离:

const routes = [

  {

    path: "projects",

    async loader({ request, params }) {
      let { loader } = await import("./projects-loader");
      return loader({ request, params });
    },
    lazy: () => import("./projects-component"),
  },
];

也许你有一个 API,它知道如何根据请求 URL 获取给定路由的数据。你可以内联所有 loader,并在数据获取和组件(或路由模块)下载之间实现完全并行化。

const routes = [

  {

    path: "projects",

    loader: ({ request }) => fetchDataForUrl(request.url),
    lazy: () => import("./projects-component"),
  },
];

没有 loader chunk

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYpbF7KQ' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片