8. 前端框架

12 min

Redux 是什么

Redux 是 React 核心成员 Dan 本人开发的状态管理库。 Redux 是基于函数式编程思想,集中式管理状态(MobX 是分散式管理)

  • 单一数据源:整个应用的全局 state 被储存在一颗 object tree 中,并且这个 Object tree 只存在于为唯一一个 store 中。
  • state 是只读的:唯一改变 state 的方法就是触发 action。
  • 使用纯函数执行修改。

redux 核心流程:

UI 触发 → dispatch(action) → reducer(state, action) → 返回新 state → 通知订阅者更新 UI

  1. createStore 创建 store 数据管理库
  2. reducer 初始化,修改状态函数,定义修改规则
  3. getState 获取状态值
  4. dispatch 提交更新
  5. subscribe 变更订阅 订阅 state 改变之后要做的事情,一般是 组件更新

redux 核心 API

  • createStore() 创建 store 实例
  • store.getState() 获取当前 state
  • store.dispatch() 发送 action 触发 reducer 更新
  • store.subscribe() 监听 state 变化
  • combineReducers() 合并多个 reducer

redux Reducer 是什么?为什么必须是纯函数?

Reducer 是一个函数 (state, action) => newState 必须是纯函数,以保证:

  • 相同输入一定得到相同输出(可预测)
  • 易于测试和调试
  • 支持时间旅行、撤销等功能

如何做异步请求?Redux 为什么默认不支持?

Redux 默认只支持同步 action,是为了保持 reducer 的纯粹性。 异步处理需借助中间件:

  • redux-thunk 函数 action → 异步逻辑在 action 中
  • redux-saga 使用 generator 写异步流程控制
  • redux-observable 基于 RxJS,支持流式异步

Redux 中间件原理?如何实现一个日志中间件?

中间件本质是对 dispatch 的“增强链式调用”。

描述 Redux-Toolkit?

RTK 是 Redux 官方推荐的,开箱即用的一个高效的 Redux 开发工具库;(底层也只是封装了一层 Redux 而已;) 它包括几个实用程序功能,这些功能可以简化最常见场景下的 Redux 开发,包括配置 store,定义 reducer,不可变的更新逻辑,甚至可以立即创建整个状态的“切片 slice”,而无需手动编写任何 action ,creator 或者 action type,还自带了一些常用的 Redux 插件,例如用于异步逻辑的 Redux Thunk 等。

Redux、MobX、Recoil,解决什么问题?说一下分别的设计原理?有什么优势?

这三者都是状态管理库,当组件内部状态无法满足需求的时候,比如需要实现组件间的状态共享,此时就可以定义一些外部状态,同时还要保证外部状态更新了,组件也要更新,状态管理库就是做这件事情的。

Redux 和 Recoil 都是 fb 内部开发的状态管理库;按照出现时机来说,先后分别是 Redux,MobX,Recoil,这也基本决定了他们的市场占有率。

  • Redux 基于函数式编程思想实现,集中式管理状态仓库,即一个项目通常只定义一个 store。
  • MobX 是一个响应式状态管理库,实现之初参考了 Vue 的设计思想。与 Redux 不同,MobX 奉行分散式管理状态,即你可以定义多个 store,其主要实现思路是拦截状态的 get 与 set 函数,get 的时候把状态标记为可观察变量,set 是时候让组件更新。

Redux 和 MobX 本身都是 js 库,如果想要和 react 一起使用,经常需要再使用一个 react 的绑定库,如 react-redux,mobx-react。

  • Recoil 与上面二者不同的是,Recoil 本身就是 React 的状态管理库,属于一体机,在 Recoil,状态定义的渐进式和分散式????

总结一下,Redux 是集中管理一个大状态,优点是比较专一,缺点是对于某些场景,比如不需要大量共享的时候,做不到特别灵活。而 MobX 和 Recoil 是可以分散式管理状态,因此相对 Redux 来说灵活性比较高。Recoil 由于又多了一层 selector,因此又可以渐进式定义状态,不过 fb 内部使用居多,目前还没发布正式版

zustand

mobX

DVA

DVA.js 是一个基于 Redux、Redux-saga 和 React-Router 封装的轻量级框架,目标是:

💡 简化 Redux 的使用,统一状态管理 + 异步处理 + 路由管理。

介绍下 vite

一句话:方案是迎合时代的;

vite 是一个新型的前端构建工具,在开发和构建中都显著提高了效率。核心思想是 bundleless,在开发阶段无需打包,而是利用浏览器原生 ESM 来进行模块加载; 前端模块化的演进过程大概是这样的:

  • 15 年以前:早期面向文件,文件满天飞;
  • 15-20:模块化渐渐被提出 讲一下模块化。..
  • 20 年以后,esm(ECMAScript Module)在现代浏览器中得到广泛的支持 ,使得前端开发可以直接使用 import/export,而无需借助打包工具,浏览器通过<script type='module'/>标签来加载 ESM,vite 正是利用了这一特性来实现无打包的开发环境, 比如早期 webpack 需要模块化规范支持,依赖分析后构建依赖图 deepGraph, bundleless 提倡少打包或者不打包(不打包基本不可能,esm 只是让 js 可以不打包,其他静态资源比如 css,png 等还是要打包)

