uni-app 黑魔法探秘 (一)—— 重写内置标签

一、背景

政采前端团队的移动端跨端解决方案选择的是 uni-app。跨端方案的好处就是一码多端,即书写一次就可以输出到 web、小程序、Anroid、iOS 等各端。既然是开发,那必然少不了配套的组件库和方法库,而我们公司因为历史原因存在一些的非 uni-app 的项目,vue2 和 vue3 也都在用,而重构的收益又不高,那如何开发一套多个技术栈下都能用的组件库&方法库就成为了我们最大的挑战。

在 uni-app 项目的开发过程中,我和小伙伴们不断为 uni-app 中一些写法感到好奇。譬如如何重写内置标签、类似c++中预处理器指令的条件编译、为什么 vue 文件中我没加 scoped 也会自动加上命名空间。后续深入了解才发现,uni-app 魔改了 vue 运行时,并且自制了一些 webpack 插件来实现。所以笔者希望通过《uni-app 黑魔法探秘》系列文章,将探索 uni-app、vue、webpack 的心路历程分享给大家,希望通过这一些列文章,最终读者自己也能实现一个简单的跨端框架。

开篇就先从如何重写内置标签讲起。什么是内置标签呢?就是 html 里约定的一些元素,譬如 div、button 等和 svg 里约定的一些标签譬如 image、view 等。

二、准备工作

先做些准备工作,创建两个项目。 1)通过 vue-cli 生成一个 vue2 项目,后续重写内置的组件的逻辑放在这个项目里。

lua

vue create vue2-project

2)再通过 vue-cli 生成一个 uni-app 项目,作为对照组。

bash

vue create -p dcloudio/uni-preset-vue uni-app-project

PS:本人 node 版本为 14.19.3,vue-cli 版本为 4.5.13,vue-cli 生成的项目中 vue 版本为 2.7.14

三、重写 html 标签,以 button 标签举例

css

复制代码

<button>click me</button>

先让我们看看 uni-app 会把上面的 button 代码转换成什么样:

image.png

重写 button 标签有什么难的呢。我直接写一个组件,然后注册不就可以用了吗?

xml

<template>  <uni-button>    <slot />  </uni-button> </template> ​ <script> export default {  name: 'VUniButton' } </script> ​

xml

<template>  <div id="app">    <button>click me!</button>  </div> </template> ​ <script> import UniButton from './components/UniButton.vue' ​ export default {  name: 'App',  components: {    'button': UniButton, } } </script>

结果是 button 根本没有被编译成 uni-button,并且控制台里能看到明显的错误

image.png

通过错误堆栈找到 vue.runtime.esm.js@4946中对应的代码

image.png

关键词是 isBuiltInTag、config.isReservedTag,再顺藤摸瓜找到如下源码:

css

var isBuiltInTag = makeMap('slot,component', true); ​ var isHTMLTag = makeMap('html,body,base,head,link,meta,style,title,' + 'address,article,aside,footer,header,h1,h2,h3,h4,h5,h6,hgroup,nav,section,' + 'div,dd,dl,dt,figcaption,figure,picture,hr,img,li,main,ol,p,pre,ul,' + 'a,b,abbr,bdi,bdo,br,cite,code,data,dfn,em,i,kbd,mark,q,rp,rt,rtc,ruby,' + 's,samp,small,span,strong,sub,sup,time,u,var,wbr,area,audio,map,track,video,' + 'embed,object,param,source,canvas,script,noscript,del,ins,' + 'caption,col,colgroup,table,thead,tbody,td,th,tr,' + 'button,datalist,fieldset,form,input,label,legend,meter,optgroup,option,' + 'output,progress,select,textarea,' + 'details,dialog,menu,menuitem,summary,' + 'content,element,shadow,template,blockquote,iframe,tfoot'); // this map is intentionally selective, only covering SVG elements that may // contain child elements. var isSVG = makeMap('svg,animate,circle,clippath,cursor,defs,desc,ellipse,filter,font-face,' + 'foreignobject,g,glyph,image,line,marker,mask,missing-glyph,path,pattern,' + 'polygon,polyline,rect,switch,symbol,text,textpath,tspan,use,view', true); var isReservedTag = function(tag) {  return isHTMLTag(tag) || isSVG(tag); };

可以看到 vue 源码中枚举了 html 标签,这些标签默认不允许被重写。

通过搜索 uni-app 源码发现, isReservedTag 这个方法其实是可以重写的(虽然 vue 文档里没标注)。改写的代码如下:

arduino

