用原生实现Vue3,真香~

2022-11-26 0 901

最终目标:用原生植物js同时实现自订模块,Vue3单向存取

孔布龙科学知识贮备:

必不可少科学知识1,自订原素(customElement)

专业术语太少,Chalancon标识符:

//html: <user-card data-open=”true”></user-card> //javascript: class Learn extends HTMLElement{ constructor(props) { super(props); console.log(this.dataset); this.innerHTML = 这是我自订的原素; this.style.border = 1px solid #899; this.style.borderRadius = 3px; this.style.padding = 4px; } } window.customElements.define(user-card,Learn);

效用:

用原生实现Vue3,真香~

导出:透过window.customElements方式能建立自订原素,里头的define方式是用以选定自订原素的中文名称,和自订原素相关联的类。

这儿有两个技术细节,自订原素尾端很大会用中截叶分隔,要不然是合宪的。

这时在那个类里头就能表述原素里的大部份文本了,这和Vue里头的模块早已较为类似于了,有了那个此基础后他们再往里头去展开开拓就能同时实现模块了。

必不可少科学知识2,Proxy

这混蛋估算他们都晓得,Vue3统计数据积极响应的核心理念,Vue2用的是Object.defineProperty; 很强悍,较好用,嘿嘿个单纯的标识符:

let obj = { a:2938, b:siduhis, item:name } obj = new Proxy(obj,{ set(target, p, value, receiver) { console.log(窃听到,p,被修正了,由原本的:,target[p],换成了:,value); } }); document.onclick = ()=>{ obj.item = newValue; }

效用:

用原生实现Vue3,真香~

那个如果深入去讲的话有很多能讲,比如说修正值的时候会触发set方式,读取值的时候会触发get方式等等,具体的他们去看看官网文档会更好。

必不可少科学知识3,事件代理

首先,我利用事件代理去处理模块中的事件,主要是写起来方便,开拓也很方便,嘿嘿看个最单纯版本的事件代理:

//html <ul class=”list”> <li class=”item” data-first=”true”>这是第两个</li> <li class=”item”>2222</li> <li class=”item”>three</li> <li class=”item” data-open=”true”>打开</li> <li class=”item”>这是最后两个</li> </ul> //javascript let list = document.querySelector(.list); list.onclick = function(ev){ let target = ev.target; console.log(点击了+target.innerHTML); }

效用:

用原生实现Vue3,真香~

这是最单纯版本,在ul身上存取了点击事件,利用事件冒泡原理,点击任何两个li都会触发其父级ul的点击事件,透过ul的事件也能反向找到被精确点击的li原素,从而把相应的li的文本打印出来,怎么样,很单纯吧~

你可能注意到了上面标识符中,有两个li的身上有data自订属性,那个一会有用

再来看两个升级版本,在这儿,能通多判断li身上不同的属性,从而去执行不同的函数,这样的话就有点语法糖的意思了:

let eventfn = function(ev){ let target = ev.target; let dataset = target.dataset; for(b in dataset){ if(eventfn[b]){ eventfn[b]({obj:target,parent:this}); } } } eventfn.first = function(){ console.log(点击了第两个,并且传了一些参数, arguments); } eventfn.open = function(){ console.log(点击了打开); } list.onclick = eventfn;

看那个属性有没有相关联的事件函数,如果有,则执行,并且传递一些参数进去,那个参数以后可能会用到,这是两个开拓点。到这儿,他们事件处理基本就成型了

第一步,建立模块文本

思路分析:1, 文本最好是直接写在页面上,然后需要填统计数据的地方用{{}}包起来2, template标签能用以包裹模板,并且不会被显示在页面上3, 在模块里复制template里的文本作为模块的文本,并且导出里头的{{}}4, 还需要解析里头的各种指令,比如data-open这代表两个open事件
用原生实现Vue3,真香~

这是效用图 上标识符:

