乐趣区

关于javascript:React-从零实践01后台-代码分割

导航

[[react] Hooks](https://juejin.im/post/684490…)

[[React 从零实际 01- 后盾] 代码宰割](https://juejin.im/post/687902…)
[[React 从零实际 02- 后盾] 权限管制](https://juejin.im/post/688148…)
[[React 从零实际 03- 后盾] 自定义 hooks](https://juejin.im/post/688713…)
[[React 从零实际 04- 后盾] docker-compose 部署 react+egg+nginx+mysql](https://juejin.im/post/689239…)
[[React 从零实际 05- 后盾] Gitlab-CI 应用 Docker 自动化部署](https://juejin.cn/post/689788…)

[[源码 -webpack01- 前置常识] AST 形象语法树](https://juejin.im/post/684490…)
[[源码 -webpack02- 前置常识] Tapable](https://juejin.im/post/684490…)
[[源码 -webpack03] 手写 webpack – compiler 简略编译流程](https://juejin.im/post/684490…)
[[源码] Redux React-Redux01](https://juejin.im/post/684490…)
[[源码] axios ](https://juejin.im/post/684490…)
[[源码] vuex ](https://juejin.im/post/684490…)
[[源码 -vue01] data 响应式 和 初始化渲染 ](https://juejin.im/post/684490…)
[[源码 -vue02] computed 响应式 – 初始化,拜访,更新过程 ](https://juejin.im/post/684490…)
[[源码 -vue03] watch 侦听属性 – 初始化和更新 ](https://juejin.im/post/684490…)
[[源码 -vue04] Vue.set 和 vm.$set](https://juejin.im/post/684490…)
[[源码 -vue05] Vue.extend](https://juejin.im/post/684490…)

[[源码 -vue06] Vue.nextTick 和 vm.$nextTick](https://juejin.im/post/684790…)

[[部署 01] Nginx](https://juejin.im/post/684490…)
[[部署 02] Docker 部署 vue 我的项目](https://juejin.im/post/684490…)
[[部署 03] gitlab-CI](https://juejin.im/post/684490…)

[[深刻 01] 执行上下文](https://juejin.im/post/684490…)
[[深刻 02] 原型链](https://juejin.im/post/684490…)
[[深刻 03] 继承](https://juejin.im/post/684490…)
[[深刻 04] 事件循环](https://juejin.im/post/684490…)
[[深刻 05] 柯里化 偏函数 函数记忆](https://juejin.im/post/684490…)
[[深刻 06] 隐式转换 和 运算符](https://juejin.im/post/684490…)
[[深刻 07] 浏览器缓存机制(http 缓存机制)](https://juejin.im/post/684490…)
[[深刻 08] 前端平安](https://juejin.im/post/684490…)
[[深刻 09] 深浅拷贝](https://juejin.im/post/684490…)
[[深刻 10] Debounce Throttle](https://juejin.im/post/684490…)
[[深刻 11] 前端路由](https://juejin.im/post/684490…)
[[深刻 12] 前端模块化](https://juejin.im/post/684490…)
[[深刻 13] 观察者模式 公布订阅模式 双向数据绑定](https://juejin.im/post/684490…)
[[深刻 14] canvas](https://juejin.im/post/684490…)
[[深刻 15] webSocket](https://juejin.im/post/684490…)
[[深刻 16] webpack](https://juejin.im/post/684490…)
[[深刻 17] http 和 https](https://juejin.im/post/684490…)
[[深刻 18] CSS-interview](https://juejin.im/post/684490…)
[[深刻 19] 手写 Promise](https://juejin.im/post/684490…)
[[深刻 20] 手写函数](https://juejin.im/post/684490…)
[[深刻 21] 算法 – 查找和排序](https://juejin.cn/post/690714…)

前置常识

一些单词

automatic:主动的
delimiter:分隔符
(automaticNameDelimiter)

lighthouse:灯塔
priority:优先级

vendor: 第三方
Suspense:悬念,悬停
fallback:进路

(1) 为什么要做代码宰割

  • (A 文件) 宰割成 (B 文件,C 文件)
  • 加载一个 2MB 的文件 A,和加载两个 1MB 的文件 B 和 C,因为存在异步加载并行加载,所以宰割后可能加载速度更快
  • 当批改代码时,不做代码宰割,只批改一小部分就会从新打包整个文件 A,生成新的文件 A',用户端就得从新加载整个文件 A';做代码宰割后,如果批改的代码在 B 文件,从新打包只须要打包 B 文件,同时用户端也只须要从新加载 B 文件,C 文件会被缓存
  • 还能够做按需加载,懒加载,路由懒加载,解决白屏等,最终晋升性能

(2) 代码宰割的三种角度

  • 拆分成 (业务代码 - 常常变动) 和 (第三方依赖代码 - 简直不变)

    • 业务代码会随着需要迭代等一直变动,而第三方依赖包根本不变,所以能够把第三方依赖包独自拆分打包,比方叫 vender.js 这个包根本不变,代码公布后,在用户端不必从新加载,而是浏览器会主动缓存
  • 依据路由进行切割,即路由懒加载

    • 比方进入首页的路由时,只须要加载首页的那局部代码
    • 首页有依赖其余模块,同步引入其实也能够在拆分成粒度更细的包,动静引入的能够通过 import()函数做动静加载拆分
  • 依据组件进行切割

    • 按路由形式进行代码切割,当 A 组件蕴含 C 组件,而 B 组件也蕴含 C 组件时,两个打包后的包,都会别离蕴含 C 组件的代码,造成冗余。
    • 按组件形式进行代码切割,则能防止下面的问题,然而由此带来的问题就是包的数量会急剧减少,须要开发者本人掂量利弊。

(3) import(specifier) 函数

  • import 加载模块时,不能做到像 require 那样的 运行时 加载模块,所以有了 import()函数 提案,动静加载模块
  • 参数:模块的门路
  • 返回值:返回一个 promise 对象
  • 实用场合:

    • 按需加载:在须要时在加载模块
    • 条件加载:在 if 语句中做条件加载
    • 动静模块门路:容许模块门路动静生成
  • 留神点:

    • import()返回的是一个 promise 实例对象,加载胜利后,模块对象 作为 .then() 办法的 参数 ,能够通过 解构赋值 获取输入接口
    • 如果模块有 default 输入接口,能够通过参数间接获取 default 接口,即 .then(moudle => module.default)
    • 通过加载多个模块
    • 当 Webpack 解析到 import()语法时,会主动进行代码宰割。如果你应用 Create React App,该性能已开箱即用,你能够立即应用该个性。当然也能够本人配置 webpack
    • 当应用 Babel 时,你要确保 Babel 可能解析动静 import 语法而不是将其进行转换。对于这一要求你须要 @babel/plugin-syntax-dynamic-import
    import(/* webpackChunkName: "AsyncTest" */'../../components/async-test') 
    .then(({default: AsyncTest}) => {...})
    .catch(err => console.log(err))
    
    
  • 代码拆分如何命名包名

    1. / webpackChunkName: “AsyncTest” /
    2. 应用插件 @babel/plugin-syntax-dynamic-import 就能够下面的 魔法正文 写法
    3. 通过 create-react-app 新建的我的项目中

       => babel-preset-react-app 依赖 => @babel/preset-env 依赖 => @babel/plugin-syntax-dynamic-import 
       

    同步加载(import)

  • 代码拆分如何命名包名

    1. 通过设置 optimization => splitchunks => cashGroups 来配置包名

(4) webpack => optimization

  • 对于用 webpack 构建的我的项目

    • 同步形式引入的模块(import),做代码宰割须要配置 optimization.splitchunks
    • 异步形式引入的模块(import() ),不须要做任何配置
  • optimization.splitchunks

    • automaticNameDelimiter

      • 指定拆分进去的包的连接符,起源组名称 连接符 入口名称(例如 vendors~main.js)
      • 默认是 ~
    • maxAsyncRequests

      • 按需加载时最大的并行申请数,默认 30
    • maxInitialRequests

      • 入口最大并行申请数,默认 30
    • chunks

      • string 或者 function
      • string 时,有效值为 allasyncinitial,all 示意同步和异步模块都进行拆分
      • function 时,能够无效的指定具体的哪些模块须要进行拆分
      • chunks 须要配合 cashGroups
    • cacheGroups

      • priority:定义每个组的优先级

        • 当一个模块满足多个组规定时,该模块将被打包到 priority 高的文件中
        • number 越大优先级越高,默认组的默认值是正数,自定义组的默认值是 0
      • filename:打包后模块的名字
      • reuseExistingChunk:boolean

        • 如果在之前的模块中引入过该模块 A,并打包了,当初又引入了模块 A,就复用之前曾经打包好的 A
    • minChunks(maxChunks)

      • 模块是否进行拆分的最小援用次数,即至多该模块被援用多少次才进行拆分
    • minSize(maxSize)

      • 模块是否进行拆分的最小大小(以字节为单位)
  • 官网阐明
  • SplitChunksPlugin

    optimization.splitchunks 默认配置项如下:module.exports = {
    //...
    optimization: {
      splitChunks: {
        chunks: 'all', // 对同步引入模块 和 异步引入模块都做代码宰割,all async initial
        minSize: 20000, // 当引入的模块大小大于 20KB 时,对该模块进行代码宰割
        minRemainingSize: 0,
        maxSize: 0, // 超过值后,对模块进行二次拆分
        minChunks: 1, // 引入的模块被援用一次时就进行代码宰割
        maxAsyncRequests: 30, // 最大的按需 (异步) 加载次数,整个我的项目最多进行 30 个代码宰割
        maxInitialRequests: 30, // 最大的初始化加载次数,首页最多进行 30 个代码宰割
        automaticNameDelimiter: '~', // 打包后的名字中的连接符,组名 + 连接符 + 入口文件名
        enforceSizeThreshold: 50000,
        cacheGroups: {
          defaultVendors: { // 组名称
            test: /[\\/]node_modules[\\/]/, // 匹配的范畴是 node_modules
            priority: -10 // 优先级,当一个模块满足多个组规定时,该模块将被打包到 priority 高的文件中
            // filename: 'vender.js' // 指定打包后模块的名字
          },
          default: { // 引入的模块,如果不满足下面的 defaultVendors 组规定的模块,就会进行 default 组的规定匹配
            minChunks: 2,
            priority: -20,
            reuseExistingChunk: true, // 之前曾经打包过该模块,就间接复用
          }
        }
      }
    }
    };

(5) 谬误边界

局部 UI 中的 JavaScript 谬误不应该毁坏整个应用程序。为了解决这个问题,React 引入了“谬误边界(Error Boundaries)”



react 中的代码宰割实现

(1) React.lazy 和 Suspense 实现代码宰割

    1. React.lazy(() => import()) 参数是一个函数,函数返回值必须是一个 promsie 对象,React.lazy 目前仅反对默认导出
    1. Suspense 组件,fallback 属性承受任何在组件加载过程中你想展现的 React 元素。你能够将 Suspense 组件置于懒加载组件之上的任何地位。你甚至能够用一个 Suspense 组件包裹多个懒加载组件。

      import React, {useState, Suspense} from 'react'
      import {Button} from 'antd';
      
      
      const Home = (props: any) => {console.log(props);
      
      const [AsyncTest, setAsyncTest] = useState<any>()
      const [AsyncTest2, setAsyncTest2] = useState<any>()
      
      // import()形式代码宰割
      const asyncLoad1 = () => {import(/* webpackChunkName: "AsyncTest" */'../../components/async-test')
       .then(({default: AsyncTest}) => {setAsyncTest((element: any) => element = AsyncTest)
       })
       .catch(err => console.log(err))
      }
      
      // React.lazy() + Suspense 形式代码宰割
      const asyncLoad2 = () => {const Test2 = React.lazy(() => import(/* webpackChunkName: "AsyncTest2" */'../../components/async-test2'))
        setAsyncTest2((component: any) => component = Test2)
      }
      
      return (
        <div>
       <header>home page bigscreen</header>
      
       <Button onClick={() => {asyncLoad1();
         asyncLoad2()}}> 异步加载 </Button>
      
       {AsyncTest ? AsyncTest() : null}
       {/* {AsyncTest ? <AsyncTest />: null} */}
      
       <Suspense fallback={<div>Loading...</div>}>
         {AsyncTest2 ? <AsyncTest2 /> : null}
       </Suspense>
        </div>
      )
      }
      
      export default Home

(2) 基于路由的代码宰割(React.laze)(Suspense)(react-router-config)

  • 和 vue 相似

    React.lazy   Suspense   react-router-config
    
    
    routes.js------------------
    const Login = lazy(() => import(/* webpackChunkName: 'Login' */'../pages/login'))
    const HomeBigScreen = lazy(() => import(/* webpackChunkName: 'HomeBigScreen' */'../pages/home/bigscreen.home'))
    const HomeAdmin = lazy(() => import(/* webpackChunkName: 'HomeAdmin' */'../pages/home/admin.home'))
    const Layout = lazy(() => import(/* webpackChunkName: 'Layout' */'../pages/layout'))
    const routes: RouteModule[] = [
    {
      path: '/login',
      component: Login,
    },
    {
      path: '/',
      component: Layout,
      routes: [ // -------------------------------------------------------- 嵌套路由
        {
          path: '/home-bigscreen',
          exact: true,
          component: HomeBigScreen,
        },
        {
          path: '/home-admin',
          exact: true,
          component: HomeAdmin,
        },
      ]
    }
    ]
    
    
    router.js------------------
    import {renderRoutes} from 'react-router-config' //--------------------- react-router-config 集中式路由解决方案
    const Router = () => {
    return (<Suspense fallback={<div>loading...</div>}> //------------------------ Suspense 包裹 lazy,Suspense.fallback
        <Switch>
          {renderRoutes(routes)}
        </Switch>
      </Suspense>
    
    )
    }
    
    
    layout.js----------------
    const render = () => {if (systemType === SYSTEMTYPE.ADMIN) {
        return (<div className={styles.layoutAdmin}>
            <header className={styles.header}>layout page admin</header>
            {renderRoutes(props.route.routes)} //--------------------------- renderRoutes(props.router.routes)
          </div>
        )
      } else {
        return (<div className={styles.layoutBigScreen}>
            {renderRoutes(props.route.routes)}
          </div>
        )
      }
    }

(3) 基于路由的代码宰割(第三方库 react-loadable)

  • https://github.com/jamiebuild…

我的项目源码

  • 我的项目源码
  • 部署成果预览地址

材料

官网教程: https://www.html.cn/create-re…
react 中做代码分片:https://juejin.im/post/684490…

退出移动版