const overrideTags = ['button'] // 1)先保存原来的 isReservedTag 逻辑 const oldIsReservedTag = Vue.config.isReservedTag; Vue.config.isReservedTag = function (tag) {  // 2)在遇到 button 标签的时候,不认为是个内置标签  if (overrideTags.indexOf(tag) !== -1) {    return false; }  // 3)非 button 标签再走原来的内置标签的判断逻辑  return oldIsReservedTag(tag); };

增加上述代码后并添加样式后, uni-button 已经能成功被渲染出来了

image.png

但是控制台中还是有一行报错

image.png

还是通过错误的堆栈定位到 vue.runtime.esm.js@6274isUnknownElement这个方法。

image.png

然后发现也是可以被重写的,重写逻辑如下

ini

// ignoredElements 默认是 [],所以直接覆盖即可 Vue.config.ignoredElements = [  'uni-button', ];

到现在为止 button 已经能被正确渲染了,main.js中的代码如下

javascript

import Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false // 为了解决报错 [Vue warn]: Unknown custom element: <uni-button> - did you register the component correctly? For recursive components, make sure to provide the "name" option. // 默认是 [],所以直接覆盖即可 Vue.config.ignoredElements = [ 'uni-button', ]; // 为了解决报错 [Vue warn]: Do not use built-in or reserved HTML elements as component id: button const overrideTags = ['button'] const oldIsReservedTag = Vue.config.isReservedTag; Vue.config.isReservedTag = function (tag) { if (overrideTags.indexOf(tag) !== -1) { return false; } return oldIsReservedTag(tag); }; new Vue({ render: h => h(App), }).$mount('#app')

四、重写 svg 标签,以 image 举例

我们根据上述流程再实现一个 image 组件

xml

<template> <uni-image> <img :src="src" > </uni-image> </template> <script> export default { name: 'image', props: { src: { type: String, default: "" } }, } </script> <style> uni-image { width: 320px; height: 240px; display: inline-block; overflow: hidden; position: relative; } </style>

结果发现元素是正确的,但是没有被页面上没有展示。

image.png

通过 uni-app 源码(uni-app/lib/h5/ui.js@24)搜索发现其中有这样一段代码,添加后可以正确渲染了

arduino

