1. 程序的主入口文件,是main.ts
引入createApp函数,创建对应的应用,产生应用的实例对象
import { createApp } from 'vue'
引入App组件(所有组件的父级组件)
import App from './App.vue'
创建App应用返回对应的实例对象,调用mount方法进行挂载
createApp(App).mount('#app')
2. Vue2中的template模板中,必须要有一对根标签,vue3的template模板中可以没有根标签
Vue2的方式实现
data() {
return {
// 属性
count:0
}
},
methods: {
updateCount() {
// 方法
this.count++
}
}
Vue3的方式实现
// setup是组合API的入口函数
setup() {
// let count = 0// 变量// 此时的数据并不是响应式数据,(响应式数据:数据变化,页面跟着渲染变化),所以方法执行,但是数据不变
const count = ref(0)//报错原因,没有引入ref => import { defineComponent, ref } from 'vue';ref是一个函数,作用是定义一个响应式数据,此时的count类型是ref
// ref对象中有一个value属性,如果需要对属性进行操作,使用该ref对象调用value属性的方式进行数据操作
// 但是在模板中,不需要.value属性的写法
function updateCount() {
// count++// 报错原因:count是一个ref的对象,对象是不能进行++操作的
count.value++
}
// 返回的是一个对象
return {
// 属性
count,
// 方法
updateCount
}
}
3. defineComponent函数,目的是定义一个组件,内部可以传入一个配置对象
4. setup
新的option, 所有的组合API函数都在此使用, 只在初始化时执行一次
函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
setup是组合api中第一个要使用的函数
setup() {
const 属性名 = ref(属性值)
function 方法名() {}
return {
属性名,
方法名
}
}
setup执行的时机
在beforeCreate之前执行(一次), 此时组件对象还没有创建,所以组件实例对象this根本就不能用
this是undefined, 不能通过this来访问data/computed/methods / props中的相关内容
其实所有的composition API相关回调函数中也都不可以
setup的返回值
一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法
setup中的对象内部的属性和data函数中的return对象的属性都可以在模板中使用
setup中的对象内部的属性和data函数中的return对象的属性会合并成为组件对象的属性
setup中的对象内部的方法和methods中的方法会合并成为组件对象的方法
返回对象中的属性会与data函数返回对象的属性合并成为组件对象的属性
返回对象中的方法会与methods中的方法合并成功组件对象的方法
如果有重名, setup优先
注意:
一般不要混合使用: methods中可以访问setup提供的属性和方法, 但在setup方法中不能访问data和methods
setup执行的时候,this还无法使用,根本找不到methods和data中的内容
setup不能是一个async函数: 因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性数据
setup的参数
setup(props, context) / setup(props, {attrs, slots, emit})
props: 包含props配置声明且传入了的所有属性的对象,是一个对象,里面有父组件向子组件传递的数据,并且是在子组件中使用props接收到的所有属性,获取当前组件标签上的属性,但是该属性是在props中没有声明接收的所有对象
attrs: 包含没有在props配置中声明的属性的对象, 相当于 this.$attrs
slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
emit: 用来分发自定义事件的函数, 相当于 this.$emit
可以在父组件下的子组件标签上绑定自定义事件@自定义事件名="自定义事件名",然后注册该事件,并接收子组件传递过来的参数,通过子组件内部的setup的参数context.emit('自定义事件名','传递数据')
5. ref
引入
import { ref } from 'vue'
作用: 定义一个数据的响应式
语法:
const xxx = ref(initValue):
创建一个包含响应式数据的引用(reference)对象
js中操作数据: xxx.value
模板中操作数据: 不需要.value
一般用来定义一个基本类型的响应式数据
ref另一个作用:
利用ref函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
<input type="text" ref="inputRef">
setup() {
// 默认是空的,页面加载完毕,说明组件已经存在,获取文本框元素
const inputRef = ref<HTMLElement | null>(null)
onMounted(() => {
inputRef.value && inputRef.value.focus()//自动获取焦点
})
return {
inputRef
}
}
6. reactive
引入:
import { reactive } from 'vue';
作用: 定义多个数据的响应式
接收一个普通对象然后返回该普通对象的响应式代理器对象
const proxy = reactive(obj):
响应式转换是“深层的”:会影响对象内部所有嵌套的属性
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
setup() {
// 把复杂数据变成响应式数据
const 复杂数据属性名 = 复杂数据
const 属性名 = reactive(复杂数据属性名)
// console.log(属性名)返回的是一个Proxy的代理对象,被代理的目标对象就是reactive中传入的复杂数据,属性名是代理对象,复杂数据是目标对象,更改数据要用代理对象
const 方法名 = () => {对数据进行操作}
return {
属性名,
方法名
}
}
如果操作代理对象,目标对象中的数据也会随之变化,如果想要在操作数据的时候,界面也要跟着重新渲染,那么也是操作代理对象
7. reactive与ref-细节
是Vue3的 composition API中2个最重要的响应式API(ref,reactive)
ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)
如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象,ref中如果放入的是复杂数据类型,那么会经过reactive的处理形成一个proxy类型的对象
ref内部: 通过给value属性添加getter/setter来实现对数据的劫持
reactive内部: 通过使用Proxy来实现对对象内部所有数据的劫持, 并通过Reflect操作对象内部数据
ref的数据操作: 在js中要.value, 在模板中不需要(内部解析模板时会自动添加.value)
8. vue2和vue3响应式对比
vue2的响应式
核心:
对象: 通过defineProperty对对象的已有属性值的读取和修改进行劫持(监视/拦截)
数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
问题
对象直接新添加的属性或删除已有属性, 界面不会自动更新
直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {}
Vue3的响应式
核心:
通过Proxy(代理): 拦截对data任意属性的任意(13种)操作, 包括属性值的读写, 属性的添加, 属性的删除等...
通过 Reflect(反射): 动态对被代理对象的相应属性进行特定的操作
文档:
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect
// 目标对象
const user = {
name:"佐助",
age:20,
wife:{
name:"小樱",
age:19
}
}
// 把目标对象变成代理对象
// 参数1:user ===> target目标对象
// 参数2:handler ===> 处理器对象,用来监视数据及数据的操作
const proxyUser = new Proxy(user,{
// 获取
get(target,prop) {
console.log('get方法调用了')
return Reflect.get(target,prop)
},
// 修改以及增加
set(target,prop,val) {
console.log('set方法调用了')
return Reflect.set(target,prop,val)
},
// 删除
deleteProperty(target,prop) {
console.log('delete方法调用了')
return Reflect.deleteProperty(target,prop)
}
})
// 通过代理对象获取目标对象中的某个属性值
console.log(proxyUser.name)
// 通过代理对象更新目标对象中的某个属性值
proxyUser.name = '鸣人'
console.log(user)
// 通过代理对象向目标对象中添加一个新的属性值
proxyUser.gender = '男'
console.log(user)
// 通过代理对象删除目标对象中的某个属性值
delete proxyUser.age
console.log(user)
9. 计算属性与监视
computed函数:
与computed配置功能一致
只有getter
有getter和setter
计算函数的属性中如果只传入一个回调函数,表示的是get
const 属性名 = computed(() => {return 计算操作})
//计算函数的属性中如果传入两个回调函数,表示的是get和set
const 属性名 = computed({
get(){},
set(val){}
})
watch函数
可以监听多个数据
需要传入监听的数据源
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
immediate默认执行一次
deep深度监听
watch(要监听的属性/对象,(val) => {
//操作
},{immediate:true,deep:true})
//当我们使用watch监听非响应式数据的时候,代码需要改一下
watch([() => 非响应式数据,() => 非响应式数据],() => {
//操作
})
watchEffect函数
自动收集数据源作为依赖
不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
默认初始时就会执行第一次, 从而可以收集需要监视的数据
监视数据发生变化时回调
watchEffect不需要配置immediate,本身就会进行监视(默认执行一次)
watchEffect(() => {
//操作
})
10. 生命周期
vue2.0
beforeCreate() { console.log('vue2中的beforeCreate') },
created() { console.log('vue2中的created') },
beforeMount() { console.log('vue2中的beforeMount') },
mounted() { console.log('vue2中的mounted') },
beforeUpdate() { console.log('vue2中的beforeUpdate') },
updated() { console.log('vue2中的updated') },
beforeUnmount() { console.log('vue2中的beforeUnmount') },
unmounted() { console.log('vue2中的unmounted') },
vue3.0
用setup替代了beforeCreate和created,且vue3中的永远比vue2中的要执行的快
setup() {
console.log('vue3中的setup')
onBeforeMount(() => { console.log('vue3中的onBeforeMount') })
onMounted(() => { console.log('vue3中的onMounted') })
onBeforeUpdate(() => { console.log('vue3中的onBeforeUpdate') })
onUpdated(() => { console.log('vue3中的onUpdated') })
onBeforeUnmount(() => { console.log('vue3中的onBeforeUnmount') })
onUnmounted(() => { console.log('vue3中的onUnmounted') })
return {}
}
11. 自定义hook函数
使用Vue3的组合API封装的可复用的功能函数
自定义hook的作用类似于vue2中的mixin技术
自定义Hook的优势: 很清楚复用功能代码的来源, 更清楚易懂
简单来说就是你要将代码封装成一个js文件,提高复用性
12. toRefs
toRefs可以把reactive包裹的数据变成普通的对象包裹的ref对象
把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
问题: reactive 对象取出的所有属性值都是非响应式的
解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
13. shallowReactive与shallowRef
shallowReactive : 只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef: 只处理了value的响应式, 不进行对象的reactive处理
什么时候用浅响应式呢?
一般情况下使用ref和reactive即可
如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
reactive,深度劫持(深监视) ===> 深度响应式
shallowReactive,浅度劫持(浅监视) ===> 浅度响应式
ref,深度劫持(深监视) ===> 深度响应式,做了reactive的处理
shallowRef,浅度劫持(浅监视) ===> 浅度响应式,不进行reactive的处理
14. readonly和shallowReadonly
readonly:
深度只读数据
获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
只读代理是深层的:访问的任何嵌套 property 也是只读的。
shallowReadonly
浅只读数据
创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
应用场景:
在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
只读数据,深度只读
const state2 = readonly(state)
只读数据,浅度只读
const state3 = shallowReadonly(state)
15. toRaw和markRaw
toRaw
返回由 reactive 或 readonly 方法转换成响应式代理的普通对象。
这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
markRaw
标记一个对象,使其永远不会转换为代理。返回对象本身
应用场景:
有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
把代理对象变成普通对象,数据变化,界面不变化
const user = toRaw(state)
markRaw标记的对象数据,从此以后都不能再成为代理对象了
state.likes = markRaw(likes)//likes不能再被修改
16. toRef的特点及使用
为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
应用: 当要将 某个prop 的 ref 传递给复合函数时,toRef 很有用
当你的子组件接收数据的时候,接收的是值,如果子组件需要这个值的类型为Ref的话,可以使用toRef将该值转换为Ref类型的数据
17. customRef的使用
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
书写防抖
const keyword = useDebouncedRef('abc',500)
// 自定义防抖函数
// value传入的数据,类型不确定,所以使用泛型,delay防抖间隔事件,默认200
function useDebouncedRef<T>(value:T,delay=200) {
// 准备存储定时器的变量
let timeoutId:number
return customRef((track,trigger) => {
return {
// 获取数据
get() {
// 告诉value追踪数据
track()
return value
},
// 设置数据
set(newValue:T) {
// 清理定时器
clearTimeout(timeoutId)
// 开启定时器
timeoutId = setTimeout(() => {
value = newValue
// 告诉vue更新界面
trigger()
},delay)
}
}
})
}
18. provide和inject
provide和inject提供依赖注入,功能类似 2.x 的provide/inject
实现跨层级组件(祖孙)间通信
提供数据provide('自定义名称',值)
setup() {
// 响应式数据
const color = ref('red')
// 提供数据
provide('color',color)
return {
color
}
}
接收数据const 属性名 = inject('自定义名称')
setup() {
// 接收数据
const color = inject('color')
return {
color
}
}
19. 响应式数据的判断方法
isRef: 检查一个值是否为一个 ref 对象
isReactive: 检查一个对象是否是由 reactive 创建的响应式代理
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理
isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理
console.log(isRef(ref({})))
console.log(isReactive(reactive({})))
console.log(isReadonly(readonly({})))
console.log(isProxy(reactive({})))
console.log(isProxy(readonly({})))
20. Fragment(片断)
在Vue2中: 组件必须有一个根标签
在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
好处: 减少标签层级, 减小内存占用
21. Teleport(瞬移)
Teleport 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签(很可能是body)下插入显示ModalButton.vue
在子组件内,使用Teleport标签包裹一段代码,在Teleport标签上使用to属性,能将包裹的这段代码书写到你所to的地方
例如:<Teleport to="body"></Teleport>,包裹的代码将会显示在body内部