项目初始化
1.vite项目构建
构建项目 npm init vite@latest (脚手架构建npm init vue@latest 特点:配置更多)
选择框架为VUE
选择语言为TS
在目录中就会产生一个Vue3的初始化项目
2.安装依赖
在终端执行npm install
他会扫描package.json里的devDependencies内的依赖并进行安装,生成node_modules
目录详解以及插件
public存放静态资源,不会被vite编译
src--assets存放静态资源
componets存放组件
app.vue入口文件
main.ts全局配置文件
vite-env.d.ts声明文件扩充 让ts认识.vue后缀的文件
index.html使用html做入口文件,使用esm引入
package.json命令以及依赖
tsconfig.json TS的配置文件
vite.config.json vite的配置文件
1.SFC单文件组件(APP.VUE)介绍
setup的script标签只能有一个
template也只能有一个
2.npm run dev执行过程
1.先找本地的node_modules找bin里的可执行的vite
2.没有的话,找npm install -g全局包找vite
3.没有的话,找环境变量Path
4.都没有,报错
VUE3语法及指令
1.Setup
常规写法 :必须对外暴露才能用
<script>
export default {
setup() {
const n=1;
return {
n
}
}
}
</script>
结合TS的setup语法糖写法:不用暴露
<script setup lang="ts">
const n:number=1
</script>
2.模版语法{{ }}
支持数值运算,三元表达式,以及API的运算
<template>
<div>
{{ n+1 }} //2
{{ n?1:2 }} //1
{{ m.map(v=>({'数字':v})) }} // [ { "数字": 1 }, { "数字": 2 }, { "数字": 3 } ]
</div>
</template>
<script setup lang="ts">
const n:number=1
const m:number[]=[1,2,3]
</script>
3.V-指令
vue
- v-text 用来显示文本
- v-html 用来展示富文本
- v-if 用来控制元素的显示隐藏(切换真假DOM)
- v-else-if 表示 v-if 的“else if 块”。可以链式调用
- v-else v-if条件收尾语句
- v-show 用来控制元素的显示隐藏(display none block Css切换)
- v-on 简写@用来给元素添加事件
- v-bind 简写: 用来绑定元素的属性Attr
- v-model 双向绑定
- v-for 用来遍历元素
- v-on修饰符 冒泡案例
- v-once 性能优化只渲染一次
- v-memo 性能优化(小) 配合v-for控制渲染
动态事件绑定
<template>
<button @[event]="xxx">点击我</button>
</template>
<script setup lang="ts">
const event='click'
const xxx=()=>{
console.log('haha');
}
</script>
介绍虚拟DOM与AST
虚拟DOM就是通过JS来生成一个AST节点树,使用Js来描述Dom对象
虚拟DOM产生的原因:DOM自身有很多的属性,直接操作dom的话,会非常影响性能,因此就用js来描述dom
AST节点树用途很广泛:
- Babel插件ES6转ES5
- TS转JS
- JS通过v8引擎转字节码
VUE3的diff算法
场景就是v-for的key值
<template>
<div>
<div :key="item" v-for="(item) in Arr">{{ item }}</div>
</div>
</template>
<script setup lang="ts">
const Arr: Array<string> = ['A', 'B', 'C', 'D']
Arr.splice(2,0,'DDD')
</script>
主要的 diff 过程如下:
-
如果新旧 vnode 的 key 不相等,直接认定为不同节点,无需继续比较,删除旧节点,插入新节点。
-
如果新旧 vnode 的 key 相等,进一步比较以下几个方面:
- 检查是否存在静态节点,如果存在,则跳过其子节点的比较(即不进入深度遍历),因为静态节点不会发生变化。
- 检查是否设置了 PatchFlag,即编译时标记。PatchFlag 用于表示当前节点的子节点是否有变化。
- 当前节点无子节点时,直接返回。如果有子节点,依次对比子节点。
- 对比子节点时,首先根据 key 在新旧子节点列表中找到相同 key 的节点,然后递归比较这些节点。如果找不到相同 key,表示为新增节点,直接插入。如果新旧节点都存在相同 key,则递归比较这些节点。
- 对比过程会继续递归进行,直到对比完所有的子节点。
-
在 diff 过程中,会根据变化情况生成 PatchFlag,并记录下需要进行具体操作的类型,如更新、删除或插入等。
-
最后,根据生成的 PatchFlag,执行具体的 DOM 操作进行更新。
Vue 3 的 PatchFlag 算法相对于 Vue 2.x 的 Virtual DOM diff 算法,在处理较大的 vnode 树时,能够更高效地减少不必要的比较和更新操作,提升性能和渲染速度。
区别于VUE2的diff算法:
VUE2会先前序对比,然后后序对比,最后会进行前后交叉对比
Ref全家桶
- 作用: 定义一个响应式的数据
- 语法:
const xxx = ref(Value)- 创建一个包含响应式数据的引用对象(reference对象,简称ref对象)。
- 一般支持普通类型的数据,但是当传入引用类型的数据时,底层也会调用reactive
- JS中操作数据:
xxx.value - 模板中读取数据: 不需要.value,直接:
<div>{{xxx}}</div>
- 泛型:
<template>
{{ man }}
<button @click="change"></button>
</template>
<script setup lang="ts">
import {ref} from 'vue'
import type {Ref} from 'vue'
// 使用Ref规定泛型
type M={
name:string,
age:number
}
const man:Ref<M>=ref({
name:"张三",
age:18
})
const change=()=>{
man.value.name="李四"
}
</script>
<style scoped>
</style>
- isRef 判断是不是一个ref对象
import { ref, Ref,isRef } from 'vue'
let message= ref("message")
let noRef:number = 123
const changeMsg = () => {
console.log(isRef(message)); //true
console.log(isRef(noRef)); //false
}
- shallowRef 浅层次的响应式,底层不会调用toReactive(只能响应到.value这个层次)
ref是深层次的响应式,可以响应每一层的数据
并且ref不能和shallowRef混用,前者会影响后者的响应范围
<script setup lang="ts">
import { ref, shallowRef } from 'vue'
let message = shallowRef({
name: "小"
})
页面的name没有变化
const change1 = () => {
message.value.name = '大'
}
页面的name发生了变化
const change2 = () => {
message.value = {
name:'大'
}
}
</script>
- triggerRef 强制更新页面DOM
因为ref的底层就调用了triggerRef,而shallowRef却没有,因此不能混用
可以使用triggerRef 强制 shallowRef更新页面
const change = () => {
message.value.name = '大'
triggerRef(message)
}
- customRef 自定义Ref 自己加对应的逻辑 (防抖什么的)
提供 track 和 trigger接口,来重写get和set
track 收集依赖
trigger触发依赖
<template>
<div>
{{ name }}
<button @click="change">修改customRef</button>
</div>
</template>
<script setup lang='ts'>
import { ref, customRef } from 'vue'
//自定义myref
function myRef<T = any>(value: T) {
return customRef((track, trigger) => {
return {
get() {
//具体的操作
track()
return value
},
set(newVal) {
//具体的逻辑,比如写一个定时器来实现防抖
value = newVal
trigger()
}
}
})
}
//调用自定义的myRef
const name = myRef<string>('张三')
const change = () => {
name.value = '李四'
}
- 获取DOM :取代document.querySelector等一系列用法
- 打的 ref 可以用 this.$refs 获取到
<template>
<!-- 给div打个ref标记 -->
<div ref="div1"></div>
<button @click="changes">查看</button>
</template>
<script setup lang="ts">
import {ref} from 'vue'
//<HTMLDivElement>是用来指定ref的类型的
const div1=ref<HTMLDivElement>()
const changes=()=>{
//读取打了ref标记的标签里的内容
console.log(div1.value?.innerText); //?就是可能没有值,可能有
}
</script>
- 补充:
- 接收的数据可以是:基本类型、也可以是对象类型。
- 基本类型的数据:响应式依然是靠
Object.defineProperty()的get与set完成的。 - 对象类型的数据:内部 “ 求助 ” 了Vue3.0中的一个新函数——
reactive函数。
Reactive全家桶
- 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用
ref函数)
在 Vue 中,Reactive(响应式)是指当数据发生变化时,相关的界面会自动更新以反映数据的最新状态。Vue 使用了一种叫做 "Reactivity System" 的机制来实现响应式。
Reactivity System 的核心是 Vue 实例中的 data 对象或组件中的 data 选项。当你将一个属性添加到 data 对象中时,Vue 会将其转换为响应式属性。这意味着当该属性的值发生变化时,与之相关的视图会自动更新。
Vue 2.x 中使用的是 Object.defineProperty 方法来实现属性的 getter 和 setter,而 Vue 3 中则引入了 Proxy 对象来实现响应式。Proxy 是 ES6 中的一个特性,它可以拦截并劫持 JavaScript 对象的操作,使我们能够监听属性的变化并执行相应的操作。
-
语法:
const 代理对象= reactive(源对象)- 接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
-
reactive只支持引用类型 Array Object Map Set,不能绑定普通类型的数据(报错)
-
reactive不需要.value来操作值
<template>
{{ obj }}
<button @click="change">点我年龄+1</button>
</template>
<script setup lang="ts">
import { reactive } from 'vue';
const obj=reactive({name:'张三',age:18})
const change=()=>{
obj.age++
}
</script>
-
reactive定义的响应式数据是“深层次的”。
-
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
-
使用数组的reactive时,不能直接给数组整个赋值,这样会覆盖掉数组的Proxy,使数组丢失响应式
<template> <ul> <li v-for="item in list">{{item}}</li> </ul> <button @click="add">点我添加</button> </template> <script setup lang="ts"> //这样赋值页面是不会变化的因为会脱离响应式 let list = reactive<number[]>([]) const add=()=>{ setTimeout(() => { list = [1, 2, 3] console.log(list); },1000) } </script>
解决方案1 使用push+解构赋值
<script setup lang="ts">
import { reactive } from 'vue'
let list = reactive<number[]>([])
setTimeout(() => {
const arr = [1, 2, 3]
list.push(...arr)
console.log(list);
},1000)
</script>
解决方案2 包裹一层对象
原理是,把数组当作对象的一个属性值,直接修改对象的属性值是被允许的,但是上面遍历的区域就要改成list.arr
<script setup lang="ts">
type list = {
arr?:Array<number>
}
let list = reactive<list>({
arr:[]
})
setTimeout(() => {
const res = [1, 2, 3]
list.arr = res;
console.log(list.arr);
},1000)
</script>
- Readonly 拷贝一份proxy对象将其设置为只读
tips:给readonly的那个变量赋值时,会报错,但是给原数据reactive赋值却是可以的
import { reactive ,readonly} from 'vue'
const person = reactive({count:1})
const copy = readonly(person)
copy.count++ //报错
person.count++ //count+1
- shallowReactive 浅层次的响应式,底层不会调用toReactive(只能响应到.first 第一个属性值 这个层次)
reactive是深层次的响应式,可以响应每一层的数据
并且reactive不能和shallowReactive混用,前者会影响后者的响应范围
To全家桶
- toRef 把某个属性转化为响应式的
对非响应式的数据没有任何作用(数据变,视图不变)
**应用场景:**请求拿到一个对象,但是只使用其中的某个属性,并且需要是响应式的
<script setup lang="ts">
import { reactive, toRef } from 'vue'
const obj = {
foo: 1,
bar: 1
}
const state = toRef(obj, 'bar')
// bar 转化为响应式对象
</script>
- toRefs 把引用类型里的所有的属性都转化为响应式的
**底层原理: **用for循环遍历整个对象,把每个属性都调用一次toRef
使用场景: 拿到对象后的解构赋值
import { reactive, toRefs } from 'vue'
const obj = reactive({
foo: 1,
bar: 1
})
let { foo, bar } = toRefs(obj)
foo++,bar--;
console.log(foo, bar);
-
toRaw 去响应式(脱离Proxy)
const state = toRaw(obj) // 响应式对象转化为普通对象
底层原理:
const toRaw = ()=>{
data['__v_raw']
}
响应式原理
-
Vue2 使用的是 Object.defineProperty
-
vue2响应式存在的问题
- 对象只能劫持预先定义好的属性,新增的属性无法进行劫持,要调用Vue.$set (XXX)
- 数组也只是重写了7个方法,直接修改数组的值,也是无法劫持的
- 虽然可以解决数组的问题,但是性能的压力比较大
-
vue3使用的是 Proxy
- Vue3 的响应式原理依赖了 Proxy 这个核心 API,通过 Proxy 可以劫持对象的某些操作
-
VUE3响应式源码实现
- reactive和effect的实现
底层函数 : effect track trigger
浅浅谈一下Vue2到Vue3都有哪些进步
- 响应式系统的改进:
在 Vue 2 中,使用了 Object.defineProperty 来实现属性的响应式绑定,而 Vue 3 则采用了更高效和强大的 Proxy 对象来实现响应式。这使得 Vue 3 的响应式系统性能更好,并支持了更多的用例,如检测数组变化、动态添加属性等。
- Composition API:
Vue 3 引入了 Composition API,它提供了一种新的组织逻辑代码的方式。与 Vue 2 的 Options API 相比,Composition API 更加灵活、可组合和易于维护。通过 Composition API,可以将相关的逻辑代码组织成逻辑组合函数,使代码更具可读性和可复用性。
- 更好的 TypeScript 支持:
Vue 3 在设计上更注重对 TypeScript 的支持,提供了更好的类型推导和类型定义。Vue 3 的源码也经过重写,使得它更易于在 TypeScript 项目中进行开发。
- 更小的体积:
Vue 3 的运行时代码经过优化,相比 Vue 2 体积更小,加载速度更快。
- 虚拟 DOM 的改进:
Vue 3 中重新设计了虚拟 DOM 的实现,采用了更高效的算法和数据结构,提升了渲染性能。
- 脚手架工具的改进:
Vue CLI 4 是基于 Vue 2 的版本,而 Vue CLI 5 是基于 Vue 3 的版本。Vue CLI 5 提供了许多新特性和工具链的改进,使得开发和构建 Vue 项目更加便捷和高效。
总结
学习本身是一件比较枯燥的事情,Vue3相较于Vue2的难度,也提升了很多,不是我们一天两天就能学会的,而且学习的过程中需要反复操作和实践。因此,在学习Vue3时,我们要合理安排好时间,比如每天上午一到两个小时用来学习Vue3,下午用来做实验和练习。每星期可以有两个晚上用来巩固所学知识,这样才能更好地掌握所学的知识和技能。