20230714----重返学习-DOM-diff算法-构建工具-包管理工具-Vite基本使用-Vue3新特性

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常见的操作做了优化。
              1. 常见操作:(增-向前向后添加一个、倒序正序、删除)
              2. diff算法中key的作用:用来识别dom元素的,如果一致的key和tag会认为是同一个元素,在使用的时候key不要不写,或者采用索引。
              • 给新老节点增加头尾指针-默认从头部开始比对,头指针比对成功后会向后移动,尾指针比对后会向前移动。
                • 如果比对成功后指针向后移动,继续进行比对,如果多出来的则插入到老节点中。
              • 如果头部指针无法比对成功,则从尾部进行比较。删除也可以通过这种方式进行比较操作。
              • 针对尾部移动到头部来说,如果头和头、尾和尾部都不行,比较尾部和头部,比对成功将尾部移动到头部去,并且继续。
                • 头和头、尾和尾都不行的情况下…。
              • 针对尾部移动到头部来说,和上面是一致的。
              • 如果没有走到优化策略,则会采用暴力比对的方式。将新节点的每一个拿出来和老节点去比对,如果找到了则复用,并且移动,如果找不到则直接插入到头指针的前面。每一轮都要经历头头、尾尾、头尾、尾头,最终老的多的删掉,新的多的则创建并插入。
          • 老儿子是文本,新的儿子也是文本。可以用新的文本换掉老的文本。

实际比对流程

  • 先比较是否是相同节点 (isSameVNode)。
    • 如果是相同节点,则比较属性,并复用老节点(将老的虚拟 dom 复用给新的虚拟节点 DOM)。
      1. 比较属性并进入下一步:
        • 属性不相同,用新的属性替换旧的属性。
        • 属性相同,则不更新。
      2. 比较儿子节点:
        • 老的没儿子,新的有儿子。直接插入新的儿子。
        • 老的有儿子,新的没儿子。直接删除页面节点。
        • 老的儿子是文本,新的儿子是文本,直接更新文本节点即可。
        • 老的有儿子,新的也有儿子。
          • 老的儿子是一个列表,新的儿子也是一个列表 updateChildren。
            1. 先做了优化-双端比对。对DOM常见的操作做了优化。
              • 每次都从老新儿子列表中各自拿出一个值来进行比较。
                1. 老节点列表的头部新节点列表的头部进行比较。
                  • 结果相同:老节点列表的头部指针向后移动一位,新节点列表的头部指针向后移动一位。跳出本轮次,进行下一轮次的比较。
                  • 结果不同,进入本轮次第2步。
                2. 老节点列表的尾部新节点列表的尾部进行比较。
                  • 结果相同:老节点列表的尾部指针向前移动一位,新节点列表的尾部指针向前移动一位。跳出本轮次,进行下一轮次的比较。
                  • 结果不同,进入本轮次第3步。
                3. 老节点列表的头部新节点列表的尾部进行比较。
                  • 结果相同:老节点列表的头部指针向后移动一位,新节点列表的尾部指针向前移动一位,移动节点-具体个人不太清楚。跳出本轮次,进行下一轮次的比较。
                  • 结果不同,进入本轮次第4步。
                4. 老节点列表的尾部新节点列表的头部进行比较。
                  • 结果相同:老节点列表的尾部指针向前移动一位,新节点列表的尾部指针向前后动一位,移动节点-具体个人不太清楚。跳出本轮次,进行下一轮次的比较。
                  • 结果不同,进入本轮次第5步。
                5. 采用暴力比对的方式。将新节点的每一个拿出来和老节点去比对,如果找到了则复用,并且移动,如果找不到则直接插入到头指针的前面。这一步必定可以解决,进入下一轮次。
          • vue-diff列表两两比较.jpg
    • 如果不是相同节点,则新创建一个元素。
  1. 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,会在后面新增,效果与预期不符,勾选的选项会错位。
  • key
