本篇文章基于该视频: 尚硅谷教学Vue2与Vue3
使用传统Options(配置)APi,新增或者修改需求,需要在data、methods、computed等中修改
使用composition(组合)API,可以更加优雅的组织我们的代码、函数,让相关功能的代码更加有序的组织起来。
新的配置项 值为一个函数
组件中的所有数据、方法等,均要配置在setup中
setup函数有两个返回值:
①若返回一个对象,则对象中的属性、方法,在模板中均可使用。(重点)
②若返回一个渲染函数,则可以自定义渲染内容。(了解)
⚠
vue2中可读取setup中的数据,若重名,则setup优先。(切记不可混用)
不能是一个async函数,因为async函数返回值是promise,在模板中看不到return对象中的属性(后期可以返回Promise实例,但需要Suspense和异步组件配合)
<template>
<div>
<h1>{
{
msg }}</h1>
<h1>{
{
uname}}</h1>
<button @click="sayUname">alert</button>
</div>
</template>
<script>
import {
h} from 'vue'
export default {
name: 'Test',
props: {
msg: String
},
setup() {
let uname = 'lyz'
function sayUname() {
alert(`我是${
uname}`)
}
// 返回一个对象(常用)
return {
uname,sayUname}
// 返回一个函数
// return () =>h('h1', 'LYZ')
}
}
</script>
参数:
props:值为对象 包含组件外部传递过来的且组件内部声明接收了的属性(父子传值)
context:上下文对象
① attrs:值为对象 包括:组件外部传递过来但未在props配置中声明的属性 相当于this.$attrs
② slots:收到插槽内容 相当于this.$slots
③ emit:分发自定义事件的函数 相当于this.$emit
// 父组件
<template>
<Test msg="Welcome to My App" @toTest="toTestOne">
<template v-slot:qwe>
<span>qwe</span>
</template>
</Test>
</template>
<script>
import Test from './components/Test.vue'
export default {
name: 'App',
components: {
Test
},
setup() {
function toTestOne(el) {
alert(`收到参数${
el}`)
}
return {
toTestOne}
}
}
</script>
// 子组件
<template>
<div>
<button @click="toTest">test</button>
</div>
</template>
<script>
export default {
name: 'Test',
props: {
msg: String
},
emits:['toTest'],
setup(props, context) {
console.log(context.slots)
function toTest() {
console.log(context)
context.emit('toTest', 'lyz')
}
return {
toTest}
}
}
</script>
<style scoped>
</style>
Vue2中所有data都会被捕捉,加入setter、getter 带来性能开销
ref()可以选择哪些数据被捕捉 哪些无需监听
基本数据类型:get set
引用数据类型:Proxy (ref()—>reactive())
<template>
<div>
<h1>{
{
msg }}</h1>
<h1>{
{
uname}}</h1>
<h2>{
{
uage}}</h2>
<h3>{
{
job.type}}</h3>
<h3>{
{
job.salary}}</h3>
<button @click="editInfo">修改</button>
</div>
</template>
<script>
import {
ref } from 'vue'
export default {
name: 'Test',
props: {
msg: String
},
setup() {
let uname = ref('lyz')
let uage = ref('22')
let job = ref({
type: '前端工程师',
salary: '30k'
})
function editInfo() {
console.log(job);
uname.value = 'my'
uage.value = 23
job.value.type = '后端工程师',
job.value.salary = '50k'
}
return {
uname, uage, job,editInfo}
}
}
</script>
引用类型的响应式数据
let 代理对象 = reacvtive(源对象)
内部基于Proxy实现,通过代理对象操作源对象内部数据进行操作
<template>
<div>
<h1>{
{
msg }}</h1>
<h1>{
{
person.uname}}</h1>
<h2>{
{
person.age}}</h2>
<h3>{
{
person.type}}</h3>
<button @click="editInfo">修改</button>
</div>
</template>
<script>
import {
reactive } from 'vue'
export default {
name: 'Test',
props: {
msg: String
},
setup() {
let person = reactive({
uname: 'lyz',
age: 22,
type: '前端工程师'
})
function editInfo() {
person.uname = 'my'
person.age = 23
person.type = '后端工程师'
}
return {
person, editInfo}
}
}
</script>
返回一个data对象 类似于vue2
<template>
<div>
<h1>{
{
data.person.uname}}</h1>
</div>
</template>
<script>
import {
reactive } from 'vue'
export default {
name: 'Test',
props: {
msg: String
},
setup() {
let data = reactive({
person: {
uname: 'lyz'
}
})
return {
data}
}
}
</script>
<style scoped>
</style>
定义:
ref用来定义基本数据类型
reactive用来定义对象、数组类型数据
▲ref也可定义对象、数组类型数据,内部会通过reactive转为代理对象
原理:
ref通过Object。defineProperty()的get和set来实现响应式(数据劫持)
reactive通过使用Proxy来实现响应式(数据劫持),应通过Reflect操作源对象内部的数据
使用:
ref定义的数据,操作数据需要.value
,读取数据时模板中直接读取不需要的.value
reactive定义的数据,操作数据和读取数据都不需要.value
对象:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
数组:通过重写数组的一系列方法来完成拦截。(对数组的变更方法进行了包裹)
[...].splice() vue.set() this.$set() vue.delete() this.$delete()
存在问题⚠
① 新增 删除界面不会更新;
② 直接通过下标修改数组,界面不会自动更新。
let p = new Proxy(person, {
// 有人读取某个属性时调用
get(target, propName) {
console.log('get')
return target[propName]
},
// 有人 修改 或 追加 某个属性时调用
set(target, propName, value) {
console.log('set')
target[propName] = value
},
deleteProperty(target, propName) {
console.log('delete')
return delete target[propName]
}
})
通过Proxy(代理)拦截对象中任意属性的变化:读 写 添加 删除等
通过Reflect(反射) 对被代理对象(源对象)的操作
let p = new Proxy(person, {
// 有人读取某个属性时调用
get(target, propName) {
console.log('get')
return Reflect.get(target, propName)
},
// 有人 修改 或 追加 某个属性时调用
set(target, propName, value) {
console.log('set')
target[propName] = value
Reflect.set(target, propName, value)
},
deleteProperty(target, propName) {
console.log('delete')
return Reflect.deleteProperty(target, propName)
}
})
与vue2配置功能一致
写在setup中
<template>
<div>
<input v-model="person.firstName"/><br>
<input v-model="person.lastName"/><br>
<input v-model="person.fullName"/>
</div>
</template>
<script>
import {
reactive,computed} from 'vue'
export default {
name: 'Test',
props: {
msg: String
},
setup() {
let person = reactive({
firstName: 'lyz',
lastName: 'my'
})
// 简写 只读
// person.fullName = computed(() =>{
// return person.firstName + '-' + person.lastName
// })
// 完整
person.fullName = computed({
get() {
return person.firstName + '-' + person.lastName
},
set(value) {
let fullName = value.split('-')
person.firstName = fullName[0]
person.lastName = fullName[1]
}
})
return {
person}
}
}
</script>
与vue2配置功能一致
写在setup中
情况一 监视ref所定义的一个响应式数据
情况二 监视ref所定义的多个响应式数据
情况三 监视reactive所定义的一个响应式数据
▲此处无法获取正确的oldValue
强制开启了深度监听(deep无效)
情况四 监视reactive所定义的一个响应式数据的某个属性
相当于监视的基本数据类型
情况五 监视reactive所定义的一个响应式数据的某些属性
特殊情况
监视的是reactive定义的对象中的对象属性 则deep有效
此处无法获取正确的oldValue
<template>
<div>
<h1>{
{
sum}}</h1>
<button @click="sum++">sum+1</button>
<hr>
<h1>{
{
hello}}</h1>
<button @click="hello+='!'">hello+!</button>
<hr>
<h1>{
{
person.name}}</h1>
<h1>{
{
person.age}}</h1>
<h1>{
{
person.job.j1.salary}}</h1>
<button @click="person.name+='!'">姓名+!</button>
<button @click="person.age++">年龄+1</button>
<button @click="person.job.j1.salary++">工资+1000</button>
</div>
</template>
<script>
import {
watch, ref, reactive} from 'vue'
export default {
name: 'Test',
props: {
msg: String
},
setup() {
let sum = ref(0),
hello = ref('hello')
// 情况一 监视ref所定义的一个响应式数据
// immediate是否立即监视
// watch(sum, (newValue, oldValue) =>{
// console.log(newValue, oldValue)
// }, {immediate: true})
// 情况二 监视ref所定义的多个响应式数据
// watch([sum, hello], (newValue, oldValue) =>{
// console.log(newValue, oldValue)
// })
let person = reactive({
name: 'lyz',
age: 21,
job: {
j1: {
salary: 10000
}
}
})
/* 情况三 监视reactive所定义的一个响应式数据
▲此处无法获取正确的oldValue
强制开启了深度监听(deep无效)
*/
// watch(person, (newValue, oldValue) =>{
// console.log('person被监听了', newValue, oldValue)
// })
// 情况四 监视reactive所定义的一个响应式数据的某个属性
// 相当于监视的基本数据类型
// watch(() =>person.age, (newValue, oldValue) =>{
// console.log('person的age被监听了', newValue, oldValue)
// })
// 情况五 监视reactive所定义的一个响应式数据的某些属性
// watch([() =>person.age, () =>person.name], (newValue, oldValue) =>{
// console.log('person的age或name被监听了', newValue, oldValue)
// })
/* 特殊情况
监视的是reactive定义的对象中的对象属性 则deep有效
此处无法获取正确的oldValue
*/
watch(() =>person.job, (newValue, oldValue) =>{
console.log('person的job被监听了', newValue, oldValue)
}, {
deep: true})
return {
sum, hello, person}
}
}
</script>
基本数据类型:监听的是RefImpl对象
引用数据类型:监听的是Proxy对象 实际为reactive定义的数据
<template>
<div>
<h1>{
{
sum}}</h1>
<button @click="sum++">sum+1</button>
<hr>
<h1>{
{
hello}}</h1>
<button @click="hello+='!'">hello+!</button>
<hr>
<h1>{
{
person.name}}</h1>
<h1>{
{
person.age}}</h1>
<h1>{
{
person.job.j1.salary}}</h1>
<button @click="person.name+='!'">姓名+!</button>
<button @click="person.age++">年龄+1</button>
<button @click="person.job.j1.salary++">工资+1000</button>
</div>
</template>
<script>
import {
watch, ref} from 'vue'
export default {
name: 'Test',
props: {
msg: String
},
setup() {
let sum = ref(0),
hello = ref('hello'),
person = ref({
name: 'lyz',
age: 21,
job: {
j1: {
salary: 10000
}
}
})
/*
监听的sum为RefImpl对象
不能监听sum.value:sum.value为值
*/
watch(sum, (newValue, oldValue) =>{
console.log('sum值变化', newValue, oldValue)
})
/*
person是由ref定义的响应式数据
person.value是Proxy对象 为reactive所定义的数据
*/
// watch(person.value, (newValue, oldValue) =>{
// console.log('sum值变化', newValue, oldValue)
// })
// person为RefImpl对象 只能检测到地址值 浅层次
watch(person, (newValue, oldValue) =>{
console.log('person变化', newValue, oldValue)
}, {
deep: true})
return {
sum, hello, person}
}
}
</script>
watch:既要指名监听的属性。也要指名监听回调函数
watchEffect:无需指名监听那个属性,只监听回调中用哪个属性就监视哪个属性
有点类似,但是
computed:注重计算出的结果,回调函数有返回值
watchEffect:更注重过程,回调函数的而函数体
eg:报销流程、ajax请求
<template>
<div>
<h1>{
{
sum}}</h1>
<button @click="sum++">sum+1</button>
<hr>
<h1>{
{
hello}}</h1>
<button @click="hello+='!'">hello+!</button>
<hr>
<h1>{
{
person.name}}</h1>
<h1>{
{
person.age}}</h1>
<h1>{
{
person.job.j1.salary}}</h1>
<button @click="person.name+='!'">姓名+!</button>
<button @click="person.age++">年龄+1</button>
<button @click="person.job.j1.salary++">工资+1000</button>
</div>
</template>
<script>
import {
ref, reactive, watchEffect} from 'vue'
export default {
name: 'Test',
props: {
msg: String
},
setup() {
let sum = ref(0),
hello = ref('hello'),
person = reactive({
name: 'lyz',
age: 21,
job: {
j1: {
salary: 10000
}
}
})
watchEffect(() =>{
let sum_w = sum.value
let salary_w = person.job.j1.salary
console.log('watchEffect监听了', sum_w, salary_w)
})
return {
sum, hello, person}
}
}
</script>
与vue2相比,修改了最后两个钩子函数,改为beforeUnmount、unmounted
<template>
<div>
<h1>{
{
sum}}</h1>
<button @click="sum++">sum+1</button>
</div>
</template>
<script>
import {
ref} from 'vue'
export default {
name: 'Test',
setup() {
let sum = ref(0)
return {
sum}
},
// 通过配置项使用生命周期钩子函数
beforeCreate() {
console.log('-----beforeCreate-----')
},
created() {
console.log('-----created-----')
},
beforeMount() {
console.log('-----beforeMount-----')
},
mounted() {
console.log('-----mounted-----')
},
beforeUpdate() {
console.log('-----beforeUpdate-----')
},
updated() {
console.log('-----updated-----')
},
beforeUnmount(){
console.log('-----beforeUnmount-----')
},
unmounted() {
console.log('-----unmounted-----')
}
}
</script>
<template>
<div>
<h1>{
{
sum}}</h1>
<button @click="sum++">sum+1</button>
</div>
</template>
<script>
import {
ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted} from 'vue'
export default {
name: 'Test',
setup() {
let sum = ref(0)
//组合式api使用生命周期函数
onBeforeMount(()=>{
console.log('-----onBeforeMount-----')
})
onMounted(()=>{
console.log('-----onMounted-----')
})
onBeforeUpdate(()=>{
console.log('-----onBeforeUpdate-----')
})
onUpdated(()=>{
console.log('-----onUpdated-----')
})
onBeforeUnmount(()=>{
console.log('-----onBeforeUnmount-----')
})
onUnmounted(()=>{
console.log('-----onUnmounted-----')
})
return {
sum}
}
}
</script>
本质上是一个函数,把setup函数中使用的Composition API进行封装,类似于Vue2.X中的mixin
自定义hook函数的优势:复用代码,让setup中的逻辑清晰易懂
//src/hook/usePiont
import {
reactive, onBeforeMount, onBeforeUnmount} from "vue"
export default function() {
// '鼠标'位置数据
let point = reactive({
x: 0,
y: 0
})
// '鼠标'位置方法
function savePiont(e) {
point.x = e.pageX
point.y = e.pageY
console.log(e.pageX, e.pageY)
}
// '鼠标'位置相关生命周期钩子函数
onBeforeMount(() =>{
window.addEventListener('click', savePiont)
})
onBeforeUnmount(() =>{
window.removeEventListener('click', savePiont)
})
return point
}
// src/components/Test.vue
<template>
<div>
<h1>{
{
sum}}</h1>
<button @click="sum++">sum+1</button>
<hr>
<h2>X:{
{
point.x}} Y:{
{
point.y}}</h2>
</div>
</template>
<script>
import {
ref} from 'vue'
import usePoint from '../hook/usePoint.js'
export default {
name: 'Test',
setup() {
let sum = ref(0)
let point = usePoint()
return {
sum, point}
}
}
</script>
作用:创建一个ref对象 其value指向另一个对象中的某个属性
console.log(toRef(p, 'uname'))
应用:要将响应式对象中的某个属性单独提供给外部使用
扩展:toRefs于其功能一致,但可批量创建多个ref对象
console.log(toRefs(p, 'uname'))
只处理最外层属性的响应式
使用:如果有一个对象数据,结构比较深,但变化的是外层属性
<template>
<div>
<h1>{
{
point.x}}</h1>
<h1>{
{
point.z.val}}</h1>
<button @click="point.x+=1">+1</button> <!--可改变-->
<button @click="point.z.val+=1">+1</button> <!--可改变-->
<hr>
<h1>{
{
point_s.x}}</h1>
<h1>{
{
point_s.z.val}}</h1>
<button @click="point_s.x+=1">+1</button> <!--可改变-->
<button @click="point_s.z.val+=1">+1</button> <!--不可改变-->
</div>
</template>
<script>
import {
reactive, ref, shallowReactive, shallowRef} from 'vue'
export default {
name: 'Test',
setup() {
let point = reactive({
x: 100,
y: 99,
z: {
val: 100
}
})
let point_s = shallowReactive({
x: 10,
y: 9,
z: {
val: 100
}
})
return {
point, point_s}
}
}
</script>
只处理基本数据类型的响应式(ref会将引用类型的转换为reactive响应式),不进行对象的响应式
使用:如果有一个对象,后续功能不会修改对象中的属性,二是新的对象来替换
<template>
<div>
<h1>{
{
sum.x}}</h1> <!--可改变-->
<h1>{
{
sum.z.val}}</h1> <!--可改变-->
<button @click="sum.x+=1">+1</button>
<button @click="sum.z.val+=1">+1</button>
<hr>
<h1>{
{
sum_s.z.val}}</h1> <!--不可改变-->
<button @click="sum_s.z.val+=1">+1</button><!--不可改变-->
<button @click="sum_s={z: {val: 1000}}">改变</button> <!--改变对象-->
</div>
</template>
<script>
import {
reactive, ref, shallowReactive, shallowRef} from 'vue'
export default {
name: 'Test',
setup() {
let sum = ref({
x: 10,
y: 9,
z: {
val: 100
}
})
let sum_s = shallowRef({
x: 10,
y: 9,
z: {
val: 100
}
})
console.log('ref', sum)
console.log('shallowRef', sum_s)
return {
sum, sum_s}
}
}
</script>
让一个响应式数据变为只读
readonly(深只读)
shallowReadonly(浅只读):对象中嵌套的对象可修改
页面中数据不更新情况:
①不是响应式
②数据不让修改
①从跟上解决不让修改数据
②▲所用的属性不是本组件定义的 别人定义的响应式数据交给你不让改
<template>
<div>
<h2>{
{
name}}</h2>
<h2>{
{
age}}</h2>
<h2>{
{
salary.val}}K</h2>
<button @click="name+='!'">姓名+!</button> <!--只读-->
<button @click="age++">年龄+1</button> <!--只读-->
<button @click="salary.val++">工资+1</button> <!--只读-->
<hr>
<h2>{
{
rname}}</h2>
<h2>{
{
rage}}</h2>
<h2>{
{
rsalary.val}}K</h2>
<button @click="rname+='!'">姓名+!</button> <!--只读-->
<button @click="rage++">年龄+1</button> <!--只读-->
<button @click="rsalary.val++;console.log(rsalary.val)">工资+1</button> <!--可修改-->
</div>
</template>
<script>
import {
reactive, readonly, ref, toRefs, shallowReadonly} from 'vue'
export default {
name: 'Test',
setup() {
let sum = ref(99)
let person = reactive({
name: 'lyz',
age: 22,
salary: {
val: 10
}
})
person = readonly(person)
let person_r =reactive({
rname: 'my',
rage: 23,
rsalary: {
val: 30
}
})
person_r = shallowReadonly(person_r)
return {
sum, ...toRefs(person), ...toRefs(person_r)}
}
}
</script>
作用:将一个由reactive生成的响应式对象转为普通对象
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象所有的操作不会引起页面更新
<template>
<div>
<h2>{
{
name}}</h2>
<h2>{
{
age}}</h2>
<h2>{
{
salary.val}}K</h2>
<button @click="name+='!'">姓名+!</button>
<button @click="age++">年龄+1</button>
<button @click="salary.val++">工资+1</button>
<button @click="changePerson">原始数据</button>
</div>
</template>
<script>
import {
reactive, ref, toRefs, toRaw} from 'vue'
export default {
name: 'Test',
setup() {
let sum = ref(99)
let person = reactive({
name: 'lyz',
age: 22,
salary: {
val: 10
}
})
function changePerson() {
let p = toRaw(person)
console.log(p)
}
return {
sum, ...toRefs(person), changePerson}
}
}
</script>
作用:标记一个对象,使其永远不会成为响应式对象
使用场景:
① 有些值不应该被设为响应式 (复杂的第三方库)
② 当渲染具有不可改变数据源的大列表时,跳转响应式可提高性能
<template>
<div>
<h2>{
{
name}}</h2>
<h2>{
{
age}}</h2>
<h2>{
{
salary.val}}K</h2>
<h2 v-if="person.car">{
{
person.car}}</h2>
<button @click="addCar">添加car</button>
<button @click="person.car.name+='!'">修改car名称</button> <!--不可修改-->
<button @click="person.car.salary++">修改car价格</button> <!--不可修改-->
</div>
</template>
<script>
import {
reactive, ref, toRefs, toRaw, markRaw} from 'vue'
export default {
name: 'Test',
setup() {
let sum = ref(99)
let person = reactive({
name: 'lyz',
age: 22,
salary: {
val: 10
}
})
function addCar() {
let car = {
name: 'bc',
salary: 40
}
person.car = markRaw(car)
}
return {
sum, person, ...toRefs(person), addCar}
}
}
</script>
创建一个自定义ref,并对其依赖项跟踪和更新触发进行显示控制
<template>
<div>
<input v-model="val"/>
<h3>{
{
val}}</h3>
</div>
</template>
<script>
import {
customRef} from 'vue'
export default {
name: 'Test',
setup() {
function myRef(value, delay) {
let timer
return customRef((track, trigger) =>{
return {
get() {
console.log(`有人读取数据${
value}`)
track()
return value
},
set(newValue) {
console.log(`有人读取数据${
value}为${
newValue}`)
// 定时器要清除
clearTimeout(timer)
timer = setTimeout(() =>{
value = newValue
trigger()
}, delay)
}
}
})
}
let val = myRef('lyz', 1000)
return {
val}
}
}
</script>
作用:祖与后代间的通信
套路:父组件使用provide传递数据,后代选用inject接收数据
// Son.vue
<template>
<h1 class="son">子组件 {
{
car}}</h1>
</template>
<script>
import {
inject} from 'vue'
export default {
name: 'Son',
setup() {
let car = inject('car')
return {
car}
}
}
</script>
<style scoped>
.son {
height: 200px;
background: rgb(241, 198, 198);
}
</style>
// Father.vue
<template>
<div class="fa">
<h1>父组件</h1>
<Son/>
</div>
</template>
<script>
import Son from './Son.vue'
export default {
name: 'Father',
components: {
Son
},
setup() {
return {
}
}
}
</script>
<style scoped>
.fa {
height: 400px;
background: rgb(236, 95, 95);
}
</style>
// App.vue
<template>
<div class="ap">
<h1>App组件 {
{
car}}</h1>
<Test></Test>
</div>
</template>
<script>
import Test from './components/Father.vue'
import {
provide} from 'vue'
export default {
name: 'App',
components: {
Test
},
setup() {
let car = {
name: '奔驰',
price: '40w'
}
provide('car', car)
return {
car}
}
}
</script>
<style scoped>
.ap {
height: 600px;
background: rgb(240, 31, 31);
}
</style>
isRef:检查一个值是否为Ref对象
isReactive:检查一个对象是否为reactive创建的响应式代理
isReadonly:检查一个对象是否为readonly创建的只读代理
isProxy:检查一个对象是否为readonly或readonly创建的代理