React基于路由进行代码分割

本文探讨了为何需要在React项目中进行代码分割和懒加载,以解决项目包体积大、加载时间长的问题。通过动态导入、React.lazy和Suspense组件,实现了按需加载路由对应的组件,从而提高首屏加载速度。文章还介绍了实现过程中的一些注意事项和最佳实践,包括路由鉴权和路由拦截。最后,作者分享了一份关于JavaScript和ES的笔记供读者领取。

这两天整理项目经历,看到这一个知识点,重新实现了一遍,顺便记录一下。

一、为什么要做代码分割和懒加载?

背景: 随着项目开发,业务功能增加,代码量随着增长,代码包体积日渐肥胖,尤其是整合了多种第三方库,导致代码包体积过大,加载时间长,性能下降。

对策: WebPack 等打包工具早有代码分离的特性来应对这种问题,将代码分离到不同的 bundle 中,需要时按需加载就可以极大改善加载时间长的问题。常见的代码分离方法有三种:

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。

今天我们就是采用动态导入来实现分包。

决定在哪引入代码分割需要一些技巧。需要确保选择的位置能够均匀地分割代码包而不会影响用户体验。

一个不错的选择是从路由开始。大多数网络用户习惯于页面之间能有个加载切换过程。

实现将代码按照路由进行分割,只在访问该路由的时候才加载该页面内容,可以提高首屏加载速度。

二、知识预知

1、import()

import :ES6语法,使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。

import()ES6语法,可用于动态引入模块,返回一个 Promise 对象。

WebPack解析代码时,遇到import()会作为一个分割点,将导入的模块作为一个单独的bundle打包。如果是使用脚手架 Create React App 搭建的项目,可直接使用此功能。

import("./a").then(res => {console.log(res);
}); 

这里我花了很多时间试错,经测试发现,import()语法如果是包含在函数或者循环内,webpack的代码分割会失效,所以后面我用了路由表配置的方式去实现,如果有更优雅的实现方式可以在评论区分享。

2、React.lazy

React.lazy 函数能让你像渲染常规组件一样处理动态引入(的组件)。React.lazy 接受一个函数,这个函数需要动态调用 import()。它必须返回一个 Promise,该 Promise 需要 resolve 一个 default export 的 React 组件。

const OtherComponent = React.lazy(() => import('./OtherComponent')); 

3、Suspense

然后应在 Suspense 组件中渲染 lazy 组件,如此使得我们可以使用在等待加载 lazy 组件时做优雅降级(如 loading 指示器等)。

import React, { useState, lazy, Suspense } from 'react'
import Loading from '@/component/Loading';
function App() {const [RouteRouter] = useState(() => {return lazy(() => import('@/routes/RouteRouterSplit'))})return <Suspense fallback={<Loading />}><RouteRouter /></Suspense>
} 

三、具体实现

路由表设计,我选择了最笨的方式实现

export const routerConfig = [{path: '/',component: lazy(() => import('@/pages'))}, {path: '/Login',component: lazy(() => import('@/pages/Login')),}, {path: '/Home',component: lazy(() => import('@/pages/Home'))} {path: '/Render',component: lazy(() => import('@/pages/Render'))}, {path: '/Test',component: lazy(() => import('@/pages/Test'))}
] 

为了更好用我还做了路由拦截路由鉴权

路由鉴权:采用 context 将路由权限向下传递,用 useContext 获取权限,并做筛选。

路由拦截: 用高阶组件对页面组件进行包裹,在页面加载前后调用处理函数

import React, { useState, useLayoutEffect, lazy, Suspense, useMemo } from 'react'
import Loading from '@/component/Loading';

export const Permission = React.createContext()
function App() {const [rootPermission, setRootPermission] = useState([])const [RouteRouter] = useState(() => {return lazy(() => import('@/routes/RouteRouterSplit'))})useLayoutEffect(() => {setRootPermission(['/','/NoPermission','/WriteDoc','/Home','/Login',])}, [])const config = useMemo(() => ({before: function () {// console.log('before');},after: function () {// console.log('after');},}), [])return <Permission.Provider value={rootPermission}><Suspense fallback={<Loading />}><RouteRouter config={config} /></Suspense></Permission.Provider>
}

export default App 
import { lazy, useContext, useLayoutEffect } from 'react';
import { Route, Routes } from 'react-router-dom'
import { Permission } from '@/App'

import { routerConfig } from './routerConfig'

const NoFound = lazy(() => import('@/component/NoFound'))

/**
 * 鉴权函数,判断此组件是否在权限范围内 (不同的鉴权方式可在此函数中修改)
 * @param {Array} permissionList
 * @param {string} componentName
 */
function authentication(permissionList, componentName) {return permissionList.indexOf(componentName) >= 0
}

/**
 * 路由拦截
 * @param {*} Component
 * @param {*} config
 * @returns
 */
function RouteInterception(Component, config) {const { before, after } = config || {}return function ProRouteComponent(props) {// const ref = useRef()// 进入路由前触发before && before()// 路由挂载之后触发useLayoutEffect(() => {after && after()}, [])return <Component {...(props || {})} />}
}

export default function RouteRouter(props) {// 获取权限数组const permissionList = useContext(Permission)const routes = routerConfig.filter(({ path }) => {// 权限筛选return authentication(permissionList, path)}).map(({ path, component: Component }) => {// 路由拦截Component = RouteInterception(Component, props.config)return <Routekey={path}path={path}element={<Component />}/>})return (<Routes>{routes}<Route path='*' element={<NoFound />} /></Routes>)
} 

打包完就是这样效果

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值