<!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>

数组的数据劫持

  1. 为什么数据不能采用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>

响应式数据的原理

  • 具体流程:
  1. 渲染的时候会去调用render函数-每个组件都有一个watcher,当渲染的时候会将这个watcher放到全局上。
  2. 调用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()

img

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响应式数据的特点

  1. vue中的特点是组件级更新,更新是以组件为单位的。

设计模式

  • 设计模式:
  1. 工厂模式、单例设计模式、发布订阅、观察者模式、策略模式、代理模式。

作业

  1. 总结diff算法、key的作用、依赖收集的流程。

构建工具

  • webpack特点:
    • 很强大,功能丰富,生态好,很多插件,webpack启动是比较慢的,内部会将所有的模块进行打包,打包后再启动服务,热更新,随着项目的增大依旧会卡。
      • 如果项目特别大,在最终打包的时候也是很大的。
        • 有方案来解决:
          • 微前端,将一个项目拆分成多个子项目,最终再组件在一起。
            • 微前端的好处就是每个子项目是一个独立的项目技术栈可以不同,最后组合在一起。
      • webpack比较强大,可以处理各种资源。可以使用ES6Module的import、nodejs的require、多线程打包插件。
  • 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'
}
  1. 其中其中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的) 在服务启动后浏览器会根据入口文件需要发送相应请求,获取资源时对代码进行转化等操作。

快速的冷启动、实时模块转换。

image-20230711150202827

image-20230711150407049

esbuild

image-20230711150803107

  • 基于 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自动加前缀

  1. 安装autoprefixer
npm install autoprefixer --save
  1. 配置相关的文件
  • 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目录下。
  1. 安装mock的vite插件

    pnpm i mockjs vite-plugin-mock -D
    
  2. 配置vite插件。

    • vite-project/vite.config.js

      import { viteMockServe } from "vite-plugin-mock";
      export default defineConfig({plugins: [vue(),vueJsx(),viteMockServe({})]
      });
      
  3. 定义mock数据

    • vite-project/mock/a.js 必须在项目根目录的mock目录下。

      export default [{url: '/api/get',method: 'get',response: () => {// 模拟的json格式return {code: 0,data: { name: 'jw' }};},}
      ]
      
  4. 调用方法通过开发服务器访问到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组件解析})],
});
  1. 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可以做到按需的tree-shakig (整个vue3项目编写后打包出的结果体积小), 以前的公共逻辑在vue2如何处理的 Vue.mixin (解决mixin问题) 高阶组件。
    • 选项式API:options API缺点 this.x this.x this.x ,this满天飞,有时候比较难确定this内部到底有什么东西。

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 提取公共逻辑非常方便
  • image-20230616151243860

将同一个逻辑的相关代码收集在一起,并且可复用。

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>
      

进阶参考

  1. Vue2的diff算法–updateChildren图文流程以及缺点
  2. Vue3 迁移指南

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

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

相关文章

【Unity面试篇】Unity 面试题总结甄选 |Unity基础篇 | ❤️持续更新❤️

2.2 前言 关于Unity面试题相关的所有知识点&#xff1a;&#x1f431;‍&#x1f3cd;2023年Unity面试题大全&#xff0c;共十万字面试题总结【收藏一篇足够面试&#xff0c;持续更新】为了方便大家可以重点复习某个模块&#xff0c;所以将各方面的知识点进行了拆分并更新整理…

Jenkins全栈体系(二)

Jenkins 第三章 Jenkins Git Maven 自动化部署配置 十、几种构建方式 快照依赖构建/Build whenever a SNAPSHOT dependency is built 当依赖的快照被构建时执行本job 触发远程构建 (例如,使用脚本) 远程调用本job的restapi时执行本job job依赖构建/Build after other proj…

matlab学习指南(2):安装工具箱Toolbox的方法(详细图解)

