day-112-one-hundred-and-twelve-20230714-DOM-diff算法-构建工具-包管理工具-Vite基本使用-Vue3新特性
DOM-diff算法
vue2中diff算法
- 同级比对,跨级比对性能差。而且采用的方式是递归比对,更差一点。
- 根节点只能有一个,比对的时候会从根节点进行比对,先判断两个根节点是否是同一个节点。
- 如果不是同一个节点则直接将老节点删除掉,换成新节点。
- 根据标签名、key。isSameVnode()这个方法来判断是否是相同的同一个节点。
- 如果两个节点的key和标签名一致-是相同节点。不过还要判断该相同节点的属性与子元素是否已经改变。
- 虚拟DOM的比对是:
- 第一次渲染的时候会根据虚拟DOM创建一个真实DOM。
- 更新后会再次产生一个新的虚拟节点。如果isSameVnode为true,则复用之前的dom,比较前后两次节点的差异,最终更新到页面上。
- 递归比较子节点:
- 新儿子有,老儿子没有。根据新的虚拟节点创建真实节点插入到老节点中。
- 老儿子有,新的儿子没有。删除老的儿子,移除DOM元素。
- 两方都有。diff算法。
- 先做了优化-双端比对。对DOM常见的操作做了优化。
- 常见操作:(增-向前向后添加一个、倒序正序、删除)
- diff算法中key的作用:用来识别dom元素的,如果一致的key和tag会认为是同一个元素,在使用的时候key不要不写,或者采用索引。
- 给新老节点增加头尾指针-默认从头部开始比对,头指针比对成功后会向后移动,尾指针比对后会向前移动。
- 如果比对成功后指针向后移动,继续进行比对,如果多出来的则插入到老节点中。
- 如果头部指针无法比对成功,则从尾部进行比较。删除也可以通过这种方式进行比较操作。
- 针对尾部移动到头部来说,如果头和头、尾和尾部都不行,比较尾部和头部,比对成功将尾部移动到头部去,并且继续。
- 头和头、尾和尾都不行的情况下…。
- 针对尾部移动到头部来说,和上面是一致的。
- 如果没有走到优化策略,则会采用暴力比对的方式。将新节点的每一个拿出来和老节点去比对,如果找到了则复用,并且移动,如果找不到则直接插入到头指针的前面。每一轮都要经历头头、尾尾、头尾、尾头,最终老的多的删掉,新的多的则创建并插入。
- 先做了优化-双端比对。对DOM常见的操作做了优化。
- 老儿子是文本,新的儿子也是文本。可以用新的文本换掉老的文本。
- 递归比较子节点:
- 如果不是同一个节点则直接将老节点删除掉,换成新节点。
实际比对流程
- 先比较是否是相同节点 (isSameVNode)。
- 如果是相同节点,则比较属性,并复用老节点(将老的虚拟 dom 复用给新的虚拟节点 DOM)。
- 比较属性并进入下一步:
- 属性不相同,用新的属性替换旧的属性。
- 属性相同,则不更新。
- 比较儿子节点:
- 老的没儿子,新的有儿子。直接插入新的儿子。
- 老的有儿子,新的没儿子。直接删除页面节点。
- 老的儿子是文本,新的儿子是文本,直接更新文本节点即可。
- 老的有儿子,新的也有儿子。
- 老的儿子是一个列表,新的儿子也是一个列表 updateChildren。
- 先做了优化-双端比对。对DOM常见的操作做了优化。
- 每次都从老新儿子列表中各自拿出一个值来进行比较。
老节点列表的头部
与新节点列表的头部
进行比较。- 结果相同:
老节点列表的头部指针
向后移动一位,新节点列表的头部指针
向后移动一位。跳出本轮次,进行下一轮次的比较。 - 结果不同,进入本轮次第2步。
- 结果相同:
老节点列表的尾部
与新节点列表的尾部
进行比较。- 结果相同:
老节点列表的尾部指针
向前移动一位,新节点列表的尾部指针
向前移动一位。跳出本轮次,进行下一轮次的比较。 - 结果不同,进入本轮次第3步。
- 结果相同:
老节点列表的头部
与新节点列表的尾部
进行比较。- 结果相同:
老节点列表的头部指针
向后移动一位,新节点列表的尾部指针
向前移动一位,移动节点-具体个人不太清楚。跳出本轮次,进行下一轮次的比较。 - 结果不同,进入本轮次第4步。
- 结果相同:
老节点列表的尾部
与新节点列表的头部
进行比较。- 结果相同:
老节点列表的尾部指针
向前移动一位,新节点列表的尾部指针
向前后动一位,移动节点-具体个人不太清楚。跳出本轮次,进行下一轮次的比较。 - 结果不同,进入本轮次第5步。
- 结果相同:
- 采用暴力比对的方式。将新节点的每一个拿出来和老节点去比对,如果找到了则复用,并且移动,如果找不到则直接插入到头指针的前面。这一步必定可以解决,进入下一轮次。
- 每次都从老新儿子列表中各自拿出一个值来进行比较。
- 先做了优化-双端比对。对DOM常见的操作做了优化。
- 老的儿子是一个列表,新的儿子也是一个列表 updateChildren。
- 比较属性并进入下一步:
- 如果不是相同节点,则新创建一个元素。
- 如果是相同节点,则比较属性,并复用老节点(将老的虚拟 dom 复用给新的虚拟节点 DOM)。
- Vue2的diff算法–updateChildren图文流程以及缺点
key的作用
-
key
的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。 -
当 Vue 正在更新使用
v-for
渲染的元素列表时,它默认使用“就地更新”的策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序,而是就地更新每个元素,并且确保它们在每个索引位置正确渲染。 -
Vue 在 patch 过程中通过 key 可以判断两个虚拟节点是否是相同节点。 (可以复用老节点)
-
无 key 会导致更新的时候出问题,尽量不要采用索引作为 key。
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<div id="app"><li v-for="item in list" :key="item"><input type="checkbox"> {{item}}</li><button @click="add">增加</button>
</div>
<script>const vm = new Vue({el: '#app',data: {list: [1, 2, 3, 4]},methods: {add() {this.list.unshift(Math.random())}}})
</script>
- 有key,会在前面新增,效果与预期一致。
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<div id="app"><li v-for="item in list" :key="item"><input type="checkbox"> {{item}}</li><button @click="add">增加</button>
</div>
<script>const vm = new Vue({el: '#app',data: {list: [1, 2, 3, 4]},methods: {add() {this.list.unshift(Math.random())}}})
</script>
- 无key或key为index,会在后面新增,效果与预期不符,勾选的选项会错位。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app"><li v-for="(item,index) in list"><input type="checkbox" />{{item}}</li><button @click="unshift()">没有key时。向前追加,`a、b、c、d`会复用成`e、a、b、c`,新增的DOM给到了d-而不是我们预期的e</button><button @click="reverse()">倒序让子元素变动了三次,不过DOM都复用了</button></div><script src="https://cdn.jsdelivr.net/npm/vue@2.7.14"></script><script>const vm = new Vue({el: "#app",data() {return {list: ["a", "b", "c", "d"],};},methods: {reverse() {this.list.reverse();},unshift() {this.list.unshift("e");},},});</script></body>
</html>
数组的数据劫持
- 为什么数据不能采用Object.defineProerty(),因为数据的length可以设置得很大,进而劫持的时候浪费性能。
- 虽然Object.defineProerty()可以监听到数组的角标,但由于路由的length可以设置成很大。如Object.defineProerty(new Array(10000)),会监听很多的的属性,设置很多的监听器,进而让性能浪费。
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script>// 设计模式:工厂模式、单例、 发布订阅、 观察者模式、策略模式, 代理模式// 为什么数组不能采用Object.defineProperty 因为劫持的时候浪费性能// 数组采用重写方法的方式// 数组不能采用Object.defineProerty()。虽然Object.defineProerty()可以监听到数组的角标,但由于路由的length可以设置成很大。如Object.defineProerty(new Array(10000)),会监听很多的的属性,设置很多的监听器,进而让性能浪费。const arr = ['a','b','c'];for(let key in arr){Object.defineProperty(arr,key,{get(){},set(){}})}console.log(arr)</script></body>
</html>
响应式数据的原理
- 具体流程:
- 渲染的时候会去调用render函数-每个组件都有一个watcher,当渲染的时候会将这个watcher放到全局上。
- 调用render方法时候会去取值,每个属性都有一个dep数组,只要取值就让这个dep数组
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><script>// 设计模式:工厂模式、单例、 发布订阅、 观察者模式、策略模式, 代理模式// 发布订阅模式(我们会把事件订阅好,后续自己来触发 【f,f,f,f】)// 观察者模式(包含发布订阅模式) 一个小宝宝会记录自己父亲和母亲,等会状态变化了会主动通知父亲和母亲function render() {// 页面渲染的时候会取取响应式数据console.log(state.name);}class Dep {constructor() {this.set = new Set(); // dep里面存的是watcher}depend() {this.set.add(Dep.target); // 属性和watcher产生关联}notify() {this.set.forEach((watcher) => watcher.update());}}Dep.target = null; // 默认没有任何的watcher正在执行class Watcher {// 页面的渲染逻辑constructor(getter) {this.render = getter;this.get();}get() {Dep.target = this;this.render();Dep.target = null;}update() {this.render();}}// 1.渲染的时候会去调用render函数 (每个组件都有一个watcher,当渲染的时候会将这个watcher放到全局上)// 2.调用render方法时候会去取值, 每个属性都有一个dep,只要取值就让这个dep记住这个watcher// 对数据进行数据劫持的目的 : 能监控用户的取值和设置值的操作。function isObject(data) {return data !== null && typeof data === "object";}function defineReactive(target, key, value) {const dep = new Dep();observe(value);Object.defineProperty(target, key, {get() {if (Dep.target) {// 在组件中渲染的dep.depend(); // 让这个属性的dep,去记录他对应的是哪个组件}return value;},set(newValue) {if (newValue === value) return;observe(newValue);dep.notify();value = newValue;},});}function observe(data) {if (!isObject(data)) {return;}for (let key in data) {defineReactive(data, key, data[key]);}}const state = { name: "zf", age: { num: 100 } };observe(state);// vue中的特点叫组件及更新 (更新是以组件为单位的)debugger;new Watcher(render);debugger;state.name = "jw";// 作业:总结diff算法, key的作用, 依赖收集的流程</script></body>
</html>
- 每个属性都拥有自己的
dep
属性,存放他所依赖的 watcher,当属性变化后会通知自己对应的 watcher 去更新 - 默认在初始化时会调用 render 函数,此时会触发属性依赖收集
dep.depend
- 当属性发生修改时会触发
watcher
更新dep.notify()
class Dep {constructor() {this.subs = new Set;}depend() {this.subs.add(Dep.target); // 让属性记住这个watcher}notify() {this.subs.forEach(watcher => watcher.update()); // 通知记住的watcher更新}
}
class Watcher{constructor(fn) {this.getter = fn;this.get();}get() { // 第一次渲染Dep.target = this;this.getter();Dep.target = null;}update() { // 数据变化后更新this.get();}
}
function defineReactive(obj, key, value) {const dep = new Dep();Object.defineProperty(obj, key, {get() {if (Dep.target) { // 说明是在watcher中访问的属性dep.depend()}return value;},set(newValue) { // 如果设置的是一个对象那么会再次进行劫持if (newValue === value) returnobserve(newValue);value = newValuedep.notify();}})
}
function isObject(value) {return typeof value === 'object' && value !== null;
}
function observe(value) {if(!isObject(value)){return;}Object.keys(value).forEach(key=>{ // 要使用defineProperty重新定义defineReactive(value,key,value[key]);});
}
const state = {name:'jw'}
observe(state); // 观测状态,在组件渲染时使用此状态
function render() { console.log(state.name)
}
new Watcher(render);
vue响应式数据的特点
- vue中的特点是组件级更新,更新是以组件为单位的。
设计模式
- 设计模式:
- 工厂模式、单例设计模式、发布订阅、观察者模式、策略模式、代理模式。
作业
- 总结diff算法、key的作用、依赖收集的流程。
构建工具
- webpack特点:
- 很强大,功能丰富,生态好,很多插件,webpack启动是比较慢的,内部会将所有的模块进行打包,打包后再启动服务,热更新,随着项目的增大依旧会卡。
- 如果项目特别大,在最终打包的时候也是很大的。
- 有方案来解决:
- 微前端,将一个项目拆分成多个子项目,最终再组件在一起。
- 微前端的好处就是每个子项目是一个独立的项目技术栈可以不同,最后组合在一起。
- 微前端,将一个项目拆分成多个子项目,最终再组件在一起。
- 有方案来解决:
- webpack比较强大,可以处理各种资源。可以使用ES6Module的import、nodejs的require、多线程打包插件。
- 如果项目特别大,在最终打包的时候也是很大的。
- 很强大,功能丰富,生态好,很多插件,webpack启动是比较慢的,内部会将所有的模块进行打包,打包后再启动服务,热更新,随着项目的增大依旧会卡。
- rollup他的目的主要就是打包类库(react源码、vue源码都是采用的rollup) 打包速度快、只支持esModule,可以更好地支持tree-shaking。而且生态也很好,用起来比webpack更简单-支持插件。
- gulp 通过流的方式对资源进行转换-不用全部打包在一起,通常在写组件库中会使用。
- esbuild 使用的是go语言编写的,用于并行打包资源,整个都进行了重写,内置了一些打包需要用的插件。但只适合开发环境,生产环境不够完善。内部也支持插件,但由于种种的原因,没有在生产环境下使用。
- parcel 打包工具,基本没什么人用。
高版本浏览器内置支持的ESMoule
- index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><script type="module">import a from './a.js'console.log(a)</script>
</body>
</html>
- a.js
export default {name:'zf'
}
- 其中其中a.js就是用ESModule来导入的。它的运行也是ES6模块的运行环境。
vite
-
vite内部会对代码进行分类,源码与第三方库,自己写的代码。
-
开发的时候vite采用esbuild来进行开发环境。它采用esbuild来做一些开发所需的第三方依赖来打包-预编译,放在那里。打包的结果到时候请求的时候资源小,而且会对这些模块进行合并以便减少请求。
-
后续的源码通过浏览器内置的esModule来处理。不过开发环境下需要使用高版本浏览器-即支持esModule的浏览器。
-
vite只需要快速的启动一个本地服务即可,其它的操作都交给浏览器来处理。请求的时候再对这个文件做编译,如:请求
xxx.vue
这类文件要转成js,而浏览器实际请求到的也是js文件。 -
vite冷启动非常快。
-
优化:代码体积,性能好,按需加载(依赖于浏览器内置的ESModule的特性)。
-
对于生产环境而言:生产环境采用了rollup性能还不错,rollup打包出来的资源小。rollup天生支持esModule,对tree-shaking支持的也比较好。插件写起来也方便。
包管理工具
- npm vs yarn vs pnpm
- npm包管理工具(慢)
- yarn需要用npm安装(无法干掉npm),yarn增加了很多功能。在一个包下管理多个包
- pnpm 软链接,安装速度快。
vite项目
vite介绍
- Vite(法语意为 “快速的”,发音
/vit/
,发音同 “veet”)是一种新型前端构建工具,能够显著提升前端开发体验。它主要由两部分组成:- 一个开发服务器,它基于原生 ES 模块提供了丰富的内建功能,如速度快到惊人的模块热更新(HMR)。(在开发环境中会基于ES模块启动一个本地服务,优势是可以进行按需加载。并且可以实时预览开发的内容)
- 一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源。(生产环境是基于rollup来进行打包,提供了内置配置,最终打包出来的资源小。可直接部署到服务器上)
vite与webpack的对比
开发环境对比
- Webpack:冷启动(首次启动)开发服务器时,需要构建完整的应用,之后才能提供开发服务。(大型项目启动时非常缓慢,后续更新也非常缓慢) Webpack中大量插件由第三方编写,可能会有性能差等问题。
- Vite:冷启动时,不需要等待构建完整的应用 (vite支持预构建)
Vite
使用esbuild
对第三方模块进行预构建,将非ESM规范的代码转换为ESM规范的代码,同时可以将多个文件资源合并成一个,减少http请求。(Vite是基于ESModule的) 在服务启动后浏览器会根据入口文件需要发送相应请求,获取资源时对代码进行转化等操作。
快速的冷启动、实时模块转换。
esbuild
- 基于 Go 语言:ESBuild 是使用 Go 语言编写的构建工具。Go 语言可以充分利用多核CPU 实现并行构建。
- esbuild专注js、ts、jsx的构建和转译,使用自己的算法进行转换和优化,相比其他打包工具速度更快。
- esbuild同样也具有插件系统,可以扩展其自身功能。
但是目前esbuild在构建方面表现出色,但是在生产环境中还不够完善。生产环境我们更关心:代码体积、代码分割、代码缓存等。 Vite在生产环境中采用Rollup的目的也是认为 Rollup 提供了更好的性能与灵活性方面的权衡。
生产环境对比
- Webpack 是一个非常成熟和广泛采用的打包工具,拥有庞大的生态系统和丰富的插件支持,对于大型复杂的项目,Webpack 的成熟生态系统和丰富的功能一定为首选。
- 对于Vite而言,生产采用的是rollup。
- 体积更小:Rollup专注于 JavaScript 模块的打包,可以生成更小、更精简的输出文件,它提供了更好的 tree shaking(摇树优化)功能。
- 更快的构建速度:Rollup在打包时的速度通常要比webpack更快。
- 针对库的打包:Rollup更适合于构建独立的库或组件
- ES模块的支持:Rollup天生支持ES模块的语法。
- 插件系统:Rollup具有灵活的插件系统。
Vue2 一般采用webpack来处理,如果使用Vite还需要解决很多问题(老项目更不要在折腾了)。 Vue3 项目我们一般采用Vite来进行创建项目,当然也可以使用@vue/cli来进行创建。但要注意的是,ESModule需要浏览器天生支持才可以
Vite项目创建
node版本:v16.20.0
pnpm create vite
✔ Project name: … vue3-lesson 项目名
✔ Select a framework: › Vue 放在那个目录下
✔ Select a variant: › Customize with create-vue 创建什么框架的项目Vue.js - The Progressive JavaScript Framework✔ Add TypeScript? … No / Yes
✔ Add JSX Support? … No / Yes
✔ Add Vue Router for Single Page Application development? … No / Yes
✔ Add Pinia for state management? … No / Yes
✔ Add Vitest for Unit Testing? … No / Yes
✔ Add an End-to-End Testing Solution? › No
✔ Add ESLint for code quality? … No / Yes
✔ Add Prettier for code formatting? … No / Yes
vite的vue项目默认内置命令
- 默认内置命令:
- “dev”: “开发服务器”,
- “build”: “打包”,
- “preview”: “预览打包后文件”,
- “lint”: “检测.vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore的代码校验提示的”
- “format”: “代码格式化,好处是可以在打包时,一次性把项目中的文件都格式化一下”
- 默认可执行命令- "dev": "vite", //开启开发环境- "build": "vite build", //打包项目生产 dist 目录- "preview": "vite preview", //预览 dist 目录- "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", //代码格式校验- "format": "prettier --write src/" //格式化代码的
目录结构
/.vscode/extensions.json
可以配置项目中使用的 vscode 插件 vue3 中需要使用 volar/dist/
打包出的结果/public/
公共的静态文件,这个目录会直接拷贝到 dist 目录下,不会被 vite 处理/src/assets/
这个目录打包的时候会进行编译 增加 hash 戳,压缩/src/components/
组件/src/router/
路由相关的配置/src/views/
页面级别组件/src/App.vue
根组件/src/main.js
入口文件 esMoudle 直接引入/.eslintrc.cjs
主要是做 eslint 配置的/.prettierrc.json
配置用户格式化的方式/index.html
是整个入口文件
核心文件的目录层级结构及说明
.vscode
:-> extensions.json 推荐安装插件public
:该目录通常用于存放不需要经过构建处理的静态资源。这些资源在打包过程中不会被处理或修改,直接复制到输出目录中src
:源代码目录- assets:通常用于存放需要经过构建处理的静态资源,这些资源在打包过程中会被构建工具所处理
- components:项目中的公共组件
- router:路由配置
- views:页面及别组件
- App.vue: 项目根组件
- main.js:入口文件
.eslintrc.cjs
:eslint配置文件,检测代码质量.prettierrc.json
: prettier插件,代码格式化index.html
:静态文件入口文件vite.config.js
:vite的配置文件
删除代码得到最简项目
-
.vscode
:-> extensions.json 推荐安装插件 -
public
:该目录通常用于存放不需要经过构建处理的静态资源。这些资源在打包过程中不会被处理或修改,直接复制到输出目录中 -
src
:源代码目录-
assets/
- 一张图片。
-
router/
:路由配置-
index.js
import { createRouter, createWebHistory } from "vue-router" const router = createRouter({history: createWebHistory(import.meta.env.BASE_URL),routes: [] }) export default router
-
-
App.vue: 项目根组件
<script setup></script> <template>红色 </template> <style scoped></style>
-
main.js:入口文件
import { createApp } from 'vue'import App from './App.vue' import router from './router' const app = createApp(App) app.use(router) app.mount('#app')
-
-
.eslintrc.cjs
:eslint配置文件,检测代码质量 -
.prettierrc.json
: prettier插件,代码格式化 -
index.html
:静态文件入口文件<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><link rel="icon" href="/favicon.ico"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Vite App</title></head><body><div id="app"></div><div id="root"></div><script type="module" src="/src/main.js"></script></body> </html>
-
vite.config.js
:vite的配置文件
vite.config.js配置
// url模块 fileURLToPath 可以转化路径的
import { fileURLToPath, URL } from 'node:url'// 在使用vite的时候写配置可以提供提示
import { defineConfig } from 'vite'// @vitejs/plugin-vue 这个插件就是让vite能支持vue
// @vitejs/plugin-react
// @vitejs/plugin-vue-jsx 支持jsx 插件
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'/* console.log(`import.meta.url-->`, import.meta.url)
console.log(`拼接好的本地文件地址:new URL('./src', import.meta.url)-->`,new URL('./src', import.meta.url)
)
console.log(`拼接好的绝对路径:fileURLToPath(new URL('./src', import.meta.url))-->`,fileURLToPath(new URL('./src', import.meta.url))
) */// process.env.NODE_ENV
// import.meta.url
// https://vitejs.dev/config/export default defineConfig({// vite的插件直接放在plugis中即可plugins: [vue(),vueJsx(),],resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}},
})
Vite基本使用
图片
-
图片可以直接使用,还可以配置别名使用
<template><!-- webpack中想显示图片 file-loader url-loader;vite不用,它内置了 --><img src="./assets/logo.svg" style="width: 100px; height: 100px" /> </template>
-
vite-project/vite.config.js
export default defineConfig({resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}}, })
-
示例:
-
vite-project/src/App.vue
<template><!-- webpack中想显示图片 file-loader url-loader --><!-- alias --><img src="@/assets/logo.svg" style="width: 100px; height: 100px" /> </template>
-
vite-project/vite.config.js
export default defineConfig({resolve: {alias: {'@': fileURLToPath(new URL('./src', import.meta.url))}}, })
-
-
服务启动时引入一个静态资源会返回解析后的公共路径
<template><img src="./assets/logo.svg" style="width: 100px;height: :100px;">
</template>
路径别名
<img src="@@/logo.svg" style="width: 100px;height: 100px;">
resolve: {alias: {'@@': fileURLToPath(new URL('./src/assets', import.meta.url))}
}
CSS
css module
- vite-project/src/App.vue
<script setup>
import colors from "./var.module.css"
console.log(`colors-->`, colors);
</script>
<template><!-- css module css模块(文件后缀必须module.css) --><span :class="colors.gold"> 金色 </span><span :class="colors.fire"> 红色 </span>
</template>
- vite-project/src/var.module.css
.gold {color: gold;
}
.fire {color: red;
}
.water {color: blue;
}
支持less和sass
-
css支持css module的写法,安装less和scss后可以直接使用。
npm install lass
npm install sass
- scss用的还是sass这个依赖。
pnpm install less sass -D
- vite-project/src/App.vue
<script setup>
</script>
<template><span class="tree"> 绿色 </span><span class="solid"> 土色 </span><div class="box"></div><van-field />
</template><style scoped>
.box {width: 100px;height: 100px;background: red;transform: rotate(45deg);
}
</style><style scoped lang="scss">
$color: green;
.tree {color: $color;
}
</style>
<style scoped lang="less">
@color: #ccc;.solid {color: @color;
}
</style>
全局在scss中导入一行代码
-
vite-project/vite.config.js
css: {preprocessorOptions: {scss: {additionalData: `@import "./assets/var.scss";`//就会在所有的scss文件中加入这样一行css代码。},} }
-
所有预处理器选项还支持 additionalData 选项,可以用于为每个样式内容注入额外代码。请注意,如果注入的是实际的样式而不仅仅是变量时,那么这些样式将会在最终的打包产物中重复出现。
-
vite-project/vite.config.js
export default defineConfig({css: {preprocessorOptions: {scss: {additionalData: `@import "./assets/var.scss";`//就会在所有的scss文件中加入这样一行css代码。},}},
})
- vite-project/src/assets/var.scss
$color: green;
- vite-project/src/App.vue
<script setup>
</script>
<template><span class="tree"> 绿色 </span>
</template><style scoped lang="scss">
// 每个scss代码块中都会注入这个公共的倒入模式
.tree {color: $color;
}
</style>
项目基础路径
- 如可以用cdn项目根目录配置
export default defineConfig({base:'http://www.fulljs.cn/' // 基础路径
});
postcss自动加前缀
- 安装autoprefixer
npm install autoprefixer --save
- 配置相关的文件
- vite-project/postcss.config.js
// 和 webpack配置一模样一样
module.exports = {plugins: [require('autoprefixer')]
}
- vite-project/.browserslistrc
> 0.1%
- vite-project/src/App.vue
<script setup></script>
<template><div class="box"></div><van-button>按钮</van-button><van-field />
</template><style scoped>
.box {width: 100px;height: 100px;background: red;transform: rotate(45deg);
}
</style>
反向代理
server: {proxy: {'/api': {target: "http://jsonplaceholder.typicode.com",changeOrigin: true,rewrite:path => path.replace(/^\/api/,'')}}
}
fetch('/api/posts/').then(res=>json()).then(data => {console.log(data)
});
mock数据模拟
pnpm i mockjs vite-plugin-mock -D
纯反向代理
- vite-project/vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({server: {proxy: {'/api': {target: "http://jsonplaceholder.typicode.com",changeOrigin: true,rewrite: path => path.replace(/^\/api/, '')}}}
})
- vite-project/src/App.vue
<script setup>
// 发请求做代理
fetch('/api/posts/1').then(res => res.json()).then(data => {console.log(data)
})
</script>
<template>
</template>
mock数据
- 必须在项目根目录的mock目录下。
-
安装mock的vite插件
pnpm i mockjs vite-plugin-mock -D
-
配置vite插件。
-
vite-project/vite.config.js
import { viteMockServe } from "vite-plugin-mock"; export default defineConfig({plugins: [vue(),vueJsx(),viteMockServe({})] });
-
-
定义mock数据
-
vite-project/mock/a.js 必须在项目根目录的mock目录下。
export default [{url: '/api/get',method: 'get',response: () => {// 模拟的json格式return {code: 0,data: { name: 'jw' }};},} ]
-
-
调用方法通过开发服务器访问到mock数据。
-
vite-project/src/App.vue
<script setup> // mock数据 fetch('/api/get').then(res => res.json()).then(data => {console.log(data) }) </script> <template> </template>
-
旧项目的兼容
- legacy 遗留的产物 vite可以兼容不支持esModule规范的逻辑 (如果不支持就采用动态创建script标签的方式来引入 Systemjs)
pnpm i @vitejs/plugin-legacy -D
-
vite-project/vite.config.js
import legacy from '@vitejs/plugin-legacy' export default defineConfig({plugins: [vue(),vueJsx(),legacy() // 在构建过程中生成传统的 ES5 兼容包,以支持旧版本的浏览器], })
polyfill
- polyfill 腻子函数,补平一些内置方法。
打包不用esBuild,而是用terser
npm add -D terser
- vite-project/vite.config.js
export default defineConfig({build: {minify:'terser', // 使用terser来压缩assetsInlineLimit: 200 * 1024}, // 支持rollup 的配置
})
UI框架的动态导入
pnpm i @vitejs/plugin-legacy -D
pnpm i unplugin-vue-components -D
pnpm install vant
import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';export default defineConfig({plugins: [Components({resolvers: [VantResolver()], // vant组件解析})],
});
- vant动态导入
pnpm i @vitejs/plugin-legacy -D
pnpm i unplugin-vue-components -D
pnpm install vant
- vite-project/vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'import Components from 'unplugin-vue-components/vite';
import { VantResolver } from 'unplugin-vue-components/resolvers';export default defineConfig({// vite的插件直接放在plugis中即可plugins: [vue(),vueJsx(),Components({resolvers: [VantResolver()], // vant组件解析}),],// 组件库动态导入 element-plus vant andt-design-vue nativeUI ....
})
- vite-project/src/App.vue
<script setup></script>
<template><van-button>按钮</van-button><van-field />
</template>
Vue3新特性
- Vue3 迁移指南
组合式api
- 组合式API
- 选项式API(method、computed、watch、props、data…)。有一个缺点:一个完整的逻辑会拆分到不同的属性中。高内聚低耦合。管理的时候非常麻烦,反复横跳。
- 组合式API则将
选项式API中的这些api
全部转换成函数,将这些函数组合起来完成功能。 - 组合式api与函数编程:组合式api不是函数式编程 (组合式API是他借鉴过来了函数式编程的思路) 先抄先超。
- 组合式API则将
- 选项式API:对象里面写的东西打包后就算没用到也会被打包。
- 组合式api可以做到按需的tree-shakig (整个vue3项目编写后打包出的结果体积小), 以前的公共逻辑在vue2如何处理的 Vue.mixin (解决mixin问题) 高阶组件。
- 选项式API:options API缺点 this.x this.x this.x ,this满天飞,有时候比较难确定this内部到底有什么东西。
- 选项式API(method、computed、watch、props、data…)。有一个缺点:一个完整的逻辑会拆分到不同的属性中。高内聚低耦合。管理的时候非常麻烦,反复横跳。
Composition API
- 使用函数的方式编写vue组件。
- 组合式API (响应式API
ref()、reactive()
,生命周期钩子onMounted()、onUnmounted()
,依赖注入inject()、provide()
) - 组合式API并不是函数式编程。
组合式API与选项式API
-
如何看待Composition API 和 Options API?
- 在Vue2中采用的是OptionsAPI, 用户提供的data,props,methods,computed,watch等属性 (用户编写复杂业务逻辑会出现反复横跳问题)
- Vue2中所有的属性都是通过
this
访问,this
存在指向明确问题 - Vue2中很多未使用方法或属性依旧会被打包,并且所有全局API都在Vue对象上公开。Composition API对 tree-shaking 更加友好,代码也更容易压缩。
- 组件逻辑共享问题, Vue2 采用mixins 实现组件之间的逻辑共享; 但是会有数据来源不明确,命名冲突等问题。 Vue3采用CompositionAPI 提取公共逻辑非常方便
将同一个逻辑的相关代码收集在一起,并且可复用。
setup组件
- 单文件组件中的组合式 API 语法糖 (
Teleport组件
-
传送门, 在vue2中编写弹框组件,我们不能将弹框渲染到id="app"中。涉及到截断的问题如父元素设置了
overflow:hidden
, 怎么逃离id="app"的容器? -
Vue.extend().mount(‘…’)
-
参考react Portial这个传送门组件,指定将组件渲染到某个容器中。
- 经常用于处理弹窗组件和模态框组件。
-
vite-project/src/components/teleport.vue
<template><!-- 编写弹框组件比较合适, 但是一般场景用不到 this.$dialog() this.$toast() 服务调用--><button @click="changeDisplay">显示弹框</button><Teleport to="#root" v-if="isShow">这是一个他框组件 <button @click="changeDisplay">关闭按钮</button></Teleport>
</template><script>
export default {data() {return {isShow:false}},methods: {changeDisplay(){this.isShow = !this.isShow}}
}
</script>
Fragments组件
- Fragment(片段)Vue3中允许组件中包含多个节点。无需引入额外的DOM元素。
- vue2中如果想抽离组件 需要给组件增添一个包裹元素, vue3 则不需要这个无意义的标签了,可以直接写,根节点可以是文本节点。
emits属性
- vue2挂载原始的事件给组件 需要通过.native修饰来做处理。
- vue3 默认绑定的事件会被绑定到根元素上, 需要通过emits属性来识别,哪些是自定义的事件。
- 即通过在子组件的emits属性可将来自于父组件的订阅事件从
attrs
中移除。 - 示例
-
vite-project/src/App.vue 父组件
<template><Emit @click="handleClick"></Emit> </template><script> import Emit from "./components/emit.vue" export default {componets: {Emit},methods: {handleClick(type) {alert(type)}} } </script>
-
vite-project/src/components/emit.vue 子组件
<template><div @click="handleClick">我是一个按钮</div> </template><script> export default {emits:['click'], // 标识哪些事件是自定义的methods: {handleClick() {this.$emit('click','hello')}} } </script>
-
进阶参考
- Vue2的diff算法–updateChildren图文流程以及缺点
- Vue3 迁移指南