<template id=”userCardTemplate”> <style> .image { width: 100px; } .container { background: #eee; border-radius: 10px; width: 500px; padding: 20px; } </style> <img src=”img/bg_03.png” class=”image”> <div class=”container”> <p class=”name” data-open=”true”>{{name}}</p> <p class=”email”>{{email}}</p> <input type=”text” v-model=”message”> <span>{{message}}</span> <button class=”button”>Follow</button> </div> </template>

这是效用图 上标识符:

<template id=”userCardTemplate”> <style> .image { width: 100px; } .container { background: #eee; border-radius: 10px; width: 500px; padding: 20px; } </style> <img src=”img/bg_03.png” class=”image”> <div class=”container”> <p class=”name” data-open=”true”>{{name}}</p> <p class=”email”>{{email}}</p> <input type=”text” v-model=”message”> <span>{{message}}</span> <button class=”button”>Follow</button> </div> </template>

第二步,开始写模块类

class UserCard extends HTMLElement { constructor() { super(); var templateElem = document.getElementById(userCardTemplate); var content = templateElem.content.cloneNode(true); this.appendChild(content); this._data = { name:用户名, email:[email protected], message:单向 } } } window.customElements.define(user-card,UserCard);

这时吧user-card那个原素往页面上丢,得到的效用是这样的了:

用原生实现Vue3,真香~

第三步,导出

那么接下来要做的事情是导出原素里头的子原素,看看里头是不是包含了{{}}这样的符号,并且要把尾端的文本拿出来,和data里头的统计数据展开比对,如果相关联上了,那就把统计数据填充到那个地方就能了,说起来单纯,做起来还是有很大难度的,这儿头会用到正则匹配,于是我在class里写了那个么个方式:

compileNode(el){ if(node.nodeType === 3){//判断是文本节点,于是直接正则伺候 let text = node.textContent; let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; //大概的意思是匹配前面有两个{{,后面也有两个}}的这么一串文本 if(reg.test(text)){//如果能找到这样的字符串 let $1 = RegExp.$1;//那就把里头的文本拿出来,比如‘name’ this._data[$1] && (node.textContent = text.replace(reg,this._data[$1]));//看看统计数据里头有没有name这么个东西,如果有,那就把统计数据里头name相关联的值填到当前那个位置。 }; } }) }

把那个方式丢到constructor里头运行一下就能了,得到效用:

用原生实现Vue3,真香~

第四步,同时实现统计数据视图存取

到这儿,还是只单纯的把数据渲染到了页面上,如果统计数据再次发生变化,他们还没有找到通知机制让视图发生改变,怎么办呢? 这时就需会用到Proxy了。这儿还需要配合自订事件,嘿嘿看Proxy部分,这儿其实很单纯,增加两个方式就可以了:

observe(){ let _this = this; this._data = new Proxy(this._data,{//窃听统计数据 set(obj, prop, value){//统计数据改变的时候会触发set方式 //事件通知机制,发生改变的时候,透过自订事件通知视图发生改变 let event = new CustomEvent(prop,{ detail: value//注意这儿我传了个detail过去,这样的话更新视图的时候就能直接拿到新的统计数据 }); _this.dispatchEvent(event); return Reflect.set(…arguments);//这儿是为了确保修换成功,不写其实也没关系 } }); }

事件通知有了,但是需要在导出函数里头窃听一下事件,以便视图及时作出改变:

compileNode(el){ let child =node.nodeType === 3){//判断是文本节点,于是直接正则伺候 let text = node.textContent; let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; //大概的意思是匹配前面有两个{{,后面也有两个}}的这么一串文本 if(reg.test(text)){//如果能找到这样的字符串 let $1 = RegExp.$1;//那就把里头的文本拿出来,比如‘name’ this._data[$1] && (node.textContent = text.replace(reg,this._data[$1]));//看看统计数据里头有没有name这么个东西,如果有,那就把统计数据里头name相关联的值填到当前那个位置。 //增加了事件窃听,窃听每两个匹配到的统计数据,并且再一次更新视图 //注意这儿的e.detail是上面observe里头的自订事件传过来的 this.addEventListener($1,(e)=>{ node.textContent = text.replace(reg,e.detail) }) }; } }) }

到这一步,他们就能同时实现修正统计数据的时候,视图也发生改变了:

let card = document.querySelector(user-card); document.onclick = function(){ console.log(点击了); card._data.name = 新的用户名; }
用原生实现Vue3,真香~

第五步,同时实现单向存取

估算你也看到了,我在template里头写了两个输入框,并且输入框上面还带了两个属性:v-model=”message” 所以估算你也猜到我要做什么了,怎么做呢? 其实很单纯: 在导出文本的时候,判断一下input原素,并且看看它身上是不是有v-model属性,如果有,窃听它的input事件,并且修正统计数据。

再次修正导出函数:
compileNode(el){ let child = el.childNodes; […child].forEach((node)=>{ if(node.nodeType === 3){ let text = node.textContent; let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; if(reg.test(text)){ let $1 = RegExp.$1; this._data[$1] && (node.textContent = text.replace(reg,this._data[$1])); this.addEventListener($1,(e)=>{ node.textContent = text.replace(reg,e.detail) }) }; }else if(node.nodeType === 1){ let attrs = node.attributes; if(attrs.hasOwnProperty(v-model)){//判断是不是有那个属性 let keyname = attrs[v-model].nodeValue; node.value = this._data[keyname]; node.addEventListener(input,e=>{//如果有,窃听事件,修正统计数据 this._data[keyname] = node.value;//修正统计数据 }); } if(node.childNodes.length > 0){ this.compileNode(node);//递归同时实现深度导出 } } }) }

第六步,处理事件

嘿嘿看看完整的模块标识符:

class UserCard extends HTMLElement { constructor() { super(); var templateElem = document.getElementById(userCardTemplate); var content = templateElem.content.cloneNode(true); this.appendChild(content); this._data = {//表述数据 name:用户名, email:[email protected], message:单向 } this.compileNode(this);//导出原素 this.observe();//窃听统计数据 this.bindEvent();//处理事件 } bindEvent(){ this.event = new popEvent({ obj:this, popup:true }); } observe(){ let _this = this; this._data = new Proxy(this._data,{ set(obj, prop, value){ let event = new CustomEvent(prop,{ detail: value }); _this.dispatchEvent(event); return Reflect.set(…arguments); } }); } compileNode(el){ let child = el.childNodes; […child].forEach((node)=>{ if(node.nodeType === 3){ let text = node.textContent; let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; if(reg.test(text)){ let $1 = RegExp.$1; this._data[$1] && (node.textContent = text.replace(reg,this._data[$1])); this.addEventListener($1,(e)=>{ node.textContent = text.replace(reg,e.detail) }) }; }else if(node.nodeType === 1){ let attrs = node.attributes; if(attrs.hasOwnProperty(v-model)){ let keyname = attrs[v-model].nodeValue; node.value = this._data[keyname]; node.addEventListener(input,e=>{ this._data[keyname] = node.value; }); } if(node.childNodes.length > 0){ this.compileNode(node); } } }) } open(){ console.log(触发了open方式); } }

能发现在这儿头多了两个方式,两个是bindEvent,没错,那个是用以处理事件的了,方式的标识符在下面,结合着第三个必不可少科学知识点去看就能看懂了。

class popEvent{ constructor(option){ /* * 接收四个参数: * 1,对象的this * 2,要窃听的原素 * 3,要窃听的事件,默认窃听点击事件 * 4,是否冒泡 * */ this.eventObj = option.obj; this.target = option.target || this.eventObj; this.eventType = option.eventType || click; this.popup = option.popup || false; this.bindEvent(); } bindEvent(){ let _this = this; _this.target.addEventListener(_this.eventType,function(ev){ let target = ev.target; let dataset,parent,num,b; popup(target); function popup(obj){ if(obj === document){ return false;} dataset = obj.dataset; num = Object.keys(dataset).length; parent = obj.parentNode; if(num<1){ _this.popup && popup(parent); num = 0; }else{ for(b in dataset){ if(_this.eventObj.__proto__[b]){ _this.eventObj.__proto__[b].call(_this.eventObj,{obj:obj,ev:ev,target:dataset[b],data:_this.eventObj}); } } _this.popup && popup(parent); } } }) } }

另外两个是open方式,那个方式是干嘛用的呢?再回过头去看看template里头的标识符:<p class=”name” data-open=”true”>{{name}}</p>这一串是不是很熟悉,猜到我想做什么了么?

没错,同时实现事件指令

当点击含有自订属性:data-open的原素的时候,就能触发模块里的open方式,并且在open方式里还能够得到任何你需要的参数。:

用原生实现Vue3,真香~

点击用户名的时候,触发了open方式。

完整标识符奉上,注意标识符最后的小技术细节哦~

<!DOCTYPE html> <html lang=”en”> <head> <meta charset=”UTF-8″> <title>Title</title> <style> </style> </head> <body> <template id=”userCardTemplate”> <style> .image { width: 100px; } .container { background: #eee; border-radius: 10px; width: 500px; padding: 20px; } </style> <img src=”img/bg_03.png” class=”image”> <div class=”container”> <p class=”name” data-open=”true”>{{name}}</p> <p class=”email”>{{email}}</p> <input type=”text” v-model=”message”> <span>{{message}}</span> <button class=”button”>Follow</button> </div> </template> <user-card data-click=”123″></user-card> <script type=”module”> class popEvent{ constructor(option){ /* * 接收四个参数: * 1,对象的this * 2,要窃听的原素 * 3,要窃听的事件,默认窃听点击事件 * 4,是否冒泡 * */ this.eventObj = option.obj; this.target = option.target || this.eventObj; this.eventType = option.eventType || click; this.popup = option.popup || false; this.bindEvent(); } bindEvent(){ let _this = this; _this.target.addEventListener(_this.eventType,function(ev){ let target = ev.target; let dataset,parent,num,b; popup(target); function popup(obj){ if(obj === document){ return false;} dataset = obj.dataset; num = Object.keys(dataset).length; parent = obj.parentNode; if(num<1){ _this.popup && popup(parent); num = 0; }else{ for(b in dataset){ if(_this.eventObj.__proto__[b]){ _this.eventObj.__proto__[b].call(_this.eventObj,{obj:obj,ev:ev,target:dataset[b],data:_this.eventObj}); } } _this.popup && popup(parent); } } }) } } class UserCard extends HTMLElement { constructor() { super(); var templateElem = document.getElementById(userCardTemplate); var content = templateElem.content.cloneNode(true); this.appendChild(content); this._data = { name:用户名, email:[email protected], message:单向 } this.compileNode(this); this.observe(this._data); this.bindEvent(); this.addevent = this.__proto__; } bindEvent(){ this.event = new popEvent({ obj:this, popup:true }); } observe(){ let _this = this; this._data = new Proxy(this._data,{ set(obj, prop, value){ let event = new CustomEvent(prop,{ detail: value }); _this.dispatchEvent(event); return Reflect.set(…arguments); } }); } compileNode(el){ let child = el.childNodes; […child].forEach((node)=>{ if(node.nodeType === 3){ let text = node.textContent; let reg = /\{\{\s*([^\s\{\}]+)\s*\}\}/g; if(reg.test(text)){ let $1 = RegExp.$1; this._data[$1] && (node.textContent = text.replace(reg,this._data[$1])); this.addEventListener($1,(e)=>{ node.textContent = text.replace(reg,e.detail) }) }; }else if(node.nodeType === 1){ let attrs = node.attributes; if(attrs.hasOwnProperty(v-model)){ let keyname = attrs[v-model].nodeValue; node.value = this._data[keyname]; node.addEventListener(input,e=>{ this._data[keyname] = node.value; }); } if(node.childNodes.length > 0){ this.compileNode(node); } } }) } open(){ console.log(触发了open方式); } } window.customElements.define(user-card,UserCard); let card = document.querySelector(user-card); card.addevent[click] = function(){ console.log(触发了点击事件!); } </script> </body> </html>

最后

放松一下咯~

作者:Mr_无忧

链接:https://juejin.im/post/6893880467305529352

相关文章

发表评论
暂无评论
官方客服团队

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