腕表之家网站,口碑营销案例分析,正规推广平台有哪些,最牛html5网站建设LigaAI 的评论编辑器、附件展示以及富文本编辑器都支持在 Vue2#xff08;Web#xff09;与 Vue3#xff08;VSCode、lDEA#xff09;中使用。这样不仅可以在不同 Vue 版本的工程中间共享代码#xff0c;还能为后续升级 Vue3 减少一定阻碍。 那么#xff0c;同时兼容 Vue…LigaAI 的评论编辑器、附件展示以及富文本编辑器都支持在 Vue2Web与 Vue3VSCode、lDEA中使用。这样不仅可以在不同 Vue 版本的工程中间共享代码还能为后续升级 Vue3 减少一定阻碍。 那么同时兼容 Vue2 与 Vue3 的代码该如何实现业务实践中又有哪些代码精简和优化的小技巧让我们先从兼容代码的工程化讲起。
1. 工程化编写同时兼容 Vue2 与 Vue3 的代码
原理上兼容工作由两部分完成
编译阶段负责根据使用的项目环境自动选择使用 Vue2 或 Vue3 的 API。使用时只需要从 Vue-Demi 里面 import 需要使用的 API就会自动根据环境进行切换可以分为在浏览器中运行IIFE和使用打包工具cjs、umd、esm两种情况。运行阶段转换 createElement 函数的参数使 Vue2 与 Vue3 的参数格式一致。Vue2 和 Vue3 Composition API 的区别非常小运行时 API 最大的区别在于 createElement 函数的参数格式不一致Vue3 换成了 React JSX 格式。
1.1 编译阶段 ——IIFE
在 window 中定义一个 VueDemi 变量然后检查 window 中的 Vue 变量的版本根据版本 reexport 对应的 API。 var VueDemi (function (VueDemi, Vue, VueCompositionAPI) { // Vue 2.7 有不同这里只列出 2.0 ~ 2.6 的版本if (Vue.version.slice(0, 2) 2.) {for (var key in VueCompositionAPI) {VueDemi[key] VueCompositionAPI[key] } VueDemi.isVue2 true} else if (Vue.version.slice(0, 2) 3.) {for (var key in Vue) {VueDemi[key] Vue[key]} VueDemi.isVue3 true} return VueDemi})(this.VueDemi,this.Vue,this.VueCompositionAPI)1.2 编译阶段 —— 打包工具
利用 npm postinstall 的 hook检查本地的 Vue 版本然后根据版本 reexport 对应的 API。 const Vue loadModule(vue) // 这里是检查本地的 vue 版本if (Vue.version.startsWith(2.)) {switchVersion(2)}else if (Vue.version.startsWith(3.)) {switchVersion(3)}function switchVersion(version, vue) {copy(index.cjs, version, vue)copy(index.mjs, version, vue)}// VueDemi 自己的 lib 目录下有 v2 v3 v2.7 三个文件夹分别对应不同的 Vue 版本Copy 函数的功能就是把需要的版本复制到 lib 目录下// 然后在 package.json 里面指向 lib/index.cjs 和 lib/index.mjsfunction copy(name, version, vue) {const src path.join(dir, v${version}, name)const dest path.join(dir, name)fs.write(dest, fs.read(src))}1.3 运行阶段 createElement 函数的区别
1.3.1 Vue 2
attrs 需要写在 attrs 属性中on: { click {}}scopedSlots 写在 scopedSlots 属性中。 h(LayoutComponent, {staticClass: button,class: { is-outlined: isOutlined },staticStyle: { color: #34495E },style: { backgroundColor: buttonColor },attrs: { id: submit },domProps: { innerHTML: },on: { click: submitForm },key: submit-button,// 这里只考虑 scopedSlots 的情况了// 之前的 slots 没必要考虑全部用 scopedSlots 是一样的scopedSlots: { header: () h(div, this.header),content: () h(div, this.content),},});1.3.2 Vue 3
attrs 和 props 一样只需写在最外层onClick: () {}slot 写在 createElement 函数的第三个参数中。 class: [button, { is-outlined: isOutlined }],style: [{ color: #34495E }, { backgroundColor: buttonColor }],id: submit,innerHTML: ,onClick: submitForm,key: submit-button,}, {header: () h(div, this.header),content: () h(div, this.content),});1.4 完整代码 import { h as hDemi, isVue2 } from vue-demi;// 我们使用的时候使用的 Vue2 的写法但是 props 还是写在最外层为了 ts 的智能提示export const h (type: String | Recordany, any,options: Options any {},children?: any,) {if (isVue2) {const propOut omit(options, [props,// ... 省略了其他 Vue 2 的默认属性如 attrs、on、domProps、class、style]);// 这里提取出了组件的 propsconst props defaults(propOut, options.props || {}); if ((type as Recordstring, any).props) {// 这里省略了一些过滤 attrs 和 props 的逻辑不是很重要return hDemi(type, { ...options, props }, children);}return hDemi(type, { ...options, props }, children);}const { props, attrs, domProps, on, scopedSlots, ...extraOptions } options;const ons adaptOnsV3(on); // 处理事件const params { ...extraOptions, ...props, ...attrs, ...domProps, ...ons }; // 排除 scopedSlotsconst slots adaptScopedSlotsV3(scopedSlots); // 处理 slotsif (slots Object.keys(slots).length) {return hDemi(type, params, {default: slots?.default || children,...slots,});}return hDemi(type, params, children);};const adaptOnsV3 (ons: Object) {if (!ons) return null;return Object.entries(ons).reduce((ret, [key, handler]) {// 修饰符的转换if (key[0] !) {key key.slice(1) Capture;} else if (key[0] ) {key key.slice(1) Passive;} else if (key[0] ~) {key key.slice(1) Once;}key key.charAt(0).toUpperCase() key.slice(1);key on${key};return { ...ret, [key]: handler };}, {});};const adaptScopedSlotsV3 (scopedSlots: any) {if (!scopedSlots) return null;return Object.entries(scopedSlots).reduce((ret, [key, slot]) {if (isFunction(slot)) {return { ...ret, [key]: slot };}return ret;}, {} as Recordstring, Function);};2. 编码技巧利用代数数据类型精简代码
这里跟大家分享我自己总结的用于优化代码的理论工具。温馨提示可能和书本上的原有概念有些不同。
于我而言衡量一段代码复杂度的方法是看状态数量。状态越少逻辑、代码就越简单状态数量越多逻辑、代码越复杂越容易出错。因此我认为「好代码」的特征之一就是在完成业务需求的前提下尽量减少状态的数量即大小。
那么什么是状态在 Vue 的场景下可以这么理解
data 里面的变量就是状态props、计算属性都不是状态。Composition API 中 ref 和 reactive 是状态而 computed 不是状态。
2.1 什么是「状态」
状态是可以由系统内部行为更改的数据而状态大小是状态所有可能的值的集合的大小记作 size(State)。而代码复杂度 States.reduce((acc, cur) acc * size(cur),1)。
2.1.1 常见数据类型的状态大小
一些常见的数据类型比如 unit 的状态大小是 1在前端里可以是 null、undefined所有的常量、非状态的大小也是 1。而 Boolean 的状态大小是 2。
Number 和 String 一类有多个或无限个值的数据类型在计算状态大小时需明确一点我们只关心状态在业务逻辑中的意义而不是其具体值因此区分会影响业务逻辑的状态值即可。
例如一个接口返回的数据是一个数字但我们只关心这个数字是正数还是负数那么这个数字的状态大小就是 2。
2.1.2 复合类型的状态大小
复合类型分为和类型与积类型两种。 和类型状态大小的计算公式为 size(C) size(A) size(B)而积类型状态大小的计算公式为 size(C) size(A) * size(B)。
了解完代码优化标准后我们通过一个案例说明如何利用代数数据类型精简代码。
2.2 案例评论编辑器的显示控制
在 LigaAI 中每个评论都有两个编辑器一个用来编辑评论一个用来回复评论且同一时间最多只允许存在一个活动的编辑器。 2.2.1 优化前的做法
为回复组件定义两个布尔变量 IsShowReply 和 IsShowEdit 通过 v-if 控制是否显示编辑器。点击「回复」按钮时逻辑如下
(1) 判断自己的 IsShowReply 是否为 true如果是直接返回 (2) 判断自己的 IsshowEdit如果为 true 则修改为 false关闭编辑评论 (3) 依次设置所有其他评论组件的 IsShowReply 和 IsShowEdit 为 false (4) 修改自己的 IsShowReply 为 true。
当有 10 个评论组件时代码复杂度是多少 size(CommentComponent) size(Boolean) * size(Boolean) 2 * 2 4size(total) size(CommentComponent) ^ count(CommentComponent) 4 ^ 10 1048576尽管逻辑上互斥但这些组件在代码层面毫无关系可以全部设置为 true。如果代码出现问题包括写错没处理好互斥这种情况完全可能出现。处理互斥还涉及查找 dom 和组件出问题的几率也会大大提高。
2.2.2 优化后的做法
在 store 中定义一个字符串变量 activeCommentEditor表示当前活动的评论组件及其类型。 type CommentId number;type ActiveCommentStatus ${Edit | Reply}${CommentId} | Close; // TS 的模板字符串类型let activeCommentEditor: ActiveCommentStatus Close;除 Close 外该变量还由两部分组成。第一部分说明当前是「编辑评论」还是「回复评论」第二部分说明评论的 id。按钮的回调函数如点击回复只需要设置 activeCommentEditor Reply${id}组件使用时可以这样 v-ifactiveCommentEditor Edit${id}v-ifactiveCommentEditor Reply${id}就这么简单没有判断没有 dom没有其他组件。虽然 id 是 number但于前端而言只是一个常量所以其大小为 1。那么当有 10 个评论组件时这段代码的复杂度就是 size(total) size(ReplyEdit) * count(Comment) * 1 size(close) 2 * 10 * 1 1 21在实际使用中我们发现确实存在 21 种状态在代码层面我们也精准控制了这个值只能在这 21 种正确的状态中所以出错的几率也大大降低几乎不可能出错。