在 vite 中

  • 开发环境:js 不用打包直接出,应用代码资源比如 ts,jsx,tsx,vue,css,woff 等要打包
    • 用 esbuild -> ts tsx jsx
    • postcss -> css
    • vite-plugin-xxx -> xxx

缺点:dts 文件需要自行处理,es5 以下

  • 生产环境:产物构建使用 rollup 打包;

为什么 vite 开发环境用 esbuild,生产环境用 rollup

尤大佬在直播中有提到,其实也想用,但是 esbuild 目前对生产包支持不够健壮,很多配置无法通过 esbuild 实现。所以目前而言,Rollup 是一个好选择,虽然远比 esbuild 慢。

另外,可以用 esbuild 作为压缩器,替代 terser,这样会更快,但是包的体积可能会有 5% - 10% 左右的增长,看用户取舍。

esbuild 不支持 es5; 尽管 esm 得到了广泛的支持,但是由于嵌套导入会导致额外的网络往返,如果在生产环境中发布未打包的 esm,效率依然是低下的;生产环境还是要进行打包,做 tree-shaking,懒加载啊,chunk split,vite 目前的插件 api 与 esbuild 是不兼容的,Vite 目前仍然依赖 Rollup 插件 API 和基础设施(如模块依赖图、代码分割等),因此短期内不会切换到 esbuild 插件体系。

开发环境中可以使用 Rollup 插件,因为 Vite 的插件系统兼容 Rollup 插件 API。 大部分 Rollup 插件都可以正常工作,尤其是与模块解析、代码转换相关的插件。 但需要注意,某些依赖 Rollup 特定功能(如输出阶段)的插件可能无法在开发环境中使用

Vite 本地开发服务流程

  1. 项目初始化:读取并解析 vite.config.js 配置文件。
  2. 启动开发服务器:基于 express 启动 HTTP 服务器,
  3. ESM 支持:利用浏览器的原生 ESM 进行模块加载。
  4. 按需编译:实时编译请求的模块。
  5. 热模块替换 (HMR): 通过 WebSocket 实现模块的局部更新。
  6. Source Maps: 自动生成 Source Maps,便于调试。

Vite 构建流程

  1. 项目初始化:读取并解析 vite.config.js 配置文件。
  2. 入口解析:使用 Rollup 构建模块依赖图。
  3. 插件处理:通过插件系统进行代码转换、压缩和资源处理。
  4. Tree shaking: 移除未使用的代码。
  5. 代码拆分:将代码拆分成多个模块块,
  6. 生成输出:打包生成最终的输出文件。
  7. 资源优化:优化 CSS 和静态资源。
  8. 为静态资源添加内容哈希,便于缓存管理。

vite 中可以使用 CommonJS 吗

业务代码推荐使用 esm; 第三方库如果是 cjs ,会在预构建的时候转为 esm; 如果一定要在业务代码中使用 cjs,可以使用插件

vite 常见 hook?

  • config 比如 alias
  • configResolved 用于解析 vite 配置后调用
  • configServe 给 dev server 添加自定义 middlew,比如 vite-plugin-mock
  • transformIndexHtml 注入变量,用来转换 html 内容,比如 vite-plugin-html
  • handleHotUpdate 执行自定义的 hmr

vite 为什么比 webpack 快?

webpack 以 entry 为起点做一个全量打包,中间涉及到构建 moduleGraph 这样的依赖分析,会进行大量的文件操作,文件内容解析,转换等

vite 会先使用 esbuild 进行预构建依赖,提前将第三方依赖转为 esm,dev server 会通过 middlewa 来对请求做拦截,对源文件做 resolve,load,transform,parse 等,再将转换之后的内容发送给浏览器

vite 预构建的目的

两个目的:

  1. 非 esm 的依赖转为 esm,开发环境将所有代码视为 esm
  2. 性能考虑,内部模块的 esm 将转为 单一模块进行加载,比如 import {debounce} from 'lodash-es' ,vite 不会去请求 600+个 lodash 内置模块,而是合并成一个单一的模块文件,

预构建的输出将会写入 node_modules/.vite 中,触发重新预构建的:

  1. lock 文件更改
  2. vite config 更改
  3. patch package 更改
  4. NODE_ENV 更改了才会触发重新预构建

已预构建的依赖请求使用 HTTP 头 max-age=31536000, immutable 进行强缓存

umi

Umi 做了很多编译时的事,如果你用过 umi,应该了解 src 下有个 .umi 临时目录,这里存放的文件本是需要开发者自己写的,现在由框架或插件在编译时自动生成。比如在 pages 目录下新建文件即是路由,新建 access.ts 文件即是权限,在 locales 目录下新建文件即是国际化语言,等等。 通过预打包,Umi 把依赖的 node 数从 1309 降到 314,这带来的不仅有安全和稳定,还有安装提速、node_modules 目录瘦身、命令行启动提速、无 peerDependency 警告等等。

Next.js