结构分析
- main.js
- 引入的不再是Vue构造函数了,引入的是一个名为createApp的工厂函数
- 创建应用实例对象——app(类似于之前的Vue2中的vm,但是比vm更‘轻’)
- template标签最外层可以不用加根标签
常用Composition API
setup
- Vue3中的一个新的配置项,值是一个函数,组件中所用到的数据,方法等,都要配置在setup中
- setup函数的两种返回值: 1.若返回一个对象,则对象中的属性,方法,在模板中均可直接使用
<template>
<div>姓名{{ name }}</div>
<div>年龄{{ age }}</div>
<button @click="sayHello">打招呼</button>
</template>
<script>
export default {
name: "App",
//此处暂时不考虑响应式
setup() {
let name = "genji";
let age = 19;
function sayHello() {
console.log(`我叫${name},年龄${age}`);
}
return {
name,
age,
sayHello,
};
},
};
</script>
2.若返回一个渲染函数:则可以自定义渲染内容
import { h } from "vue"; //需要手动引入渲染函数
...
return () => h("h1", "守望先锋");
注意:setup不能是一个async函数,因为加了async后,返回值不再是return的对象,而是promise
ref函数
定义一个响应式的数据
<template>
<div>姓名{{ name }}</div>
<div>年龄{{ age }}</div>
<button @click="change">修改人名</button>
<div>工作{{ job.type }}</div>
<div>薪水{{ job.salary }}</div>
</template>
<script>
import { ref } from "vue";
export default {
name: "App",
setup() {
//将数据生成引用对象
const name = ref("genji");
const age = ref(19);
const job = ref({
type: "前端",
salary: "20k",
});
function change() {
name.value = "Mcree";
job.value.type = "后端";
}
return {
name,
age,
change,
job,
};
},
};
</script>
reactive函数
定义一个对象类型的响应式数据(基本类型不要用它,用ref函数) ,接收一个对象或数组,返回一个代理对象(Proxy的实例对象),内部基于Proxy实现,通过代理对象操作源对象内部数据进行操作
<script>
import { ref, reactive } from "vue";
export default {
name: "App",
setup() {
//将数据生成引用对象
const name = ref("genji");
const age = ref(19);
const job = reactive({
type: "前端",
salary: "20k",
});
function change() {
name.value = "Mcree";
job.type = "后端";
}
return {
name,
age,
change,
job,
};
},
};
</script>
Vue3实现响应式原理
- 通过Proxy代理,拦截对象中任意属性的变化,包括:属性值的读写,属性的添加,属性的删除等
- 通过Reflect反射,对被代理对象的属性进行操作
const person = {name:'genji',age:18}
//模拟Vue3中实现响应式
const p = new Proxy(person,{
get(target,propName){
console.log('有人读取了某个属性');
return Reflect.get(target,propName)
},
set(target,propName,value){
console.log('有人修改或者新增了某个属性');
return Reflect.set(target,propName,value)
},
deleteProperty(target,propName){
console.log('有人删除了某个属性');
return Reflect.deleteProperty(target,propName)
}
})
reactive和ref对比
- 定义数据角度
- ref用来定义基本类型数据
- reactive用来定义对象或数组类型数据
- ref也可以用来定义对象或数组类型数据,它内部会自动通过reactive转为代理对象
- 原理角度
- ref仍然是通过Object.defineProperty()的get与set来实现响应式(数据劫持)
- reactive通过Proxy来实现响应式,并通过Reflect操作源对象内部的数据
- 从使用角度对比
- ref定义的数据,操作数据需要用.value,读取数据时模板中直接读取,不需要.value
- reactive定义的数据,操作数据与读取数据,均不需要.value
setup的两个注意点
-
setup执行的时机 在beforeCreate之前执行一次,this是undefined
-
setup的参数
- props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性
- context:上下文对象
- attrs:值为对象,包含:组件外部传递过来,但没有在props中配置声明的属性,相当于 this.$attrs
- slots:收到的插槽内容,相当于this.$slots ,如果想用具名插槽,要用v-slot,不要直接slot:' '
- emit:分发自定义事件的函数,相当于this.$emit
computed计算属性
- 与Vue2中 computed配置功能一致
<template>
第一个名字:<input v-model="person.firstName" />
<br />
第二个名字:<input type="text" v-model="person.lastName" />
<br />
<span>全名:{{ person.fullName }}</span>
全名: <input type="text" v-model="person.fullName" />
</template>
<script>
import { reactive, computed } from "vue";
export default {
name: "Demo",
setup() {
let person = reactive({
firstName: "genji",
lastName: "源氏",
});
//简略写法
person.fullName = computed(() => {
return `${person.firstName}-${person.lastName}`;
});
//完整写法
person.fullName = computed({
get() {
return `${person.firstName}-${person.lastName}`;
},
set(value) {
const arr = value.split("-");
person.firstName = arr[0];
person.lastName = arr[1];
},
});
return {
person,
};
},
};
</script>
watch
- 监视简单变量时,可以获得oldVal,监视复杂变量时,无法获取oldVal
<template>
<div>总数为:{{ sum }}</div>
<button @click="sum++">加</button>
<div>{{ msg }}</div>
<button @click="msg += '!'">加!</button>
<div>{{ person.name }}</div>
<button @click="person.name += '~'">加~</button>
<div>{{ person.age }}</div>
<button @click="person.age++">加1</button>
</template>
<script>
import { ref, watch, reactive } from "vue";
export default {
name: "Demo",
setup() {
let sum = ref(0);
let msg = ref("守望先锋");
let person = reactive({
name: "genji",
age: 18,
job: {
salary: 20,
},
});
//情况一:监视ref定义的一个响应式数据
watch(sum, (newVal, oldVal) => console.log("sum变化了", newVal, oldVal), { immediate: true });
//情况二:监听多个ref定义的响应式数据,可以写多个watch,也可以写在一个watch里,newVal和oldVal为数组
watch([sum, msg], (newVal, oldVal) => console.log("sum或msg变化了", newVal, oldVal));
/**
* 情况三:监视reactive所定义的一个响应式数据的全部属性
* 1、无法获取正确的oldVal
* 2、强制开启了深度监视(deep无效)
*/
watch(person, (newVal, oldVal) => console.log("person变化了", newVal, oldVal));
//情况四:监视reactive所定义的一个响应式数据中的某个属性,需要用函数返回需要监听的属性
watch(
() => person.name,
(newVal, oldVal) => console.log("person的name变化了", newVal, oldVal)
);
//情况五:监视reactive所定义的一个响应式数据中的多个属性
watch([() => person.name, () => person.age], (newVal, oldVal) => console.log("person的name或者age变化了", newVal, oldVal));
//特殊情况,监视reactive所定义的响应式数据中的一个深层次属性,即一个对象属性,需要配置deep
watch(
() => person.job,
(newVal, oldVal) => console.log("person的job变化了", newVal, oldVal),
{ deep: true }
);
return {
sum,
msg,
person,
};
},
};
</script>
watchEffect函数
- 不用指明监视哪个属性,监视的回调中用到哪个属性,就监视哪个属性
- 有点像computed
- 但computed更注重计算出来的值,所以必须有返回值
- watchEffect更注重过程,不用写返回值
watchEffect(() => {
const x = sum.value; //回调中用到了sum,即监视sum属性
console.log(123);
});
Vue3生命周期
- Vue3中可以继续使用2中的生命周期钩子,但是最后两个被更名:
- beforeDestroy --> beforeUnmount 2.destroyed --> unmounted
- Vue3也提供了组合式API形式的生命周期钩子,除了beforeCreate,created都可以写在setup中
...
import {onBeforeMount,onMounted,...} from 'Vue'
export default {
name:'',
setup(){
onBeforeMount(()=>console.log(onBeforeMount))
onMounted(()=>console.log(onBeforeMount))
}
}
自定义hook
- 本质是一个函数,把setup函数中使用的composition API进行了封装,类似于mixin 创建/src/hooks文件夹,以use开头,简历自定义hook js文件,
//src/hooks/usePoint.js
import {reactive,...} from 'vue'
function usePoint(){
...
相关数据,
相关方法
相关生命周期钩子
return{...}
}
export default usePoint
在需要用的文件中引入
import usePoint from '...'
...
setup(){
const point = usePoint()
}
toRef,toRefs
- 创建一个ref对象,其value值指向另一个对象中的某个属性
- toRefs和toRef功能一致,但可以批量创建多个ref对象
<template>
<div>{{ name }}</div>
<button @click="name += '~'">加~</button>
<div>{{ age }}</div>
<button @click="age++">加1</button>
<div>{{ salary }}</div>
</template>
<script>
import { reactive, toRef } from "vue";
export default {
name: "Demo",
setup() {
let person = reactive({
name: "genji",
age: 18,
job: {
salary: 20,
},
});
return {
name: toRef(person, "name"),
age: toRef(person, "age"),
salary: toRef(person.job, "salary"),
};
},
};
</script>
- toRefs
<template>
<div>{{ name }}</div>
<button @click="name += '~'">加~</button>
<div>{{ age }}</div>
<button @click="age++">加1</button>
<div>{{ job.salary }}</div>
</template>
...
return {
// name: toRef(person, "name"),
// age: toRef(person, "age"),
// salary: toRef(person.job, "salary"),
...toRefs(person),
};
shallowReactive 和 shallowRef
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)
- shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理
readonly 和 shallowReadonly
- readonly:让一个响应式数据变为只读的(深只读)
- shallowReadonly:浅只读 应用场景:不希望数据被修改时,例:从别处引用了响应式数据
...
let person = reactive({
name: "genji",
age: 18,
job: {
salary: 20,
},
});
person = readonly(person);
toRaw 和 markRaw
toRaw:
- 将一个reactivce生成的响应式对象转为普通对象
- 场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
markRaw:
- 标记一个对象,使其永远会不会再成为响应式对象
- 场景:有些值不应被设置为星影视的,滴入复杂的第三方库等,当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
<script>
import { reactive, toRaw, markRaw, toRefs } from "vue";
export default {
name: "Demo",
setup() {
let person = reactive({
name: "genji",
age: 18,
job: {
salary: 20,
},
});
function showRawPerson(){
const p = toRaw(person)
console.log(p);
}
function addCar(){
const car = {name:'benz',price:40}
person.car = markRaw(car)
}
return {
...toRefs(person),
showRawPerson,
addCar
};
},
};
</script>
customRef
- 创建一个自定义的ref,并对其依赖想跟踪和更新触发进行显示控制
<script>
import { ref, customRef } from "vue";
export default {
name: "Demo",
setup() {
function myRef(value) {
let timer;
return customRef((track, trigger) => {
return {
get() {
track(); //通知Vue追踪value(返回值)的变化
return value;
},
set(newValue) {
clearTimeout(timer);
timer = setTimeout(() => {
value = newValue;
trigger(); //通知Vue重新解析模板
}, 500);
},
};
});
}
// let text = ref("genji"); //使用Vue提供的ref
let text = myRef("genji"); //使用自定的ref
return { text };
},
};
</script>
provide 和 inject
- 实现祖孙组件间通信
- 父组件通过provide来提供数据,后代组件通过inject来使用数据
//祖组件
<template>
<div class="app">
<div>{{name}}</div>
<div>{{price}}</div>
<Demo />
</div>
</template>
<script>
import { reactive, toRefs } from '@vue/reactivity';
import Demo from "./components/Demo.vue";
import { provide } from '@vue/runtime-core';
export default {
name: "App",
components: { Demo },
setup(){
let car = reactive({
name:'benz',
price:40
})
provide('car',car) //传递数据给后代组件
return {...toRefs(car)}
}
};
</script>
<style>
.app {
background-color: gray;
padding: 10px;
}
</style>
//后代组件
<template>
<div class="child">child
<div>{{name}}</div>
<div>{{price}}</div>
</div>
</template>
<script>
import { inject, toRefs } from '@vue/runtime-core';
export default {
name: "child",
setup(){
let car = inject('car') //接收祖组件传递的数据
return{...toRefs(car)}
}
};
</script>
<style>
.child {
background-color: skyblue;
padding: 10px;
}
</style>
响应式数据的判断
- isRef:检查一个值是否为一个ref对象
- isReactive:检查一个对象是否是由reactive创建的响应式代理
- isReadonly:检查一个对象是否是由readonly创建的只读代理
- isProxy:检查一个对象是否是由reactive或者readonly方法创建的代理
Fragment
Vue3中,组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
减少标签层级,减少内存占用
Teleport
- 将组件html结构移动到指定位置
<teleport to='移动位置'> //可以直接html,body;或者标签选择器#id等
....
</teleport>
Suspense
- 等待异步组件时渲染一些额外内容,让应有有更好的用户体验
// 父组件
<template>
<div class="app">
<Suspense>
<template v-slot:default> //default插槽包裹要展示的组件
<Demo />
</template>
<template v-slot:fallback> //fallback插槽包裹异步组件未渲染时展示的内容
<div>加载中</div>
</template>
</Suspense>
</div>
</template>
<script>
import {defineAsyncComponent} from 'vue'
const Demo = defineAsyncComponent(()=>import('./components/Demo.vue'))
// import Demo from "./components/Demo.vue";
export default {
name: "App",
components: { Demo },
};
</script>
<style>
.app {
background-color: gray;
padding: 10px;
}
</style>
//子组件 子组件异步引入,且使用了suspense的时候,setup可以返回promise
<template>
<div class="demo">demo
</div>
</template>
<script>
import { ref } from '@vue/reactivity';
export default {
name: "demo",
setup(){
let sum = ref(0)
return new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve({sum})
},2000)
})
}
// async setup(){
// let sum = ref(0)
// let p = new Promise((resolve,reject)=>{
// setTimeout(()=>{
// resolve({sum})
// },2000)
// })
// return await p
// }
};
</script>
<style>
.demo {
background-color: pink;
padding: 10px;
}
</style>
全局API转移
- Vue.config.xxx ———— app.config.xxx
- 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的修饰符(@keyup.13),同时也不再支持config.keyCodes
- 移除v-on.native
//父组件
<Child @close='handleClose' @click='handleClick' />
//子组件中声明自定义事件
export default{
emits:['close'] // 生命过的为自定义事件,未声明的(click)为原生事件
}
- 移除过滤器 filter