&#x1f305;*&#x1f539;** φ(゜▽゜*)♪ **&#x1f539;*&#x1f305; 欢迎来到馒头侠的博客&#xff0c;该类目主要讲数学建模的知识&#xff0c;大家一起学习&#xff0c;联系最后的横幅&#xff01; 喜欢的朋友可以关注下&#xff0c;私信下次更新不迷路&#xff0…

青岛大学_王卓老师【数据结构与算法】Week05_12_队列的类型定义_学习笔记

本文是个人学习笔记&#xff0c;素材来自青岛大学王卓老师的教学视频。 一方面用于学习记录与分享&#xff0c; 另一方面是想让更多的人看到这么好的《数据结构与算法》的学习视频。 如有侵权&#xff0c;请留言作删文处理。 课程视频链接&#xff1a; 数据结构与算法基础…

Elasticsearch 倒排索引原理

看下面这个表格里的文档内容&#xff1a; 如果我这时候想要在这么多文档中查找带有 比亚迪 的我要怎么查&#xff0c;传统这个查询里面我想查这个比亚迪的话。就是先在文档1里面搜索一下有没有 比亚迪&#xff0c;没有&#xff0c;我在到文档2中查找比亚迪&#xff0c;还是没有…

多态的基本使用

这部分的内容主要是记住使用方法&#xff0c;原理在之后会讲。 多态的概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 比如说买票&#xff0c;普通人买票就是正常买&…

Low-Light Image Enhancement via Self-Reinforced Retinex Projection Model 论文阅读笔记

这是马龙博士2022年在TMM期刊发表的基于改进的retinex方法去做暗图增强&#xff08;非深度学习&#xff09;的一篇论文 文章用一张图展示了其动机&#xff0c;第一行是估计的亮度层&#xff0c;第二列是通常的retinex方法会对估计的亮度层进行RTV约束优化&#xff0c;从而产生…

OPPO手机便签怎么上传录音文件?

相信很多网友对OPPO这个手机品牌并不陌生&#xff0c;因为它凭借时尚轻薄的外观设计、流畅简约的系统、清晰的拍照摄影以及高中低不同的价位选择&#xff0c;赢得了不少年轻消费者的青睐。不过在使用OPPO手机的过程中&#xff0c;也有不少用户表示自己遇到了各种各样的问题&…

接口突然超时10宗罪。。。

前言 不知道你有没有遇到过这样的场景&#xff1a;我们提供的某个API接口&#xff0c;响应时间原本一直都很快&#xff0c;但在某个不经意的时间点&#xff0c;突然出现了接口超时。 也许你会有点懵&#xff0c;到底是为什么呢&#xff1f; 今天跟大家一起聊聊接口突然超时的…

IEEE ICME 2023论文|基于交互式注意力的语音情感识别联合网络

论文题目&#xff1a; A Joint Network Based on Interactive Attention for Speech Emotion Recognition 作者列表&#xff1a; 胡英&#xff0c;侯世静&#xff0c;杨华敏&#xff0c;黄浩&#xff0c;何亮 研究背景 语音情感识别&#xff08;Speech Emotion Recognitio…

Java正则表达式简介及Jar包

Java提供了java.util.regex包&#xff0c;用于与正则表达式进行模式匹配。 Java正则表达式与Perl编程语言非常相似&#xff0c;非常容易学习。 正则表达式定义了字符串的模式。 正则表达式可以用来搜索、编辑或处理文本。 正则表达式并不仅限于某一种语言&#xff0c;但是在…

【LeetCode热题100】打卡第34天:排序链表乘积最大的子数组

文章目录 【LeetCode热题100】打卡第34天&#xff1a;排序链表&乘积最大的子数组⛅前言 排序链表&#x1f512;题目&#x1f511;题解 乘积最大的子数组&#x1f512;题目&#x1f511;题解 【LeetCode热题100】打卡第34天&#xff1a;排序链表&乘积最大的子数组 ⛅前…