》使用传统的option配置方法写组件的时候问题,随着业务复杂度越来越高,代码量会不断的加大;由于相关业务的代码需要遵循option的配置写到特定的区域,导致后续维护非常的复杂,同时代码可复用性不高,而composition-api就是为了解决这个问题而生的。
》Composition API字面意思是组合APl,它是为了实现基于函数的逻辑复用机制而产生的。主要思想是,我们将它们定义为从新的 setup函数返回的JavaScript变量,而不是将组件的功能(例如state、method、computed等)定义为对象属性。
1. 首先创建一个vue脚手架, 我们在About组件中进行操作
在export default配置项中,我们不用写传统的vue配置项格式了,而使用setup函数来代替,这里我们创建一个对象,需要写在reactive方法里,reactive方法需要从vue中引入,然后在末尾用return返回
import {reactive} from 'vue'
export default {
setup(){
const data = reactive({
count:0
})
return {data} //返回出去了才能访问到
}
}
在页面中访问:
count:{{data.count}}
2. 如果我们使用一个简单的计算属性(这里是一个方法),可以将计算属性返回的结果直接保存至变量中,注意计算属性也是需要引入的
import {reactive, computed} from 'vue'
export default {
setup(){
const data = reactive({
count:0,
double:computed(()=>data.count*2)
})
return {data}
}
}
count:{{data.count}}
double:{{data.double}}
3. 定义方法,方法也是定义在setup里,通过return返回,因为setup本身就是一个方法,这里就不能简写add方法了,完整代码↓
count:{{data.count}}
double:{{data.double}}
import {reactive, computed} from 'vue'
export default {
setup(){
const data = reactive({
count:0,
double:computed(()=>data.count*2)
})
function add(){
data.count++;
}
return {data,add}
}
》setup()函数是vue3中专门新增的方法,可以理解为Composition Api的入口.
》执行时机在beforecreate之后,create之前执行.
setup函数在创建组件之前被调用,所以在setup被执行时,组件实例并没有被创建。因此在setup函数中,我们将没有办法获取到this
export default {
name:'SubComp',
props:{
one:{
type:String
},
two:{
type:String
}
}
setup(){
// 在创建组件之前调用的,没有this
console.log('setup');
console.log(this); //undifined
},
beforeCreate() {
console.log('beforeCreate--');
},
created() {
console.log('Created--');
console.log(this); //Proxy {…}
},
}
但是setup函数提供了两个参数:props和context,用来访问其他组件传过来的数据
props:在父组件向子组件传值时,子组件用props属性接收到的数据,可以通过setup中的props参数访问
export default {
name:'SubComp',
props:{
one:{
type:String
},
two:{
type:String
}
},
setup(props){
console.log(props.one+props.two);
}
}
context:上下文对象,大致包含context.attrs、context.slot、context.parent、context.root、context.emit、context.refs这些属性
export default {
name:'SubComp',
props:{
one:{
type:String
},
two:{
type:String
}
},
setup(props,context){
console.log(props.one+props.two);
//当子组件没有用props接收的时候,可以用attrs访问父组件中的数据
console.log(context.attrs.desc);
// 获取父组件插槽的内容
console.log(context.slots.default());
//向父类传数据
context.emit('myadd','向父组件传数据')
}
}
我们也可以把context解构出来方便使用
setup(props,{attrs,slots,emit}){
// 在创建组件之前调用的,没有this
console.log('setup');
console.log(this);
console.log(props.one+props.two);
//当子组件没有用props接收的时候,可以用attrs访问父组件中的数据
console.log(attrs.desc);
// 获取父组件插槽的内容
console.log(slots.default());
emit('myadd','向父组件传数据')
},
在setup中声明一个变量num,以及一个函数,在函数中更改num的值,return出去的num的值并没有变化,原因就在于:原生变量的数据是没有响应式的
export default {
name:'ComApi',
setup(){
let num=1;
let myfun=()=>{
num++;
console.log(num); //2
}
return{
num, //1
myfun
}
}
}
如果我们要声明一个数据是响应式的变量,需要用到ref()函数 ↓
ref()函数用来给定的值创建一个响应式的数据对象,ref()的返回值是一个对象,这个对象上只包含一个.value属性.
ref的变量:{{num2}}{{myfun2(55)}}
import {ref} from 'vue'
export default {
name:'ComApi',
setup(){
//let num=1;
//let myfun=()=>{
// num++;
// console.log(num); //2
//}
let num2=ref(22);
let myfun2=(newvalue)=>{
console.log(num2.value); //55
num2.value=newvalue;
}
return{
//num, //1
//myfun,
num2, //55
myfun2
}
}
}
注:在setup程序中用的时候需要.value,在模板中直接用变量名即可
如果我们要声明一个对象,就不能用refs了,需要用到reactive函数将对象变成响应式
reactive中的对象:{{user.name}}---{{user.age}}
import {reactive, ref} from 'vue'
export default {
name:'ComApi',
setup(){
//声明对象:reactive将对象变成响应式
let user=reactive({
name:'aaa',
age:18,
sex:'男'
})
return{
user
}
}
}
此时插值里用user.name调用比较麻烦,那能不能直接用name调用呢,可以考虑把对象展开,一个一个返回,可以使用es6中的三点运算符
对象展开之后,数据就不是响应式的了,这时可以使用toRefs函数转换成响应式
{{name}}---{{age}}
name:
age:
import {reactive, ref, toRefs} from 'vue'
export default {
name:'ComApi',
setup(){
//声明对象:reactive将对象变成响应式
let user=reactive({
name:'aaa',
age:18,
sex:'男'
})
return{
...toRefs(user)
}
}
}
将ref响应式数据挂载到reactive中,当把ref()创建出来值直接挂载到reactive()中时,会自动把响应式数据对象的展开为原始的值,不需要通过.value就可以直接访问到
let num2=ref(2);
let user=reactive({
name:'aaa',
age:18,
sex:'男',
num2
})
let myfun2=(newvalue)=>{
user.num2=100; //响应式
}
如果要将响应式的数据转换为原始数据,可以使用readonly函数
import { readonly } from 'vue' //记得引入
let user2=readonly(user)
用来判断变量是原生的还是响应式的,也需要引入
let num3=isRef(num2)?num2.value=44:num2=55 //44
computed()用来创建计算属性,返回值是一个ref的实例。
1. 引入computed,在setup中定义一个响应式user对象,定义计算属性
import {reactive, toRefs, computed} from 'vue'
export default {
name:'ComputedDemo',
setup() {
const user = reactive({
firstname:'aaa',
lastname:'bbb'
})
let fullname=computed(()=>{
return user.firstname+'.'+user.lastname
})
return{
...toRefs(user),
fullname
}
}
}
2. 定义完之后,直接在插值里面访问对应的变量就可以了,这里firstname和lastname的值更改之后,fullname也会同步变化
计算属性
firstname:
lastname:
{{fullname}}
》watch()函数用来监视某些数据项的变化,从而触发某些特定的操作。
》watchEffect立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。
定义(默认会初始化一下,每当值变化的时候就会监听一次):
import { ref } from "vue";
import { watch, watchEffect } from 'vue';
export default {
name: "WatchDemo",
setup() {
let a = ref(1);
let b = ref(2);
watch(()=>{
console.log(a.value+'------'+b.value);
})
watchEffect(()=>{
console.log(a.value+'####'+b.value);
})
return {
a,
b,
};
},
};
watch还有更多用法,比如我们可以监听某个值,当其值变化时进行回调,回调函数中可以传入新值和初始值两个参数;监听某个值时不会自动初始化,通过第三个参数immediate的值是true或false来确认是否以当前的初始值执行回调函数
import { ref } from "vue";
import { watch, watchEffect } from 'vue';
export default {
name: "WatchDemo",
setup() {
let a = ref(1);
let b = ref(2);
//监听某个值,不会自动初始化,需要加参数
watch([a,b],([newA,oldA],[newB,oldB])=>{
console.log(newA+'---------'+oldA);
console.log(newB+'#########'+oldB);
},{immediate:true})
return {
a,
b,
};
},
};
import { reactive,toRefs } from "@vue/reactivity";
import { watch } from "@vue/runtime-core";
export default {
name: "WatchDemo",
setup() {
//监听对象
const user = reactive({
a:1,
b:2
})
watch([()=>user.a,()=>user.b],([newA,newB],[oldA,oldB])=>{
// console.log(user.a+'######'+user.b);
console.log(newA+'---------'+oldA);
console.log(newB+'#########'+oldB);
},{immediate:true})
return {
...toRefs(user)
}
},
};
在新版的生命周期函数,可以按需导入到组件中,且只能在setup()函数中使用。
import {onMounted} from 'vue'
export default {
name:'LifeHook',
setup() {
onMounted(() => {
console.log('mounted!')
})
onUpdated(() => {
console.log('updated!')
})
onUnmounted(() => {
console.log('unmounted!')
})
}
}
下面是vue 2.x 的生命周期函数与新版 Composition API 之间的映射关系
// beforeCreate -> use setup()
// created -> use setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
》父子组件:通过props,$emit,【$root,$parent,$children】
》非父子组件:Vuex实现,父子层层传递、$ref
》Vue官网建议,在正常情况下,这两种方式已经能满足绝大多数甚至所有的业务需求,对于应用程序代码应优先使用它们处理。
》provide/inject这对选项允许一个祖先组件向其所有子孙后代组件注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
》provide就相当于加强版父组件prop,可以跨越中间组件,inject就相当于加强版子组件的props
首先我们来看一下在data中使用的provide和inject:
//父组件RootApp中
import TwoComp from '../components/TwoComp.vue'
export default {
components: { TwoComp },
name:'RootApp',
data() {
return {
title:'这是根组件提供的数据'
}
},
provide(){
return{
title:this.title
}
}
}
//接下来我们跨过子组件,来到孙子组件ThreeComp中接收
export default {
name:'ThreeComp',
inject:['title']
}
这是孙子组件
{{title}}
这种方式存在一个问题:子组件接收的数据不是响应式的
下面来看在setup中使用响应式数据进行provide和inject传值:
//父组件RootApp中响应式数据
import TwoComp from '../components/TwoComp.vue'
import {provide, ref, reactive, toRefs} from 'vue'
export default {
components: { TwoComp },
name:'RootApp',
setup() {
let title=ref('这是根组件提供的数据')
const user = reactive({
name:'abc',
age:18
})
provide('title',title)
provide('user',user)
return{
title,
...toRefs(user)
}
}
}
//孙子组件ThreeComp
import { inject } from 'vue'
export default {
name:'ThreeComp',
// inject:['title']
setup() {
let title=inject('title')
let user=inject('user')
return{
title,
user
}
}
}
并且使用provide和inject传值是可逆的,当我们改变子组件接收的值,父组件也会同步变化
更多API的使用可以参考官方文档:https://vue3js.cn/vue-composition-api
》setup和Vue的Composition API的引入,开辟了新的可能性,但为了能够充分利用Vue Router的潜力,我们将需要使用一些新功能来替换对访问this和组件内导航保护的使用。
》由于我们无权访问setup的内部this,因此无法直接访问this.$router或this.$route了,相反,我们使用useRouter和useRoute函数。
》请注意,我们仍然可以访问$router和$route在模板中,因此无需在router或之route内返回setup。
示例:选择左侧菜单,将菜单项的索引传给右侧界面显示出来
1. 创建一个RouterApi页面作为主页,同时创建另一个页面MyPage显示右侧的页面,配置路由,将MyPage嵌套在主页里(路由配置参考:Router路由详解_m0_50744582的博客-CSDN博客)
{
path: '/routerapi',
name: 'RouterApi',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/RouterApi.vue'),
children:[
{
path:'page',
component: () => import('../views/MyPage.vue'),
},
{
path:'page/:id',
component: () => import('../views/MyPage.vue'),
}
]
},
然后在主页的的data中定义一些数据并遍历显示出来,作为菜单项,并向访问的页面传参
2.在Mypage页中就可以直接使用$route访问传过来的参数
MyPage
{{$route.params.id}}
运行界面如下:
但是一般插值中我们只写一些简单的数据,如果要将传递过来的参数进一步处理呢?在原来的vue中我们可以定义计算属性,通过this.$route来访问参数
在组合API的setup函数中通过route.params.id可以访问,但是存在一个问题,setup函数只执行一次,接收到的参数不会变化,这时我们想到了监听器
我们可以创建一个监听器来监听当前路由的参数,使用useRoute函数对其进行操作:
MyPage
{{$route.params.id}}
id:{{id}}
接下来我们看一下useRouter函数的使用:
1. 在主页写两个跳转链接
文章一
2. 新建一个MyArticle页面,用userRouter进行页面跳转
MyArticle
{{$route.query.name}}
{{$route.query.keyword}}
》导航守卫:
- 尽管您仍可以将组件内导航保护与setup功能结合使用,但Vue Router会将更新和离开提供CompositionAPI函数:
- onBeforeRouteLeave((to, from) => {})
- onBeforeRouteUpdate(async (to, from) =>{})
onBeforeRouteLeave((to,from)=>{
let answer=window.confirm(`你确定要从${from.fullPath}到${to.fullPath}吗`)
if(!answer) return false
})
更多可以参考官网:Vue Router and the Composition API | Vue Router
因为无法访问setup的内部this,所以我们使用useStore函数。
vuex参考:Vuex五大核心概念_m0_50744582的博客-CSDN博客_vuex的五个属性
定义两个num,对num分别进行状态管理,先来温习一下原代码:
//index.js
import { createStore } from 'vuex'
export default createStore({
state: {
num1:11,
num2:22
},
getters:{
double1(state){
return state.num1*2
}
},
mutations: {
changenum1(state,payload){
state.num1=payload;
}
},
actions: {
timecnum1({commit,state}){
setTimeout(()=>{
commit('changenum1',44)
},3000)
}
},
modules: {
}
})
Vuex API useStore
num1:{{$store.state.num1}}
getters-num1:{{$store.getters.double1}}
当使用组合API结合vuex时,所有内容都写在setup函数中,以下是完整代码:
// index.js
import { createStore } from 'vuex'
export default createStore({
state: {
num1:11,
num2:22
},
getters:{
double1(state){
return state.num1*2
},
double2(state){
return state.num2*2
}
},
mutations: {
changenum1(state,payload){
state.num1=payload;
},
changenum2(state,payload){
state.num2=payload;
}
},
actions: {
timecnum1({commit,state}){
setTimeout(()=>{
commit('changenum1',44)
},3000)
},
timecnum2({commit,state}){
setTimeout(()=>{
commit('changenum2',55)
},3000)
}
},
modules: {
}
})
Vuex API useStore
num1:{{$store.state.num1}}
getters-num1:{{$store.getters.double1}}
num2:{{num2}}
getters-num2:{{double2}}
更多可以去官网学习:Composition API | Vuex