目录
1.setup
为了开始使用Composition API,我们需要有一个可以实际使用它(编写代码)的地方;在Vue组件中,这个位置就是 setup 函数;setup其实就是组件的另外一个选项:只不过这个选项强大到我们可以用它来替代之前所编写的大部分其他选项;比如methods、computed、watch、data、生命周期等等;
(1)setup函数的参数
setup函数的参数,它主要有两个参数:第一个参数:props ;第二个参数:context
props:它其实就是父组件传递过来的属性会被放到props对象中,我们在setup中如果需要使用,那么就可 以直接通过props参数获取;
- 对于定义props的类型,我们还是和之前的规则是一样的,在props选项中定义;
- 并且在template中依然是可以正常去使用props中的属性,比如message;
- 如果我们在setup函数中想要使用props,那么不可以通过 this 去获取,因为setup没有绑定this;
- 因为props有直接作为参数传递到setup函数中,所以我们可以直接通过参数来使用即可;
context:我们也称之为是一个SetupContext,它里面包含三个属性:
- attrs:所有的非prop的attribute;
- slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用,后面会讲到);
- emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);
(2)setup的返回值
setup返回值的作用:
- setup的返回值可以在模板template中被使用;也就是说我们可以通过setup的返回值来替代data选项;
- 可以返回一个执行函数来代替在methods中定义的方法;
- 但是返回值在template中使用不是响应式的,需要进行处理才行;
<template>
<div>
<h2>{{ message }}</h2>
<h2>{{ messagea }}</h2>
<h2>counter: {{counter}}</h2>
<button @click="addNum">+1</button>
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
required: true,
},
},
// setup(props, context) {
setup() {
let counter = 100;
// 局部函数
const addNum = () => {
counter++
// counter改变了,但是界面中h2中的counter没有改变,不是响应式的
console.log(counter);
}
return {
messagea: "return setup",
counter,
addNum
}
},
};
</script>
2.reactive API
为在setup中定义的数据提供响应式的特性,那么我们可以使用reactive的函数
<template>
<div>
<h2>{{ message }}</h2>
<h2>{{ messagea }}</h2>
<h2>counter: {{ sate.counter }}</h2>
<button @click="addNum">+1</button>
</div>
</template>
<script>
import { reactive } from "vue";
export default {
props: {
message: {
type: String,
required: true,
},
},
// setup(props, context) {
setup() {
// 需要进行响应式的数据放到reactive中,使用时需要sate. 的方式
const sate = reactive({
counter: 100
})
// 局部函数
const addNum = () => {
sate.counter++;
// counter改变了,但是界面中h2中的counter没有改变,不是响应式的,通过reactive实现响应式
console.log(sate.counter);
};
return {
messagea: "return setup",
sate,
addNum,
};
},
};
</script>
Reactive判断的API:
- isProxy :检查对象是否是由 reactive 或 readonly创建的 proxy。
- isReactive :检查对象是否是由 reactive创建的响应式代理: p 如果该代理是 readonly 建的,但包裹了由 reactive 创建的另一个代理,它也会返回 true;
- isReadonly :检查对象是否是由 readonly 创建的只读代理。
- toRaw:返回 reactive 或 readonly 代理的原始对象(不建议保留对原始对象的持久引用。请谨慎使用)。
- shallowReactive:创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (深层还是原生对象)。
- shallowReadonly:创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换(深层还是可读、可写的)。
3.ref API
(1)基本使用
reactive API对传入的类型是有限制的,它要求我们必须传入的是一个对象或者数组类型:如果我们传入一个基本数据类型(String、Number、Boolean)会报一个警告;这个时候Vue3给我们提供了另外一个API:ref API
- ref 会返回一个可变的响应式对象,该对象作为一个 响应式的引用 维护着它内部的值,这就是ref名称的来源;
- 它内部的值是在ref的 value 属性中被维护的;
<template>
<div>
<h2>{{ message }}</h2>
<h2>{{ messagea }}</h2>
<!-- 当在template中使用ref会自动解包,不能counter.value,直接counter就可以了 -->
<h2>counter: {{ counter }}</h2>
<button @click="addNum">+1</button>
</div>
</template>
<script>
import { ref } from "vue";
export default {
props: {
message: {
type: String,
required: true,
},
},
setup() {
let counter = ref(100)
// 局部函数
const addNum = () => {
// 使用时还是需要 counter.value的方式
counter.value++;
console.log(counter.value);
};
return {
messagea: "return setup",
counter,
addNum,
};
},
};
</script>
- 在模板中引入ref的值时,Vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过 ref.value 的方式 来使用;
- 但是在 setup 函数内部,它依然是一个 ref引用, 所以对其进行操作时,我们依然需要使用 ref.value的方式;
(2)ref自动解包
- 模板中的解包是浅层的解包,如果我们将ref放到一个reactive的属性当中,那么在模板中使用时,它会自动解包;
<template>
<div>
<h2>{{ message }}</h2>
<h2>{{ messagea }}</h2>
<!-- 当在template中使用ref会自动解包,不能counter.value,直接counter就可以了 -->
<h2>counter: {{ counter }}</h2>
<!-- ref是浅层解包,当ref被一个对象包裹时,还是需要使用.value的方式进行使用 -->
<h2>counter: {{ info.counter.value }}</h2>
<!-- 当ref最外层是被reactive所包裹时,这是它又会进行解包 -->
<h2>counter: {{ reactiveInfo.counter }}</h2>
<button @click="addNum">+1</button>
</div>
</template>
<script>
import { ref,reactive } from "vue";
export default {
props: {
message: {
type: String,
required: true,
},
},
setup() {
let counter = ref(100)
const info = {
counter
}
const reactiveInfo = reactive({
counter
})
// 局部函数
const addNum = () => {
// 使用时还是需要 counter.value的方式
counter.value++;
console.log(counter.value);
};
return {
messagea: "return setup",
counter,
addNum,
info,
reactiveInfo
};
},
};
</script>
(3)toRefs和toRef
如果我们使用ES6的解构语法,对reactive返回的对象进行解构获取值,那么之后无论是修改结构后的变量,还是修改reactive 返回的state对象,数据都不再是响应式的: 那么有没有办法让解构出来的属性是响应式的呢? Vue为我们提供了一个toRefs的函数,可以将reactive返回的对象中的属性都转成ref;
如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法;
<template>
<div>
<!-- <h2>{{ info.name }}-{{ info.age }}</h2> -->
<h2>{{ name }}-{{ age }}</h2>
<button @click="changeAge">修改age</button>
</div>
</template>
<script>
// import { reactive, toRefs, toRef } from "vue";
import { reactive, toRef } from "vue";
export default {
setup() {
const info = reactive({ name: "tjx", age: 19 });
// 解构后的数据不是响应式的,这里的解构只是取值
// const {name,age} = info
// 1.toRefs:将reactive对象中的所有属性转化为ref,建立链接
// 为了解构后的值也是响应式的,可以使用toRefs
// let {name, age} = toRefs(info)
// 2.toRef: 只转化一个属性
let {name} = info
let age = toRef(info, "age")
const changeAge = () => {
// info.age++;
// 使用toRefs后:
age.value++
};
return {
info,
changeAge,
name,
age
};
},
};
</script>
<style scoped>
</style>
(4)ref其他API
- unref:如果我们想要获取一个ref引用中的value,那么也可以通过unref方法:如果参数是一个 ref,则返回内部值,否则返回参数本身;这是 val = isRef(val) ? val.value : val 的语法糖函数;
- isRef:判断值是否是一个ref对象。
- shallowRef:创建一个浅层的ref对象;
- triggerRef:手动触发和 shallowRef 相关联的副作用
<template>
<div>
<h2>{{ info }}</h2>
<button @click="changeInfo">修改age</button>
</div>
</template>
<script>
import { shallowRef, triggerRef } from "vue";
export default {
setup() {
const info = shallowRef({name: 'tjx'})
const changeInfo = () => {
// ref默认情况下是深层响应式
// info.value.name = 'james'
// 为了让他是浅层的响应式,我们可以使用shallowRef
info.value.name = "james"
// triggerRef用于手动触发这个浅层的ref为深层的ref
triggerRef(info)
}
return {
info,
changeInfo
};
},
};
</script>
4.readonly
通过reactive或者ref可以获取到一个响应式的对象,但是某些情况下,传入给其他地方(组件)的这个 响应式对象希望在另外一个地方(组件)被使用,但是不能被修改,这个时候如何防止这种情况的出现呢?Vue3为我们提供了readonly的方法; readonly会返回原生对象的只读代理(也就是它依然是一个Proxy,这是一个proxy的set方法被劫持,并且不 能对其进行修改);
在开发中常见的readonly方法会传入三个类型的参数:
- 类型一:普通对象;
- 类型二:reactive返回的对象;
- 类型三:ref的对象;
// 普通对象
const info = {name: "tjx"}
const readonlyInfo = readonly(info)
// 响应式对象
const info2 = reactive({
name: 'tjx'
})
const readonlyInfo2 = readonly(info)
// 响应式对象
const info3 = ref({
name: 'tjx'
})
const readonlyInfo3 = readonly(info)
5.computed
计算属性computed:当我们的某些属性是依赖其他状态时,我们可以使用计算属性来处理 ,在Options API中,我们是使用computed选项来完成的; 在Composition API中,我们可以在 setup 函数中使用 computed 方法来编写一个计算属性;
<template>
<div>
<h2>{{ fullName }}</h2>
<button @click="changeName">修改firstName</button>
</div>
</template>
<script>
import { ref, computed } from 'vue'
export default {
setup() {
const fistName = ref("Kobe")
const lastName = ref("Bryant")
// 用法一:传入一个getter函数 computed返回的是一个ref对象
// const fullName = computed(() => fistName.value + lastName.value)
// 用法二:传入一个对象,对象包含getter和setter
const fullName = computed({
get: () => fistName.value + lastName.value,
set(newValue) {
const names = newValue.split(" ")
fistName.value = names[0]
lastName.value = names[1]
}
})
const changeName = () => {
// fistName.value = 'James'
// 因为computed返回值是一个ref所以要使用 .value
fullName.value = "hello tjx"
}
return {
fullName,
changeName
}
}
}
</script>
<style scoped>
</style>
6.数据侦听
在Composition API中,我们可以使用watchEffect和watch来完成响应式数据的侦听; watchEffect用于自动收集响应式数据的依赖;watch需要手动指定侦听的数据源;
(1)watchEffect
当侦听到某些响应式数据变化时,我们希望执行某些操作,这个时候可以使用 watchEffect。
基本使用:
- watchEffect传入的函数会被立即执行一次,并且在执行的过程中会收集依赖;
- 只有收集的依赖发生变化时,watchEffect传入的函数才会再次执行;
// watchEffect:自动收集响应式的依赖
const name = ref("tjx")
const age = ref(18)
const changeName = () => name.value = "kobe"
const changeAge = () => age.value++
// 默认一开始的时候就会执行一次
watchEffect(() => {
console.log(name.value,age.value);
})
停止侦听:在发生某些情况下,我们希望停止侦听,这个时候我们可以获取watchEffect的返回值函数,调用该函数即可。
// watchEffect:自动收集响应式的依赖
const stop = watchEffect(() => {
console.log(name.value, age.value);
});
const changeName = () => (name.value = "kobe");
const changeAge = () => {
age.value++
if (age.value > 25) {
stop()
}
}
清除副作用:
什么是清除副作用呢? 如开发中需要在侦听函数中执行网络请求,但是在网络请求还没有达到的时候,就停止了侦听器, 或者侦听器侦听函数被再次执行了。那么上一次的网络请求应该被取消掉,这个时候我们就可以清除上一次的副作用;
- 在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate ;当副作用即将重新执行 或者 侦听器被停止 时会执行该函数传入的回调函数;我们可以在传入的回调函数中,执行一些清除工作;
const stop = watchEffect((onInvalidate) => {
// 模拟网络请求
const timer = setTimeout(() => {
console.log("网络i请求成功");
}, 2000)
// 根据name和age两个变量清除额外的副作用
onInvalidate(() => {
// 在这个函数中清楚额外的副作用
clearTimeout(timer)
console.log("onInvalidatae");
})
console.log(name.value, age.value);
});
**setup中使用ref:**只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可;
<template>
<div>
<h2 ref="title">哈哈哈</h2>
</div>
</template>
<script>
import { ref, watchEffect } from "vue";
export default {
setup() {
const title = ref(null)
watchEffect(() => {
console.log(title.value);
}, {
// pre:提前执行(默认) post:等挂载完成后再执行
flush: "pre"
})
return {
title
}
},
};
</script>
(2)watch
watch需要侦听特定的数据源,并在回调函数中执行副作用; 默认情况下它是惰性的,只有当被侦听的源发生变化时才会执行回调;
与watchEffect的比较,watch允许我们: 懒执行副作用(第一次不会直接执行); 更具体的说明当哪些状态发生变化时,触发侦听器的执行;访问侦听状态变化前后的值;
侦听单个数据源:
watch侦听函数的数据源有两种类型:一个getter函数:但是该getter函数必须引用可响应式的对象(比如reactive或者ref);直接写入一个可响应式的对象,reactive或者ref(比较常用的是ref);
<template>
<div>
<h2 ref="title">哈哈哈</h2>
<button @click="changeDate">修改</button>
</div>
</template>
<script>
import { reactive, ref, watch } from "vue";
export default {
setup() {
const info = reactive({name: 'tjx', age: 18})
// 1.侦听watch时,传入一个getter函数
// watch(() => info.name, (newValue, oldValue) => {
// console.log("newValue:", newValue, "oldValue:", oldValue);
// })
// 2.传入一个可响应式对象:ref对象/reactive对象
// 情况一:reactive对象获取到的newValue和oldValue本身都是reactive对象
// watch(info, (newValue, oldValue) => {
// console.log("newValue:", newValue, "oldValue:", oldValue);
// })
// 如果希望newValue和oldValue是一个普通对象
watch(() => {
return {...info}
}, (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue:", oldValue);
})
// 情况二:ref对象获取到的newValue和oldValue是值本身
// const name = ref("tjx")
// watch(name, (newValue, oldValue) => {
// console.log("newValue:", newValue, "oldValue:", oldValue);
// })
const changeDate = () => {
info.name = "yinnan"
// name.value = "yinnan"
}
return {
changeDate
}
},
};
</script>
侦听多个数据源:
<template>
<div>
<h2 ref="title">哈哈哈</h2>
<button @click="changeDate">修改</button>
</div>
</template>
<script>
import { reactive, ref, watch } from "vue";
export default {
setup() {
// 定义可响应式对象
const info = reactive({name: 'tjx', age: 18})
const name = ref("tjx")
// 侦听器:侦听多个对象通过 []
// watch([info, name], (newValue, oldValue) => {
watch([() => ({...info}), name], (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue", oldValue);
})
const changeDate = () => {
info.name = "yinnan"
}
return {
changeDate,
info
}
},
};
</script>
侦听响应式对象:
<template>
<div>
<h2 ref="title">哈哈哈</h2>
<button @click="changeDate">修改</button>
</div>
</template>
<script>
import { reactive, watch } from "vue";
export default {
setup() {
// 定义可响应式对象
const info = reactive({
name: "tjx",
age: 18,
friend: {
name: 'yinnan'
}
});
// 侦听器:默认可以深度侦听
// watch(info, (newValue, oldValue) => {
// console.log("newValue:", newValue, "oldValue", oldValue);
// });
// 但是通过这种解构的方式就不能进行深度侦听
watch(() => ({...info}), (newValue, oldValue) => {
console.log("newValue:", newValue, "oldValue", oldValue);
}, {
deep: true, // 深度侦听
immediate: true, // 立即执行
});
const changeDate = () => {
info.friend.name = "yinnanlover";
};
return {
changeDate,
info,
};
},
};
</script>
7.生命周期钩子
过 setup 可以用来替代 data 、 methods 、 computed 、watch 等等这些选项,也可以替代 生命周 期钩子。 那么setup中如何使用生命周期函数呢?可以使用直接导入的 onX 函数注册生命周期钩子;
option API | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
Mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onupdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
activated | onActivated |
deactivated | onDeactivated |
**注:**因为setup是围绕beforeCreate和created生命周期钩子运行的,所以不需要显示地定义他们。换句话说,这些钩子编写的任何代码都因该直接在setup函数中编写;
<template>
<div>
<button @click="counterAdd">{{counter}}</button>
</div>
</template>
<script>
import {onMounted, onUpdated, onUnmounted, ref} from 'vue'
export default {
setup() {
onMounted(() => {
console.log("app mounted");
})
onUpdated(() => {
console.log("app updated");
})
onUnmounted(() => {
console.log("app unmounted");
})
const counter = ref(0)
const counterAdd = () => {
counter.value++
}
return {
counter,
counterAdd
}
}
}
</script>
<style scoped>
</style>
8.Provid和Inject
事实上我们之前还学习过Provide和Inject,Composition API也可以替代之前的 Provide 和 Inject 的选项。
-
我们可以通过 provide来提供数据: 可以通过 provide 方法来定义每个 Property;provide可以传入两个参数: name:提供的属性名称; value:提供的属性值;
-
在 后代组件 中可以通过 inject 来注入需要的属性和对应的值: 可以通过 inject 来注入需要的内容; inject可以传入两个参数:要 inject 的 property 的 name;默认值;
子组件:
<template>
<div>
<h2>{{name}}</h2>
<h2>{{counter}}</h2>
<button @click="homeIncrement">home++</button>
</div>
</template>
<script>
import {inject} from "vue"
export default {
setup() {
// inject有两个参数,第二个参数为默认值
const name = inject("name")
const counter = inject("counter")
const homeIncrement = () => counter.value++
return {
name,
counter,
homeIncrement
}
}
}
</script>
父组件:
<template>
<div>
<home></home>
<h2>"app counter" {{counter}}</h2>
<button @click="increment">app++</button>
</div>
</template>
<script>
import { provide, ref, readonly } from "vue";
import Home from "./Home.vue";
export default {
components: {
Home,
},
setup() {
const name = ref("tjx");
let counter = ref(100);
// 1.为了增加 provide 值和 inject 值之间的响应性,我们可以在 provide 值时使用 ref 和 reactive。
// 2.为了实现单项数据流(子不能改变父数据)这里可以通过readonly
provide("name", readonly(name));
provide("counter", readonly(counter));
const increment = () => counter.value++;
return {
increment,
counter
};
},
};
</script>
<style scoped>
</style>