重写elementui部分方法-uni-app黑魔法探索(一)——重写外部标签

2024-04-27 0 2,531 百度已收录

uni-app魔法访问(一)——重写外部标签

一、背景

政府采购后端团队选择uni-app作为他们的移动跨端解决方案。 跨终端方案好处是,一份代码可以用于多个终端,即一次编写,可以输出到Web、小程序、Android、iOS等多种终端。 既然是开发,就少不了配套的组件库和技术库。 由于历史原因,我们公司有一些非uni-app项目。 也用的是vue2、vue3,搭建利润不高,那么如何开发呢? 开发一个可以跨多个技术栈使用的组件库和方法库已经成为我们最大的挑战

在uni-app项目的开发过程中,我和小伙伴们一直对uni-app中的一些写法感到好奇。 比如如何重画外部标签,条件编译类似于C++中的预处理指令(),为什么我没有在vue文件添加scoped并且还手动添加了命名空间。 经过进一步深入研究,我发现uni-app神奇修改了vue运行时,并制作了一些自制webpack插件实现。 因此,笔者希望通过《Uni-app黑魔法揭秘系列文章与大家分享探索uni-app、vue、webpack的旅程。 希望通过这一系列的文章,读者最终能够自己实现一个简单的跨端框架。 。

让我们从如何重绘外部标签开始。 什么是外部标签? 就是html中约定的一些elements()重写elementui部分方法,比如div、button等,以及svg中约定的一些标签,比如image、view等。

2、准备工作

首先做一些规划创建两个项目。

1)通过vue-cli生成一个vue2项目,然后将外部组件重绘的逻辑放到这个项目中。

vue create vue2-project

2)然后通过vue-cli生成uni-app项目作为对照组。

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

3、重写html标签,以button标签为例


我们先来看看uni-app会将里面的按钮代码转换成什么:

重写按钮标签有什么困难? 如果我直接写一个组件然后注册一下可以使用吗?








export default {
name: 'VUniButton'
}









import UniButton from './components/UniButton.vue'

export default {
name: 'App',
components: {
'button': UniButton,
}
}

结果是该按钮根本没有编译成单一按钮,并且可以在控制台中看到重大错误

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

关键词是isBuiltInTag、config.isReservedTag,然后顺着线索找到如下源码

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文档中没有注明)。 重写后的代码如下:

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就可以成功渲染了。

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

或者通过错误的堆栈定位到 vue.runtime.esm.js@6274 中的 isUnknownElement 。

后来我发现还可以重画。 重写逻辑如下

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

至此,按钮就可以正确渲染了。 main.js中的代码如下

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 为了解决报错 [Vue warn]: Unknown custom element:  - 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({
  renderh => h(App),
}).$mount('#app')

4、重写svg标签,以image为例

我们按照上面的流程实现另一个图像组件








export default {
name: 'image',
props: {
src: {
type: String,
default: ""
}
},
}



uni-image {
width: 320px;
height: 240px;
display: inline-block;
overflow: hidden;
position: relative;
}

事实证明元素是正确的,但没有显示页面上。

搜索uni-app源码(uni-app/lib/h5/ui.js@24)发现有这么一段代码。 添加后就可以正确渲染了。

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。

继续查找getTagNamespace实现逻辑,发现vue在创建元素时会通过getTagNamespace来判断是使用createElement还是createElementNS。

// 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中最终代码如下

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:  - 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({
  renderh => h(App),
}).$mount('#app')

5. 总结

关于外部标签的分析,还好Vue还有侧门,不然Vue的源码就得修改了。 uni-app中有很多类似的黑魔法。 为什么它被称为黑魔法? 因为使用的方法可能官网上没有提到,或者可能需要别出心裁。 就像魔术一样,看似神奇,但当你真正知道解决办法后,你就会恍然大悟。 我相信黑魔法的出现并不是为了炫耀技巧更多的是为了熟悉原理后做出决定

以上作者的思路是根据已知问题(内置标签无法解析)和答案(重写Vue.config方法)进行推理。 即便如此,我还是了很多时间思考uni-app。 这个关键技术点的认识让我更加肃然起敬,更加好奇。 这也是我写这个系列文章的初衷。

读完两件事后

如果你觉得这篇内容对你有启发,我想请你帮我做两件小事。

1.点击“正在看”重写elementui部分方法,让更多人看到此内容(点击“正在看”,bug -1)

2.关注公众号“正财云前端”,持续推送精选的好文章

招聘

正彩云后端团队(ZooTeam)是一支年轻充满激情、富有创造力的后端团队,隶属于正彩云研发部,总部位于风景如画的广州。 团队目前拥有后端合伙人80余人,平均年龄27岁,近30%为全栈工程师。 是一个完美青春风暴团体成员中有来自阿里巴巴网易的“老兵”,也有来自交通大学中国交通大学、杭州电力大学等学校应届毕业生。 除了日常业务对接外,团队还在材料系统、工程平台施工平台、性能体验、云应用数据分析可视化等方面进行技术探索和实践推广实施了一系列内部技术产品,持续探索后端技术系统的新边界

如果你想改变,还在被事情折腾,希望你能开始折腾; 如果你想改变,仍然被警告要少发表意见,但没有办法打破这种局面; 如果你想改变,你有能力达到那个结果,但不需要你; 如果你想改变你想要达到的目标,你需要一个团队来支持,但没有位置让你去领导人; 如果要改变既定的节奏,那就是“5年工作时间+3年工作经验”; 如果你想改变 本来,我的智慧是好的,但总有一层像阳台纸一样的模糊……如果你相信信念力量,相信平凡的人也能成就不平凡的事,相信你能遇见更好的自己。 如果你想参与到业务腾飞过程中,亲自推动一个业务理解深入、技术体系完整、技术创造价值、溢出影响力的后端团队成长,我觉得我们应该谈谈。 任何时候,我都在等你写点东西发送给ZooTeam@cai-inc.com

收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

悟空资源网 elementui 重写elementui部分方法-uni-app黑魔法探索(一)——重写外部标签 https://www.wkzy.net/game/201358.html

常见问题

相关文章

官方客服团队

为您解决烦忧 - 24小时在线 专业服务