mini-vue 的设计

mini-vue 的设计

mini-vue 使用流程与结果预览:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app"></div><button class="btn">执行patch</button><hr></hr><button class="refreshBtn">刷新页面</button></body><script src="./renderer.js"></script><script src="./mount.js"></script><script src="./patch.js"></script><script>/*** 思路:* 1. 通过 h 函数创建 vnode* 2. 通过 mount 函数挂载*/// 1. 生成 vnodeconst vnode = h("div",{ class: "youxiaobei", id: "oldId", del: "这是将被删除的" },[h("ul", null, [h("li", null, "我是一个小li"),h("li", null, "我是一个小li"),h("li", null, "我是一个小li"),h("li", null, "我是一个小li"),]),h("button", null, "这是将被保留的小小按钮"),h("h4", null, "以上内容都会被比较为不同然后删除,包括我")]);// 2. 挂载,生成真实 dom,添加到 container 容器中const container = document.getElementById("app");mount(vnode, container);// 3. 新节点 01 (最外层 tagName 不一样,直接都被替换了)const newVnode = h("h2", { class: "newNode", id: "newId" }, [h("button", null, "我是你后来加的小按钮"),]);const btn = document.querySelector('.btn')btn.addEventListener("click", () => {patch(vnode, newVnode);btn.disabled = true;},true);const refreshBtn = document.querySelector('.refreshBtn')refreshBtn.addEventListener("click", () => {location.reload()})</script><style>/* 新的节点背景色是红色的 */.newNode {background-color: red; }</style>
</html>

执行 patch 前:

img01

执行后:

img02

1. h 函数

h 函数也就是 render 函数,作用简单:返回一个 Vnode 虚拟节点,但很重要!

/*** h 函数* 功能:返回vnode** @param {String} tagName  - 标签名* @param {Object | Null} props  - 传递过来的参数* @param {Array | String} children  - 子节点* @return {vnode} 虚拟节点*/
const h = (tagName, props, children) => {// 直接返回一个对象,里面包含vnode结构return {tagName,props,children,};
};

2. 响应式

考虑以下功能:

  1. 收集依赖某个数据的函数
  2. 当数据变化后,重新执行依赖此数据的函数