const oldGetTagNamespace = Vue.config.getTagNamespace const conflictTags = ['switch', 'image', 'text', 'view'] Vue.config.getTagNamespace = function (tag) { if (~conflictTags.indexOf(tag)) { // svg 部分标签名称与 uni 标签冲突 return false } return oldGetTagNamespace(tag) || false }

尝试理解一下,在 vue.runtime.esm.js 中搜索关键词 getTagNamespace,最终定位在这一关键行 vue.runtime.esm.js@@2873。有无上述一行代码的区别是 ns 的值为 svg 还是 false

image.png

继续找到 getTagNamespace 实现逻辑,发现 vue 会通过 getTagNamespace 这个方法决定创建元素时使用的方法是 createElement 还是 createElementNS。

javascript

// vue.runtime.esm.js@6263 function getTagNamespace(tag) {  if (isSVG(tag)) {    return 'svg'; }  // basic support for MathML  // note it doesn't support other MathML elements being component roots  if (tag === 'math') {    return 'math'; } } ​ // vue.runtime.esm.js@6240 var namespaceMap = {  svg: 'http://www.w3.org/2000/svg',  math: 'http://www.w3.org/1998/Math/MathML' }; ​ ​ // vue.runtime.esm.js@6317 function createElement(tagName, vnode) {  var elm = document.createElement(tagName);  if (tagName !== 'select') {    return elm; }  // false or null will remove the attribute but undefined will not  if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {    elm.setAttribute('multiple', 'multiple'); }  return elm; } function createElementNS(namespace, tagName) {  return document.createElementNS(namespaceMap[namespace], tagName); }

命名空间存在的意义是,一个文档可能包含多个软件模块的元素和属性,在不同软件模块中使用相同名称的元素或属性,可能会导致识别和冲突问题,而 xml 命名空间可以解决该问题。

所以为什么没法渲染的原因显而易见了。因为把一个通过 svg 命名空间的创建出来的元素,挂载在非 svg 标签下。解决方案也很好理解,重写 getTagNamespace 方法,在遇到 image 标签时,认为其不是个 svg 标签,通过 createElement 方法创建在默认命名空间下即可。

最后 main.js中的代码如下

javascript

import Vue from 'vue' import App from './App.vue' ​ Vue.config.productionTip = false ​ // 下面三条参考 uni-app 源码实现 uni-app/lib/h5/ui.js ​ // ① 为了解决报错 [Vue warn]: Unknown custom element: <uni-button> - did you register the component correctly? For recursive components, make sure to provide the "name" option. Vue.config.ignoredElements = [  'uni-button',  'uni-image', ]; ​ // ② 为了解决报错 [Vue warn]: Do not use built-in or reserved HTML elements as component id: button const overrideTags = ['button', 'image'] // 1)先保存原来的 isReservedTag 逻辑 const oldIsReservedTag = Vue.config.isReservedTag; Vue.config.isReservedTag = function (tag) {  // 2)在遇到 button 标签的时候,不认为是个内置标签  if (overrideTags.indexOf(tag) !== -1) {    return false; }  // 3)非 button 标签再走原来的内置标签的判断逻辑  return oldIsReservedTag(tag); }; ​ // ③ 为了解决 image 标签虽然被解析,但是渲染不出来的问题 // svg 部分标签名称与 uni 标签冲突 const conflictTags = [  'image',  // 'switch',  // 'text',  // 'view', ]; const oldGetTagNamespace = Vue.config.getTagNamespace; Vue.config.getTagNamespace = function (tag) {  // “~”运算符(位非)用于对一个二进制操作数逐位进行取反操作。位非运算实际上就是对数字进行取负运算,再减 1  // 等价于 conflictTags.indexOf(tag) > -1  if (~conflictTags.indexOf(tag)) {    return false; }  return oldGetTagNamespace(tag); }; ​ new Vue({  render: h => h(App), }).$mount('#app') ​

五、总结

针对内置标签的解析,很庆幸 vue 还是留了一道后门的,不然就要魔改 vue 的源码了。uni-app 中有很多类似的黑魔法。为什么称之为黑魔法呢,因为其中使用的方法可能是官网中不会讲到的,或者是些需要巧思的。像魔术一样,看似很神奇,真的知道解决方案后就会恍然大悟。我相信这些黑魔法的出现并不是为了炫技,而更多的是在熟悉原理后的决策。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2805619.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

每日一题(寻找奇数,寻找峰值)

寻找奇数_牛客题霸_牛客网 (nowcoder.com) #include <stdio.h> #include<stdlib.h> int main() {int n0;int num0;scanf("%d",&n);int* arr(int*)malloc(sizeof(int)*n);int i0;for(i0;i<n;i){scanf("%d",&arr[i]);//在循环内&…

C++之类作用域

目录 1、全局作用域 2、类作用域 2.1、设计模式之Pimpl 2.2、单例模式的自动释放 2.2.0、检测内存泄漏的工具valgrind 2.2.1、可以使用友元形式进行设计 2.2.2、内部类加静态数据成员形式 2.2.3、atexit方式进行 2.2.4、pthread_once形式 作用域可以分为类作用域、类名…

MobaXterm连接VirtualBox虚拟机

目录 1.下载MobaXterm 2.获取连接配置 3.mobaXterm连接虚拟机 4.更好的方案 1.下载MobaXterm 据说MobaXtrem是远程终端的超级全能神器,官网下载地址&#xff1a;MobaXterm free Xserver and tabbed SSH client for Windows 选择适合你的版本&#xff1a;一个是Home Editi…

基于编译器的静态代码分析与软件开发效率、质量和性能

基于编译器的静态代码分析与软件开发效率、质量和性能 本文节选自《基础软件之路&#xff1a;企业级实践及开源之路》一书&#xff0c;该书集结了中国几乎所有主流基础软件企业的实践案例&#xff0c;由 28 位知名专家共同编写&#xff0c;系统剖析了基础软件发展趋势、四大基…

JavaScript实现页面随着某元素自动滚动(歌词居中)

在开发过程中想要使页面随着某个元素自动滚动&#xff0c;可以使用Element.scrollIntoView()。 下面举例一个歌词的居中显示 let lyc document.querySelectorAll(.has-lyric li)[i] //获取当前播放的歌词lyc.scrollIntoView({behavior: "smooth", // 定义动画过渡…

ChatGPT/GPT4科研应用与AI绘图及论文写作

2023年随着OpenAI开发者大会的召开&#xff0c;最重磅更新当属GPTs&#xff0c;多模态API&#xff0c;未来自定义专属的GPT。微软创始人比尔盖茨称ChatGPT的出现有着重大历史意义&#xff0c;不亚于互联网和个人电脑的问世。360创始人周鸿祎认为未来各行各业如果不能搭上这班车…

Flink ML 的新特性解析与应用

摘要&#xff1a;本文整理自阿里巴巴算法专家赵伟波&#xff0c;在 Flink Forward Asia 2023 AI特征工程专场的分享。本篇内容主要分为以下四部分&#xff1a; Flink ML 概况在线学习的设计与应用在线推理的设计与应用特征工程算法与应用 一、Flink ML 概况 Flink ML 是 Apache…

算法打卡day1|数组篇|Leetcode 704.二分查找、27.移除元素

数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合&#xff0c;可以方便的通过下标索引的方式获取到下标下对应的数据。 1.数组下标都是从0开始的。 2.数组内存空间的地址是连续的。 正是因为数组的在内存空间的地址是连续的&#xff0c;所以我们在删除或者增添…

ShardingSphere5.x 分库分表

一、shardingSphere介绍 1、官网&#xff1a;Apache ShardingSphere 2、开发文档&#xff1a; 概览 :: ShardingSphere 3、shardingsphere-jdbc ShardingSphere-JDBC 定位为轻量级 Java 框架&#xff0c;在 Java 的 JDBC 层提供的额外服务。 它使用客户端直连数据库&#x…

转运机器人,AGV底盘小车:打造高效、精准的汽车电子生产线

为了满足日益增长的市场需求&#xff0c;保持行业领先地位&#xff0c;某汽车行业电子产品企业引入富唯智能AMR智能搬运机器人及其智能物流解决方案&#xff0c;采用自动化运输措施优化生产节拍和搬运效率&#xff0c;企业生产效率得到显著提升。 项目背景&#xff1a; 1、工厂…

【算法与数据结构】1971、LeetCode寻找图中是否存在路径

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;本题应用并查集的理论直接就可以解决&#xff1a;【算法与数据结构】回溯算法、贪心算法、动态规划、图…

深入浅出JVM(七)之执行引擎的解释执行与编译执行

本篇文章围绕执行引擎&#xff0c;深入浅出的解析执行引擎中解释器与编译器的解释执行和编译执行、执行引擎的执行方式、逃逸分析带来的栈上分配、锁消除、标量替换等优化以及即时编译器编译对热点代码的探测 执行引擎 hotspot执行引擎结构图 执行引擎分为解释器、JIT即时编译…

QT_day4

1.思维导图 2. 输入闹钟时间格式是小时:分钟 widget.cpp #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);id startTimer(1000);flag1;speecher new QTextT…

做抖音小店怎么选品?给新手商家的三条建议,能让你销量猛增999+

大家好&#xff0c;我是电商花花。 总是担心店铺不出单&#xff0c;没有销量&#xff0c;看着断断续续的收益&#xff0c;新手商家应该都是愁容满面吧。 今天花花从是3个维度上给新手商家一些建议&#xff0c;讲解一下如何高效选品&#xff0c;加你如何让你出单猛增999。 以前…

训练Sora模型,你可能需要这些开源代码,模型,数据集及算力评估

在之前的文章&#xff0c;我们总结了Sora模型上用到的一些核心技术和论文 复刻大模型 Sora 有多难&#xff1f;一张图带你读懂 Sora 的技术路径一文看懂大模型 Sora 技术推演 今天这篇文章来自我们社区讨论交流&#xff0c;我这边整理和总结现有的一些开源代码、模型、数据集…

【大数据】Flink 内存管理(一):设置 Flink 进程内存

Flink 内存管理&#xff08;一&#xff09;&#xff1a;设置 Flink 进程内存 1.配置 Total Memory2.JVM 参数3.根据比例限制的组件&#xff08;Capped Fractionated Components&#xff09; Apache Flink 通过严格控制各种组件的内存使用&#xff0c;在 JVM 上提供高效的工作负…

【论文阅读】ICCV 2023 计算和数据高效后门攻击

文章目录 一.论文信息二.论文内容1.摘要2.引言3.主要图表4.结论 一.论文信息 论文题目&#xff1a; Computation and Data Efficient Backdoor Attacks&#xff08;计算和数据高效后门攻击&#xff09; 论文来源&#xff1a; 2023-ICCV&#xff08;CCF-A&#xff09; 论文团…

AI文生图网站测评

主要测评文章配图生成效果、绘制logo等效果 测评关键点&#xff1a;生成效果、网站易用度、是否免费 测评prompt&#xff1a;请生成一个文章内容配图&#xff0c;图片比例是3&#xff1a;2&#xff0c;文章主旨是AI既是机遇&#xff0c;也存在挑战和风险&#xff0c;要求图片…

Matlab/simulink基于vsg的风光储调频系统建模仿真(持续更新)

​ 1.Matlab/simulink基于vsg的风光储调频系统建模仿真&#xff08;持续更新&#xff09;

leet hot 100-3 最长连续序列

两数之和 原题链接思路代码 原题链接 leet hot 100-3 128. 最长连续序列 思路 可以把所有的数字放到容器里面去 维护一个最大值 每一次去遍历数字 查看但当前数字是否为起始位置&#xff08;它的前面是否有比它小一位的数字&#xff09; 如果是起始位置 就记录一下当前值 并…