网站架构设计师待遇怎么样,wordpress 虚拟下载插件,wordpress修改注册邮件内容,免费网站建设哪个好目录 问题思考课程目标Web Component类型说明定义组件属性添加 Shadow DOMTemplate and SlotExparser 框架原理自定义组件内置组件 下周计划 问题思考
首先#xff0c;给大家抛出去几个问题#xff1a;
前端框架 Vue React 都有自己的组件库#xff0c;但是并不兼容#… 目录 问题思考课程目标Web Component类型说明定义组件属性添加 Shadow DOMTemplate and SlotExparser 框架原理自定义组件内置组件 下周计划 问题思考
首先给大家抛出去几个问题
前端框架 Vue React 都有自己的组件库但是并不兼容那么 不依赖框架能 自定义组件 吗微信小程序开发的时候都会自定义组件是吧那么调试控制台出现的 shadow-root 是什么有注意吗微信小程序编写 wxml 的时候为什么和 html 语法不一致多出来 view text 这些标签里面究竟是如何实现的彼此有什么关联
课程目标
通过本节课程的学习希望大家掌握如下的目标
弄懂上述问题背后的执行逻辑能够利用原生 Web Component 自定义一个简易的组件
Web Component
使用自定义元素 - Web API | MDN
Web Component直译过来就是 web 组件的意思就是说明离开了前端框架的帮助我们依然可以用原生组件来进行开发复用。
类型说明 如同官网所说继承特定元素类得到的组件是 自定义内置元素组件可以得到特定类型的属性和方法继承元素基类得到的组件是 独立自定义元素本质上两种没什么区别接下来我们重点就放在第二个上面。
定义组件 button ismy-button-one内置按钮/button
my-button-two/my-button-two// 01 定义一个内置元素的按钮
class MyButtonOne extends HTMLButtonElement {constructor() {self super();}// 元素添加到文档调用connectedCallback() {// 1.创建一个 divconst div document.createElement(div);// 2.设置 div 的样式div.style.width 100px;div.style.height 50px;div.style.textAlign center;div.style.lineHeight 50px;div.style.cursor pointer;self.style.marginBottom 20px;// 3.设置 div 的内容div.innerHTML 自定义按钮;// 4.将 div 添加到页面self.appendChild(div);}
}// 02 定义一个自定义的按钮
class MyButtonTwo extends HTMLElement {constructor() {// 先调用父类构造器实例化 HTMLElement 这样才能有 html 元素的基本属性super();}// 元素添加到文档调用connectedCallback() {console.log(自定义元素添加到页面, this);// 1.创建一个 divconst div document.createElement(div);// 2.设置 div 的样式div.style.width 100px;div.style.height 50px;div.style.backgroundColor red;div.style.color white;div.style.textAlign center;div.style.lineHeight 50px;div.style.cursor pointer;// 3.设置 div 的内容div.innerHTML 自定义按钮;// 4.将 div 添加到页面this.appendChild(div);}// 元素从文档中移除时调用disconnectedCallback() {console.log(自定义元素从页面移除);}// 元素被移动到新文档时调用adoptedCallback() {console.log(自定义元素被移动到新文档);}// 监听属性变化attributeChangedCallback(name, oldValue, newValue) {console.log(属性 ${name} 已由 ${oldValue} 变更为 ${newValue});}
}// 组件注册
customElements.define(my-button-one, MyButtonOne, { extends: button });
customElements.define(my-button-two, MyButtonTwo);// 监听组件状态
customElements.whenDefined(my-button-two).then(() {console.log(my-button-two 组件已定义);
});自定义组件的命名规则是有限制的
自定义元素的名称必须包含短横线-。它可以确保html解析器能够区分常规元素和自定义元素还能确保html标记的兼容性。自定义元素只能一次定义一个一旦定义无法撤回。自定义元素不能单标记封闭。比如 custom-component /必须写一对开闭标记。比如 custom-component/custom-component。
上面两个就是最基本的自定义组件但是这个也没有样式 class 属性传值 事件方法都没有下面我们一步步加上。
属性添加
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /style* {margin: 0;padding: 0;}body {width: 100px;margin: 200px auto;background-color: #f5f5f5;}.my-button-two {width: 180px;height: 50px;background-color: red;color: white;text-align: center;line-height: 50px;cursor: pointer;}/stylemy-button-two colorpink textCustom Component clickclickButton()/my-button-twotitle02_属性添加/title/headbodyscript// 自定义方法const clickButton () {alert(点击了自定义按钮);};class MyButtonTwo extends HTMLElement {// 监控属性变化static observedAttributes [color, text, click];constructor() {// 先调用父类构造器实例化 HTMLElement 这样才能有 html 元素的基本属性super();}// 元素添加到文档调用connectedCallback() {// 1.创建一个 divconst div document.createElement(div);// 2.设置 div 的样式div.className my-button-two;// 3.设置 div 的内容const bgColor this.getAttribute(color);const textValue this.getAttribute(text);const clickValue this.getAttribute(click);// 需要在同一个 js 执行环境内部执行div.addEventListener(click, () {eval(clickValue);});div.style.backgroundColor bgColor;div.innerHTML textValue;// 4.将 div 添加到页面this.appendChild(div);}// 元素从文档中移除时调用disconnectedCallback() {console.log(自定义元素从页面移除);}// 元素被移动到新文档时调用adoptedCallback() {console.log(自定义元素被移动到新文档);}// 监听属性变化attributeChangedCallback(name, oldValue, newValue) {console.log(属性 ${name} 已由 ${oldValue} 变更为 ${newValue});}}customElements.define(my-button-two, MyButtonTwo);// 监听组件状态customElements.whenDefined(my-button-two).then(() {console.log(my-button-two 组件已定义);});/script/body
/html对着调试控制台我们可以发现当前 html 写的样式可以影响到组件内部这并不符合我们之前说的组件和外部彼此 属性隔离 的特点这就需要了解到下一个概念了。
Shadow DOM
使用影子 DOM - Web API | MDN
影子 DOMShadow DOM允许你将一个 DOM 树附加到一个元素上并且使该树的内部对于在页面中运行的 JavaScript 和 CSS 是隐藏的。 有一些 影子 DOM 术语 需要注意
影子宿主Shadow host影子 DOM 附加到的常规 DOM 节点。影子树Shadow tree影子 DOM 内部的 DOM 树。影子边界Shadow boundary影子 DOM 终止常规 DOM 开始的地方。影子根Shadow root影子树的根节点。
这里的 影子宿主Shadow host 可以选取普通的 div 标签但是由于我们是自定义元素这里的 挂载节点 就是 自定义组件 Web Component 了接下来我们举一个例子 const shadow this.attachShadow({ mode: open });// 这里的 this 就是标识 自定义组件 DOM 元素
// mode 分为 open closed 表示能否通过 dom.shadowRoot 获取
// 不能获取的话只能在内部通过 shadow 访问了!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /style* {margin: 0;padding: 0;}body {width: 100px;margin: 200px auto;background-color: #f5f5f5;}.my-button-two {width: 180px;height: 50px;background-color: red;color: white;text-align: center;line-height: 50px;cursor: pointer;}/stylemy-button-two colorpink textCustom Component clickclickButton()/my-button-twotitle03_shadow dom/title/headbodyscript// 自定义方法const clickButton () {alert(点击了自定义按钮);};class MyButtonTwo extends HTMLElement {// 监控属性变化static observedAttributes [color, text, click];constructor() {// 先调用父类构造器实例化 HTMLElement 这样才能有 html 元素的基本属性super();}// 元素添加到文档调用connectedCallback() {// 隔离 DOMconst shadow this.attachShadow({ mode: open });// 1.创建一个 divconst div document.createElement(div);// 2.设置 div 的样式div.className my-button-two;// 3.设置 div 的内容const bgColor this.getAttribute(color);const textValue this.getAttribute(text);const clickValue this.getAttribute(click);// 需要在同一个 js 执行环境内部执行div.addEventListener(click, () {eval(clickValue);});div.style.backgroundColor bgColor;div.innerHTML textValue;// 4.将 div 添加到页面shadow.appendChild(div);}// 元素从文档中移除时调用disconnectedCallback() {console.log(自定义元素从页面移除);}// 元素被移动到新文档时调用adoptedCallback() {console.log(自定义元素被移动到新文档);}// 监听属性变化attributeChangedCallback(name, oldValue, newValue) {console.log(属性 ${name} 已由 ${oldValue} 变更为 ${newValue});}}customElements.define(my-button-two, MyButtonTwo);// 监听组件状态customElements.whenDefined(my-button-two).then(() {console.log(my-button-two 组件已定义);});/script/body
/html
这里我们可以看到 文档的样式已经无法影响我们的自定义组件了这是因为被 shadow 阻隔了接下来就可以继续完善这段逻辑了。
Template and Slot
使用模板和插槽 - Web API | MDN
前端组件开发中有两套我们熟悉的 Template模板和 Slot插槽接下来就利用这两个功能继续完善一下我们的代码逻辑。
!DOCTYPE html
html langenheadmeta charsetUTF-8 /meta nameviewport contentwidthdevice-width, initial-scale1.0 /style* {margin: 0;padding: 0;}body {width: 100px;margin: 200px auto;background-color: #f5f5f5;}.my-button-two {width: 180px;height: 50px;background-color: red;color: white;text-align: center;line-height: 50px;cursor: pointer;}/styletemplate idbutton-templatestyle.my-button-two {width: 180px;height: 50px;background-color: red;color: white;text-align: center;line-height: 50px;cursor: pointer;}/stylediv classmy-button-twoslot nametext/slot/div/templatemy-button-two idmy-button-two colorpink clickclickButton()span slottextCustom Component/span/my-button-twotitle04_Tempalte and Slot/title/headbodyscript// 自定义方法const clickButton () {alert(点击了自定义按钮);};class MyButtonTwo extends HTMLElement {// 监控属性变化static observedAttributes [color, text, click];constructor() {// 先调用父类构造器实例化 HTMLElement 这样才能有 html 元素的基本属性super();}// 元素添加到文档调用connectedCallback() {// 隔离 DOMconst shadow this.attachShadow({ mode: closed });// 1.获取模板const template document.querySelector(#button-template);// 2.克隆模板const content template.content.cloneNode(true);// 3.显示文本const clickValue this.getAttribute(click);// 4.执行函数const clickEvent content.querySelector(.my-button-two);clickEvent.addEventListener(click, () {eval(clickValue);});// 5.将 template 添加到页面shadow.appendChild(content);}// 元素从文档中移除时调用disconnectedCallback() {console.log(自定义元素从页面移除);}// 元素被移动到新文档时调用adoptedCallback() {console.log(自定义元素被移动到新文档);}// 监听属性变化attributeChangedCallback(name, oldValue, newValue) {console.log(属性 ${name} 已由 ${oldValue} 变更为 ${newValue});}}customElements.define(my-button-two, MyButtonTwo);// 监听组件状态customElements.whenDefined(my-button-two).then(() {console.log(my-button-two 组件已定义);});/script/body
/html
艺龙酒店科技官网
举例 video 标签就是利用这套机制封装的…
Exparser 框架原理
Exparser 是微信小程序的组件组织框架内置在小程序基础库中为小程序提供各种各样的组件支撑。内置组件和自定义组件都有 Exparser 组织管理。
Exparser 的组件模型与 WebComponents 标准中的 Shadow DOM 高度相似Exparser 会维护整个页面的节点树相关信息包括节点的属性、事件绑定等相当于一个简化版的 Shadow DOM 实现。Exparser 的主要特点包括以下几点
基于 Shadow DOM 模型模型上与 WebComponents 的 Shadow DOM 高度相似但不依赖浏览器的原生支持也没有其他依赖库实现时还针对性地增加了其他 API 以支持小程序组件编程。可在纯 JS 环境中运行这意味着逻辑层也具有一定的组件树组织能力。高效轻量性能表现好在组件实例极多的环境下表现尤其优异同时代码尺寸也较小。
自定义组件 上图是小程序利用 shadow dom 实现 样式和JS 逻辑隔离的组件这只是第一层里面的 view text 也是由 Exparser 从普通 div span 封装得来的接下来让我们深入了解下
内置组件
接下来带大家一步步过一遍微信小程序内置组件是如何渲染的
// 1.在微信开发工具找到解析命令 wcc
// wcc 是将 wxml 解析为 js 文件然后逻辑线程注入 webview 执行的
微信web开发者工具\code\package.nw\node_modules\wcc-exec// 2.将命令文件移动到文件目录下开始执行解析
./wcc -js index.wxml dom.js可以看到本质上就是一个封装好的 $gwx 函数它的作用是生成微信自定义的组件和虚拟 dom 节点 diff 算法用来给后面的 Exparser 生成真实的 DOM 节点那这个函数是在哪里调用的呢我们继续向下看
// 1. 调试控制台打开当前页面的 webview
document.getElementsByTagName(webview)
document.getElementsByTagName(webview)[0].showDevTools(true, null)// 2. 可以发现编译后的 wxml 会利用 js 脚本以一定格式插入到页面中执行
var decodeName decodeURI(./pages/command_component/index.wxml)
var generateFunc $gwx(decodeName)generateFunc()// 3.传入数据
generateFunc({logs:[1,2,3]})view wx:for{{ logs }} wx:keyindextext{{ item }}/text
/view可以看到如上图所示的虚拟节点数组接下来我们详细剖析一下
$gwx(decodeName) 不直接返回 dom 树而是返回一个函数的原因是因为需要动态注入和相关配置函数能够很好的把控时机利用动态传参我们发现包含循环数组和 key 的会带有 virtual 标识用来后面的 DIff 算法比较document.dispatchEvent 触发自定义事件 将 generateFunc 当作参数传递给底层渲染库 可以看得到无论是 view 还是 text 底层都是通过 div span 的自定义组件构成的这一切来源于 Exparser 框架在 渲染层 会内置一系列方法大致和上面自定义 web component 一致进行对组件的定义注册后将 js 脚本引入页面那么当前页面就可用了接下来带大家进行源码的拆解
下周计划
继续深入小程序原理收益不高扩展前端其他的技术方向感兴趣建议 前端组件库实现拆解前端调试能力提升前端工程化能够了解