/*** 实现一个类* 构造一个 订阅列表 subscribers set 对象* 收集者 addEffect  添加影响的函数,往 subscribers 里面添加* 通知者 notifier 通知函数, 依次执行 subscribers 里面的函数*/
class Dep {constructor() {// 1.订阅列表 构造一个 subscribers set 对象this.subscribers = new Set();}// 2. 收集者 addEffect 添加影响的函数addEffect(effect) {this.subscribers.add(effect);}// 3. 通知者 notifiernotifier() {this.subscribers.forEach((effect) => {effect();});}
}const dep = new Dep();let count = 1;const addFun = function () {console.log(++count);
};addFun(); // 2// 收集订阅
dep.addEffect(addFun);// 当数据改变后
count = 100;// 通知者通知函数重执行
dep.notifier(); // 101

3. mount 函数

挂载 Vnode 为真实的 DOM 元素

/*** mount 函数* 功能:挂载 vnode 为 真实dom* 重点:递归调用处理子节点** @param {Object} vnode -虚拟节点* @param {elememt} container -需要被挂载节点*/
const mount = (vnode, container) => {// 1. 创建出真实元素, 给 vnode 添加 el 属性const el = (vnode.el = document.createElement(vnode.tagName));// 2. 处理 propsif (vnode.props) {for (const key in vnode.props) {const value = vnode.props[key];// 2.1 prop 是函数if (key.startsWith("on")) {el.addEventListener(key.slice(2).toLowerCase(), value);} else {// 2.2 prop 是字符串el.setAttribute(key, value);}}}// 3. 处理 childrenif (vnode.children) {// 3.1 如果 children 是字符串,直接设置文本内容if (typeof vnode.children === "string") {el.textContent = vnode.children;}// 3.2 如果 children 是数组,递归挂载每个子节点else {// 先拿到里面的每一个 vnodevnode.children.forEach((item) => {// 再把里面的vnode递归调用mount(item, el);});}}// 4. 挂载container.appendChild(el);
};

04. patch 函数

patch 对比节点数组,优化性能

/*** 节点比较* 调用时机:节点发生变化(数量,内容)* 功能:比较节点数组,尽可能减少 DOM 操作*//*** @param {Vnode} n1 - 旧节点* @param {Vnode} n2 - 新节点*/
const patch = (n1, n2) => {// 节点不相同,卸载旧节点,挂载新节点if (n1.tagName !== n2.tagName) {const parentElementNode = n1.el.parentElement;parentElementNode.removeChild(n1.el);mount(n2, parentElementNode);} else {// 1. 取出 element 并保存到 n2const el = (n2.el = n1.el);// 2. 处理 propsconst oldProps = n1.props || {};const newProps = n2.props || {};for (const key in newProps) {const oldValue = oldProps[key];const newValue = newProps[key];// 2.1 值不同才替换if (oldValue !== newValue) {if (key.startsWith("on")) {el.addEventListener(key.slice(2).toLowerCase(), newValue);} else {// 2.2 prop 是字符串el.setAttribute(key, newValue);}}}// 3. 删除旧的 propsfor (const key in oldProps) {// 如果旧 key 不在新的 props 里if (!(key in newProps)) {const oldValue = oldProps[key];if (key.startsWith("on")) {el.removeEventListener(key.slice(2).toLowerCase(), oldValue);} else {// 2.2 prop 是字符串el.removeAttribute(key, oldValue);}}}// 4. 处理 childrenconst oldChildren = n1.children;const newChildren = n2.children;// children 字符串if (typeof newChildren === "string") {// 4.1 如果新 children 是字符串,直接设置文本内容if (oldChildren !== newChildren) {el.textContent = newChildren;} else {el.innerHTML = newChildren;}} else {// 4.2 如果新 children 是数组,递归挂载每个子节点// 如果旧 children 的是字符串if (typeof oldChildren === "string") {el.innerHTML = "";// 遍历 childrennewChildren.forEach((item) => {mount(item, el);});} else {// 两个都是数组,开始 diff 算法// n1: [a,b,d]// n2: [b,a,c,f]/*** 没有 key*/if (!n1.props.key && !n2.props.key) {// 4.3.1 获取两个 vnode 数组的公共长度,比较相同的const commonLength = Math.min(oldChildren.length, newChildren.length);for (let i = 0; i < commonLength; i++) {patch(oldChildren[i], newChildren[i]);}// 4.3.2 新的长度多于旧的,挂载if (oldChildren.length < newChildren.length) {newChildren.slice(oldChildren.length).forEach((item) => {mount(item, el);});}// 4.3.3 旧的长度多于新的,卸载if (oldChildren.length > newChildren.length) {oldChildren.slice(newChildren.length).forEach((item) => {el.removeChild(item.el);});}} else {/*** 有 key*/// 4.4.1 根据 key 创建一个映射表,方便查找和比较const keyMap = {};oldChildren.forEach((child) => {if (child.props.key) {keyMap[child.props.key] = child;}});// 4.4.2 遍历新的 children 数组newChildren.forEach((newChild, index) => {const oldChild = keyMap[newChild.props.key];if (oldChild) {// 4.4.2.1 如果旧的 children 存在对应的 key,对比并更新子节点patch(oldChild, newChild);oldChildren[index] = oldChild; // 更新旧的 children 数组,方便后续删除处理} else {// 4.4.2.2 如果旧的 children 中没有对应的 key,说明是新增的节点,直接挂载mount(newChild, el, index);}});// 4.4.3 删除旧的 children 中没有对应的 key 的子节点oldChildren.forEach((oldChild) => {if (!oldChildren.find((child) => child.props.key === oldChild.props.key)) {el.removeChild(oldChild.el);}});}}}}
};

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Unity - 各向异性 - 丝绸材质

文章目录 目的环境主观美术效果的[假]丝绸基于物理的方式ProjectPBR filament web captureReferences 目的 拾遗&#xff0c;备份 环境 Unity : 2020.3.37f1 Pipeline : Builtin Rendering Pipeline 主观美术效果的[假]丝绸 非常简单 : half specualr pow(1 - NdotV, _Edg…

换根dp学习笔记

最近模拟赛经常做到&#xff0c;于是我就学习了一下。 算法原理 换根 d p dp dp的题一般都会给出一个无根树&#xff0c;因为以不同的点为根时&#xff0c;问题的答案不一样&#xff0c;所以它会让你输出答案的最大或最小值。 暴力去做这种题&#xff0c;就是以每个点为根然…

汽车ECU的虚拟化技术初探(一)

目录 1.为什么要提汽车ECU的虚拟化&#xff1f; 2.虚拟化技术分类 2.1 硬件虚拟化 2.2 操作系统虚拟化 问题引入&#xff1a; Hypervisor是如何来管理和隔离硬件资源&#xff0c;保证各个不同功能的应用程序的资源使用安全和资源调度&#xff1f;没有MMU就做不了虚拟化&am…

CS224W6.2——深度学习基础

在本文中&#xff0c;我们回顾了深度学习的概念和技术&#xff0c;这些概念和技术对理解图神经网络至关重要。从将机器学习表述为优化问题开始&#xff0c;介绍了目标函数、梯度下降、非线性和反向传播的概念。 文章目录 1. 大纲2. 优化问题2.1 举例损失函数 3. 如何优化目标函…

案例续集留言板

前端没有保存数据的功能,后端把数据保存下来(内存,数据库等等......) 前端代码如下 : <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initia…

RT-Thread:嵌入式实时操作系统的设计与应用

RT-Thread&#xff08;Real-Time Thread&#xff09;是一个开源的嵌入式实时操作系统&#xff0c;其设计和应用在嵌入式领域具有重要意义。本文将从RT-Thread的设计理念、核心特性&#xff0c;以及在嵌入式系统中的应用等方面进行探讨&#xff0c;对其进行全面的介绍。 首先&a…

git的分支及标签使用及情景演示

目录 一. 环境讲述 二.分支 1.1 命令 1.2情景演练 三、标签 3.1 命令 3.2 情景演示 ​编辑 一. 环境讲述 当软件从开发到正式环境部署的过程中&#xff0c;不同环境的作用如下&#xff1a; 开发环境&#xff1a;用于开发人员进行软件开发、测试和调试。在这个环境中…

2023数据安全战场回顾:迅软科技助您稳固阵线

随着各行业的数字化转型不断深入&#xff0c;数据安全逐步进入法制化的强监管时代。然而&#xff0c;由于人为攻击、技术漏洞和监管缺位等原因&#xff0c;各种数据泄露事件频繁发生&#xff0c;企业数据安全威胁日益严峻。 以下是我对2023年第三季度安全事件的总结&#xff0c…

PHP生成pdf格式准考证带照片完整示范

PDF效果图 PHP生成pdf格式准考证带照片完整示范以某省公务员考试下载的准考证模板为模板参考&#xff0c;故很有参考意义。 环境支持:linux PHP(5.5-7.3)环境,推荐宝塔环境。 基于fpdf.php插件开发&#xff0c;现有模板适合准考证生成并用于查询下载。 现有排版简单:替换data文…

springboot项目使用Swagger3

一、Swagger介绍 号称世界上最流行的Api框架&#xff1b;Restful Api 文档在线自动生成工具>Api文档与API定义同步更新直接运行&#xff0c;可以在在线测试API 接口支持多种语言&#xff1a;&#xff08;java&#xff0c;Php…&#xff09; 二、Swagger3 准备工作 1、在p…

计算机中丢失mfc140u.dll怎么解决

mfc140u.dll是一个Microsoft Visual C库文件&#xff0c;主要用于MFC&#xff08;Microsoft Foundation Class&#xff09;应用程序的开发。它包含了MFC应用程序所需的一些常用功能&#xff0c;如对话框、窗口、菜单等。当mfc140u.dll丢失时&#xff0c;可能会导致MFC应用程序无…

Vue3+NodeJS 接入文心一言, 发布一个 VSCode 大模型问答插件

目录 一&#xff1a;首先明确插件开发方式 二&#xff1a;新建一个Vscode 插件项目 1. 官网教程地址 2. 一步一步来创建 3. 分析目录结构以及运行插件 三&#xff1a;新建一个Vue3 项目&#xff0c;在侧边栏中展示&#xff0c;实现vscode插件 <> vue项目 双向消息传…