setup函数
- 执行时机:在
beforeCreate
函数之前,setup函数中的this是undefined
- setup的参数
- props:值是一个对象。包含 组件外部传递过来,且组件内部
声明
接受了的属性。 - context上下文对象
- attrs:值是一个对象。包含 组件外部传递过来,但组件内部
没有声明
接受了的属性。相当于vue2中this.$attrs
- slots:收到的插槽内容。相当于vue2中
this.$slots
- emit:分发自定义事件的函数。相当于vue2中
this.$emit
- attrs:值是一个对象。包含 组件外部传递过来,但组件内部
- props:值是一个对象。包含 组件外部传递过来,且组件内部
RefImpl :引用对象,是一个对象,ref函数的返回值。
ref
- ref:定义一个响应式的数据。可以处理基本数据类型的数据,也可以处理对象类型的数据
- ref处理
基本数据类型
使用的是Object.defineProperty()
的get
和set
来实现响应式,在处理对象和数组
时使用的是es6中window的Proxy
来实现响应式。reactive函数实现了Proxy的功能。
reactive
- reactive:定义一个
对象类型
的响应式数据 - reactive定义的响应式数据是
深层次的
ref与reactive对比
- 定义数据角度
ref用来定义基本数据
类型
reactive用来定义对象或数组
ref也可以用来定义对象或数组,它内部会通过reactive
自动转为代理对象
- 原理角度
ref通过Object.defineProperty()
的get
和set
来实现响应式(数据劫持)。
reactive通过使用Proxy
来实现响应式(数据劫持)。 - 使用角度
ref定义的数据:操作数据需要.value
,模板中读取数据数据时不需要.value
reactive定义的数据:操作数据与读取数据都不需要.value
响应式原理
vue2的响应式
- 实现原理
对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
数组类型:通过重写更新数组的一系列方法来实现拦截。对数组的变更方法进行了包裹。
// 只能进行读取和修改,无法进行新增和删除属性
Object.defineProperty(data, 'count',{
get(){},
set(){}
})
- 存在问题
- 新增属性、删除属性,界面不会更新。
this.$set(this.person,'sex','男')
或Vue.set(this.person,'sex','男')
可以解决添加属性不是响应式的问题。
this.$delete(this.person,'sex')
或Vue.delete(this.person,'sex')
可以解决删除属性不是响应式的问题。 - 直接通过下标修改数组,界面不会自动更新。
this.$set(this.list,0,'学习')
或Vue.set(this.list,0,'学习')
或slice
方法可以解决该问题。(第二个参数是数组的下标)
- 新增属性、删除属性,界面不会更新。
vue3的响应式
- 实现原理
通过Proxy(代理):拦截对象中任意属性的变化,包括属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射):对被代理对象的属性进行操作。
模拟实现响应式
<script>
let person = { name: "张三", age: 10 };
const p = new Proxy(person, {
// 获取属性值
get(target, key) {
console.log(`监测到访问${key}属性`);
// return target[key];
return Reflect.get(target, key);
},
// 修改 或 新增 属性
set(target, key, value) {
console.log(`检测到 修改${key}属性`);
// target[key] = value;
Reflect.set(target, key, value);
},
// 删除属性
deleteProperty(target, key) {
console.log(`检测到 删除${key}属性`);
// return delete target[key];
return Reflect.deleteProperty(target, key);
},
});
</script>
计算属性 computed
import { computed } from 'vue'
export default{
setup(){
let person = reactive({
firstName:'',
lastName:'',
});
// 简写形式,只读 无法修改
person.fullNmae = computed(()=>{
return firstName + '-' + lastName;
});
// 完整写法 支持读和写
person.fullNmae = computed(()=>{
get(){
return person.firstName + '-' + person.lastName;
},
set(value){
const arr = value.split('-');
person.firstName = arr[0];
person.lastName = arr[1];
}
});
return { person }
}
}
监视属性 watch
- 监视reactive定义的响应式数据时,oldValue无法正确获取,强制开启了深度监视(deep配置失效)
- 监视reactive定义的响应式数据中某个属性时:deep配置有效
- 监视ref定义的对象类型响应式数据,可以配置deep,可以传递person.value
- watchEffect 回调函数中使用了谁就监视谁
import { ref , watch } from 'vue'
export default{
setup(){
let sum = ref(0);
let msg = ref('xx');
let person = reactive({
name: '',
age: 10,
job: {
j1: {
salary: 20
}
}
});
// 情况一:监视ref所定义的一个响应式数据
watch(sum,(newVal, oldVal)=>{ }, { immediate :true });// 监视多个 可以调用多次watch函数
// 情况二:监视ref所定义的多个响应式数据
watch([sum, msg],(newVal, oldVal) =>{ }); // 监视多个 可以调用多次watch函数
/*
情况三:监视reactive所定义的一个响应式数据的全部属性。
注意:此处无法正确地获取到oldVal,oldVal与newVal的属性值【一样】
注意:深度监视 deep 参数配置无效(reactive本身就支持深度响应式)
*/
watch(person,(newVal, oldVal)=>{ }, { deep :false });// deep配置无效
// 情况四:监视reactive所定义的一个响应式数据中的某个属性。传递一个函数。
watch(()=>person.name,(newVal, oldVal)=>{ }, { immediate :true });
// 情况五:监视reactive所定义的一个响应式数据中的多个属性。传递一个数组,元素是函数。
watch([ ()=>person.name, ()=>person.age ],(newVal, oldVal)=>{ }, { immediate :true });
// 情况六:监视的reactive所定义的对象中的某个属性(对象类型),所以deep配置有效
watch(()=>person.job,(newVal, oldVal)=>{ }, { deep :true });
// 情况七:监视ref所定义的对象类型响应式数据
let student = ref({
name: '',
age: 20,
});
// 监视方式一 student是RefImpl类型,student.value的类型是Proxy类型
watch(student.value, (newVal, oldVal)=>{});
// 监视方式二
watch(student, (newVal, oldVal)=>{}, {deep: true});
// watchEffect 这里监视了salary和sum
watchEffect(()=>{
const salary = person.job.salary;
const s = sum.value;
});
return { sum, msg, person};
}
}
watch:要指明监视的属性,也要指明监视的回调
watchEffect:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性
watchEffect类似computed:computed注重计算出来的值,必须要写 返回值;watchEffect注重的是过程,不必写返回值
生命周期
组合式API生命周期钩子,与Vue中钩子对应关系
beforeCreate ===> setup()
created ===> setup()
beforeMount ===> onBeforeMount
mounted ===> onMounted
beforeUpdate ===> onBeforeUpdate
updated ===> onUpdated
beforeUnmount ===> onBeforeUnmount
unmounted ===> onUnmounted
引入
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
<script>
export default{
setup(){
onBeforeMount(()=>{});
onMounted(()=>{});
onBeforeUpdate(()=>{});
onUpdated(()=>{});
onBeforeUnmount(()=>{});
onUnmounted(()=>{});
}
}
</script>
自定义hook函数
hook本质是一个函数,把setup函数中使用的组合式API进行了封装。类似与Vue2中的mixin。
在src/hooks/xxx.js
创建一个JS文件,导出一个函数
import { onBeforeMount, onMounted, reactive } from 'vue'
export default function() {
let point = reactive({
x: 0,
y: 0
});
function setPoint(event) {
point.x = event.pageX;
point.y = event.pageY;
}
onMounted(()=>{
window.addEventListener('click', setPoint);
})
onBeforeMount(()=>{
window.removeEventListener('click', setPoint);
})
return point;
}
在组件中使用hook
<script>
import { usePoint } from '../hooks/usePoint'
export default {
setup() {
const point = usePoint();
return { point }
}
}
</script>
toRef 和 toRefs
- 作用:创建一个ref对象,其value值指向另一个对象的某个属性。
- 语法:const name = roRef(person,'name')
- 应用:要讲响应式对象的某个属性单独提供给外部使用时。
- 扩展:toRefs 与 toRef功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
<scrip>
setup(){
let person = reactive({
name: 'John Doe',
job:{
jQuery:{
salary: 100000,
},
}
})
// 一次性处理多个属性
return {
person,
...toRefs(person)
};
// 每次处理一个
return {
person,
name:toRef(person,'name'),
salary:toRef(person.job.jQuery,'salary')
};
}
</script>
shallowReactive 和 shallowRef
shallowReactive
:浅层次的响应式,只处理对象最外层的响应式。其他层次的数据就不是响应式了
shallowRef
:只处理基本数据类型的响应式,不处理对象的响应式。shallowRef与ref传递的是基本数据类型,作用一样,如果传递的是对象类型,shallowRef不会把对象处理成响应式,ref则会将对象处理成响应式
。
readonly 和 shallowReadonly
- readonly:让一个响应式数据(ref、reactive)变为只读(深只读)
person = readonly(person)
person的数据无法修改 - shallowReadonly:让一个响应式数据变为只读(浅只读)
person = shallowReadonly(person)
person的最外层数据无法修改,其他的数据可以修改
toRaw 和 markRaw
- toRaw:将一个由reactive生成的响应式对象转为普通对象(不具备响应式)
- markRaw:标记一个对象,使其永远不会再成为响应式对象
customRef
<script>
setup(){
function myRef(value,delay){
let timer;
return customRef((track, trigger) => {
return {
get() {
track()// 追踪 数据变化
return value
},
set(newValue) {
clearTimeout(timer)
timer = setTimeout(() => {
value = newValue
trigger()// 触发数据变化
},delay)// 延时触发
}
}
})
}
let key = myRef('hello');
return { key };
}
</script>
provide 与 inject
组件间的通信方式,实现祖孙间的通信
。父组件有一个provide选项来提供数据,后代组件有一个inject选项来开始使用这些数据。
- 祖组件
setup(){
let car = reactive({name:'ben', price:'40w'});
provide('car',car)
}
- 后代组件
setup(){
const car = inject('car');
return { car }
}
响应式数据的判断
-
isRef
:检查一个值是否为一个ref对象 -
isReactive
:检查一个对象是否是由reactive创建的响应式代理 -
isReadonly
:检查一个对象是否是由readonly创建的只读代理 -
isProxy
:检查一个对象是否是由reactive或者readonly方法创建的代理
组合式api与选项式api
新组件
Fragment
- 在vue2中:组件必须有一个根标签
- 在vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素值
- 好处:减少标签层级,减小内存占用
-
Teleport
Teleport是一种能够将我们的组件html结构移动到指定位置的技术
<teleport to="body">
<h3> title </h3>
</teleport>
Suspense
- 等等异步组件时渲染一些额外内容,让应用有更好的用户体验
- Suspense是用插槽实现的
- 使用方法
异步引入组件
import { defineAsyncComponent } from 'vue'
const Child = defineAsyncComponent(()=> import('./component/Child.vue'))
使用Suspense 包裹组件,并配置好 default 与 fallback
<template>
<div class="app">
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中...</h3>
</template>
</Suspense>
</div>
</template>
其他API的变化
全局API的转移
Vue.config.xxx -> app.config
Vue.config.productionTip -> 移除
Vue.component -> app.component
Vue.directive -> app.directive
Vue.mixin -> app.mixin
Vue.use -> app.use
Vue.prototype -> app.config.globalProperties
data选项应该始终被声明为一个函数
过渡类名的更改
.v-enter -> .v-enter-from
.v-leave -> .v-leave-from
移除keyCode作为v-on的修饰符,同时 不再支持config.keyCodes
移除v-on.native修饰符
v-on:close=""
v-on:click=""
export default {
emits: ['close']// 声明了close事件,是一个自定义事件。没有声明click事件,默认为原生事件
}
- 移除过滤器
建议使用计算属性或方法调用去实现