8. 前端框架
Redux 是什么
Redux 是 React 核心成员 Dan 本人开发的状态管理库。 Redux 是基于函数式编程思想,集中式管理状态(MobX 是分散式管理)
- 单一数据源:整个应用的全局 state 被储存在一颗 object tree 中,并且这个 Object tree 只存在于为唯一一个 store 中。
- state 是只读的:唯一改变 state 的方法就是触发 action。
- 使用纯函数执行修改。
redux 核心流程:
UI 触发 → dispatch(action) → reducer(state, action) → 返回新 state → 通知订阅者更新 UI
- createStore 创建 store 数据管理库
- reducer 初始化,修改状态函数,定义修改规则
- getState 获取状态值
- dispatch 提交更新
- 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 本地开发服务流程
- 项目初始化:读取并解析 vite.config.js 配置文件。
- 启动开发服务器:基于 express 启动 HTTP 服务器,
- ESM 支持:利用浏览器的原生 ESM 进行模块加载。
- 按需编译:实时编译请求的模块。
- 热模块替换 (HMR): 通过 WebSocket 实现模块的局部更新。
- Source Maps: 自动生成 Source Maps,便于调试。
Vite 构建流程
- 项目初始化:读取并解析 vite.config.js 配置文件。
- 入口解析:使用 Rollup 构建模块依赖图。
- 插件处理:通过插件系统进行代码转换、压缩和资源处理。
- Tree shaking: 移除未使用的代码。
- 代码拆分:将代码拆分成多个模块块,
- 生成输出:打包生成最终的输出文件。
- 资源优化:优化 CSS 和静态资源。
- 为静态资源添加内容哈希,便于缓存管理。
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 预构建的目的
两个目的:
- 非 esm 的依赖转为 esm,开发环境将所有代码视为 esm
- 性能考虑,内部模块的 esm 将转为 单一模块进行加载,比如
import {debounce} from 'lodash-es',vite 不会去请求 600+个 lodash 内置模块,而是合并成一个单一的模块文件,
预构建的输出将会写入 node_modules/.vite 中,触发重新预构建的:
- lock 文件更改
- vite config 更改
- patch package 更改
- NODE_ENV 更改了才会触发重新预构建
已预构建的依赖请求使用 HTTP 头 max-age=31536000, immutable 进行强缓存
umi
Umi 做了很多编译时的事,如果你用过 umi,应该了解 src 下有个 .umi 临时目录,这里存放的文件本是需要开发者自己写的,现在由框架或插件在编译时自动生成。比如在 pages 目录下新建文件即是路由,新建 access.ts 文件即是权限,在 locales 目录下新建文件即是国际化语言,等等。 通过预打包,Umi 把依赖的 node 数从 1309 降到 314,这带来的不仅有安全和稳定,还有安装提速、node_modules 目录瘦身、命令行启动提速、无 peerDependency 警告等等。