前言
Vue3 自2020年9月份发布之后,截至目前也有半年的时间了,一直以来,刷过文档,看过文章,窥过视频,就是没有撸过代码 ~ 那么 ~ 今天 ~ 现在 ~ 在这里 ~ 撸一把
针对 Vue3 和 Vue2 的差异,我就不过多的言语了,官网也给到了一些说明,最直接能体现的就是编码的风格上,把Vue2中的零散逻辑改成新提供的CompositionAPI组合在一起来维护,并且还可以将单独的逻辑的功能拆分成单独的文件,完美的解决了Vue2中mixin混入的缺点(命名冲突,逻辑重用等问题)
进入正题 : Composition API
setup
setup 是 Vue3 新增的一个选项,是Composition API 的入口函数,它的执行时机是在 beforCreate
之前,实践是检验真理的唯一标准
export default {
beforeCreate () {
console.log(`----beforeCreate----`);
},
created () {
console.log(`----created----`);
},
setup () {
console.log(`----setup----`);
// 这些跟 vue2 的生命周期差不多,就是写法上加了前缀 on
onBeforeMount(() => console.log(`----onBeforeMount----`));
onMounted(() => console.log(`----onMounted----`));
onBeforeUpdate(() => console.log(`----onBeforeUpdate----`));
onUpdated(() => console.log(`----onUpdated----`));
onBeforeUnmount(() => console.log(`----onBeforeMount----`));
onUnmounted(() => console.log(`----onUnmounted----`));
}
}
由于在执行setup 时尚未创建组件实例,因此在 setup 选项中没有 this 。
setup 参数
setup 接收两个参数,分别是 props,context , 组件传入的属性和上下文,这里的props是响应式的,由于是响应式的,所以不可以使用 ES6 解构,解构会影响它的响应式
那么context有什么作用呢 ?
上边说了,在 setup 执行的时候并没有 this 对象,所以 context 就提供了 Vue 常用的三个属性,attrs , slot 和 emit , 分别对应的 Vue2 中的 $attr 属性 slot 插槽和 $emit 发布事件,并且这几个属性都是自动同步最新的值
默认的 props 和 context
ref , toRefs 和 reactive
在Vue2 中初始化的响应式数据都放在 data 中 , 但是 Vue3 做了一个小小的变动,响应式数据通过 ref 和 reactive 来声明
有人说: ref 一般用于声明基本数据类型,reactive 一般用于声明引用数据类型,其实 ref 也是可以声明对象的响应式绑定的,不信小伙伴可以试试,反正我是试过了,好了,我们来看看它们具体怎使用的
<template>
<div class="about">
<h3>我是 About</h3>
<p>
{{ refValue }}
<br />
原对象形式: {{ reactiveValue.name }} : {{ reactiveValue.age }}
<br />
toRefs形式(解构):{{ name }} : {{ age }}
</p>
</template>
<script>
import {
ref, reactive
} from "vue";
export default {
setup () {
const refValue = ref("---");
const reactiveValue = reactive({ name: "小娜", age: 17 });
return {
refValue,
reactiveValue,
...toRefs(reactiveValue), // 解构 reactiveValue ,相当于返回了多个变量 name,age .....
};
},
};
</script>
相信小伙伴们也看到了 toRefs , 其实它就是将对象解构,然后 return 出去,给到组件实例使用 ;
通过上边的一个实例代码,相信童鞋们对Composition API 有个大概的认识,脱离开 Vue2 的 options API 来讲,Composition API 就是按需引入需要使用的 vue 属性,通过入口函数 setup 声明,然后进行一系列操作(生命周期,监听计算,逻辑处理等)最后 return 出去,给到组件渲染,这里需要注意一点,凡是响应式的数据,组件需要使用,必须 return 出去
注,Vue3 的声明周期与Vue2 的使用上有点小小的差异与改变,在Vue3 里需放在 setup 函数里,并且有个前缀 on , 然后组件实例销毁前后变的更语义化,onBeforeUnmount , onUnmounted
好,我们按照上边的逻辑,分别实践一下生命周期,监听计算,逻辑处理等操作
生命周期
<template>
<div class="about">
<h3>我是 About</h3>
<p>
{{ refValue }}
<br />
原对象形式: {{ reactiveValue.name }} : {{ reactiveValue.age }}
<br />
toRefs形式(解构):{{ name }} : {{ age }}
</p>
</div>
</template>
<script>
import {
ref, reactive, toRefs, onMounted, onBeforeUpdate, onUpdated, onRenderTriggered,
} from "vue";
export default {
setup (props, context) {
onBeforeUpdate(() => console.log(`----onBeforeUpdate----`));
onUpdated(() => console.log(`----onUpdated----`));
// 新增生命周期:监听哪些数据发生变化
onRenderTriggered((event) =>
console.log(`----onRenderTriggered----`, event)
);
const refValue = ref("---");
const reactiveValue = reactive({ name: "小娜", age: 17 });
// 赋值声明变量
const D = new Date();
onMounted(() => {
console.log("执行mounted")
refValue.value = `${D.getFullYear()}年${D.getMonth()}月${D.getDay()}日`;
reactiveValue.name = "Hisen";
reactiveValue.age++;
});
return {
refValue,
reactiveValue,
...toRefs(reactiveValue), // 解构 reactiveValue ,相当于返回了多个变量 name,age .....
};
},
};
</script>
首先可以看到的是页面的渲染,从代码的角度,我们可以看出,进入 setup 函数,执行 mounted 声明周期,对变量声明进行赋值,然后会触发 Vue3 新增的生命周期 onRenderTriggered
, 这个声明周期主要用来监听哪些数据发生了变化,最后触发 update
更新前后声明周期,此时,如若我们离开当前组件,则会触发组件销毁前后声明周期,跟 Vue2 几乎可以说是一样的顺序;具体的生命周期可参考下图
监听与计算
这两个属性,可以说写过 vue 的童鞋都不陌生吧,反而还会用的很多,我们来看看Vue3 中式怎么使用它们的
<template>
<div class="about">
<h3>我是 About</h3>
<p>
{{ refValue }}
<br />
原对象形式: {{ reactiveValue.name }} : {{ reactiveValue.age }}
<br />
toRefs形式(解构):{{ name }} : {{ age }}
</p>
计算属性值:{{computedValue}}
</div>
</template>
<script>
import {
ref, reactive, watch, toRefs, onMounted, computed
} from "vue";
export default {
setup () {
const refValue = ref("---");
const reactiveValue = reactive({ name: "小娜", age: 17 });
// 复制声明变量
const D = new Date();
onMounted(() => {
refValue.value = `${D.getFullYear()}年${D.getMonth()}月${D.getDay()}日`;
reactiveValue.name = "Hisen";
reactiveValue.age++;
});
// 多变量监听,注意基本类型与引用类型的不同写法
// 第三个参数是 options 支持 deep、immediate 和 flush
watch(
[refValue, () => reactiveValue.age],
([n_ref, n_rt], [o_ref, o_rt]) => {
console.log("refValue:new", n_ref, "old", o_ref);
console.log("reactiveValue.key: new", n_rt, "old", o_rt);
}, {}
);
// 计算属性
let computedValue = computed({
get: () => reactiveValue.age + 10,
set: val => {
// 创建一个可写的 ref 对象
console.log(val)
}
})
return {
refValue,
reactiveValue,
...toRefs(reactiveValue), // 解构 reactiveValue ,相当于返回了多个变量 name,age .....
computedValue
};
},
};
</script>
同样,我们从代码的逻辑上来看,炸眼一看,效果是实现了,可是写法上略有一丝复杂呀,听我细细道来,
watch 我这里直接写了一个多变量同时监听,当然也可以写成单个的监听,然后多个 watch 执行,具体的用法是这样的
watch(source, callback, [options])
source:可以支持string,Object,Function,Array; 用于指定要侦听的响应式变量
callback: 执行的回调函数
options:支持deep、immediate 和 flush 选项。
还有一个小小的点,需要大家注意一下,针对ref声明和reactive声明,watch 传递的第一个参数是有要求的,写法上需要注意
ref 声明的变量,可以直接写变量名,而reactive 声明的变量则需要使用箭头函数,类似与这样
const a = ref(); // watch(a,,() => {},{})
const b = reactive() // watch(() => b,() => {},{})
多变量同时监听呢
// 根据不同类型的声明,第一个参数,使用不同的方式
watch([one,two],([new_one,new_two],[old_one,old_two]) => { ... },{})
计算属性呢就比较简单了,直接创建了一个可读写的新变量(依赖已声明的变量)
逻辑处理这块儿呢,其实会Vue2 的童鞋看到这里就可以大概的明白了,就是 setup 函数里的一系列操作,最终返回出去响应式变量 。
组件通信
这个也许是众多童鞋比较在意的事情,Vue3 中如何进行组件通信,参数传递,当然了,Vuex 依然是通用的,那么在小型项目中,,简单的通信如何做呢 ?
父组件
<template>
<div class="about">
<h3>我是 About</h3>
<p>
{{ refValue }}
<br />
原对象形式: {{ reactiveValue.name }} : {{ reactiveValue.age }}
<br />
toRefs形式(解构):{{ name }} : {{ age }}
</p>
<hr />
【数组】
<button @click="addList">Push list.lengrth++</button>
<br />
<span v-for="i in reactiveValueArray"
:key="i">{{ i }}, </span>
</div>
<hr />
<h3>About 子组件</h3>
<Detail :attr="reactiveValue"
@addList="addAgeFun" />
</template>
<script>
import {
ref, reactive, toRefs, provide, onMounted
} from "vue";
import Detail from "./Detail.vue";
export default {
components: { Detail },
setup () {
const refValue = ref("---");
const reactiveValue = reactive({ name: "小娜", age: 17 });
const reactiveValueArray = reactive([1, 2, 3, 4, 5]);
// 复制声明变量
const D = new Date();
onMounted(() => {
refValue.value = `${D.getFullYear()}年${D.getMonth()}月${D.getDay()}日`;
reactiveValue.name = "Hisen";
reactiveValue.age++;
});
// 按钮点击事件
const addList = () => {
reactiveValueArray.push(reactiveValueArray.length + 1);
};
// 父组件监听子组件 emit
const addAgeFun = (num) => {
reactiveValueArray.push(num._value);
}
// provide / inject 提供注入的方式传参
provide("provideValue", {
title: 'from about comp',
value: refValue
})
return {
refValue,
reactiveValue,
reactiveValueArray,
...toRefs(reactiveValue), // 解构 reactiveValue ,相当于返回了多个变量 name,age .....
addList,
addAgeFun,
};
},
};
</script>
子组件
<template>
<div>
我是 Detail , About 的子组件
<p>props 接收参数: {{name || "From App link-router"}} {{age || "no attrs"}}</p>
<p>
<!-- inject_value 可以用 toRefs 解构 -->
provie/inject 接收参数:
静态:{{inject_value.title}}
动态:{{inject_value.value._value}}
</p>
<input v-model="txt"
type="text">
<button @click="add">添加到父组件 num 数组</button>
<hr>
<p>计算属性值 {{computedValue}}</p>
</div>
</template>
<script>
import { ref, reactive, toRefs, nextTick, inject, computed } from 'vue';
export default {
props: {
attr: Object
},
setup (props, context) {
const txt = ref();
const attrs = reactive(props.attr);
// 注入的方式接收参数
const inject_value = inject("provideValue");
// 上下文包含 emit , slot , props
// 利用 emit 发布向父组件传参
const add = () => {
nextTick(() => {
txt.value && context.emit("addList", txt);
txt.value = ""
})
}
// 计算属性
let computedValue = computed({
get: () => txt.value,
set: val => {
// 创建一个可写的 ref 对象
console.log(val)
}
})
return {
...toRefs(attrs),
add,
txt,
inject_value,
computedValue
}
}
};
</script>
props : 可以看出来,父组件传递来的参数,跟Vue2 中一样,需要在子组件用 props 去接收,但是Vue3 的所有操作,均在setup 函数里,所以就用到了我们上边说的,setup 函数接收两个参数,props/context , 在这里就起到了质的作用
其实简单点儿,就是在 setup 函数声明变量接收props传递过来的参数,再 return 出去而已 。
细心的小伙伴可能也看到了另外一个东西,provide / inject
在 Vue2 中相信有的童鞋已经用过了,跨级传递参数,注入的方式,用法上也大同小异,只是都写在了 setup 函数里,子组件接收呢,也是同理,声明接收 inject 函数的返回值,即父组件传递过来的参数,再 return 出去,即可。
通过上边的两种形式,可以看出来,父子组件之间的传递,那么子组件如何向父组件传递数据呢,跟Vue2 基本相同,只是写法上略有差异,同样的发布订阅,在Vue3中,emit 在 setup 函数的第二个参数context 里,所以当子组件需要向父组件传递参数的时候,只用触发 context.emit , 用法参考 Vue2 中的$emit , 父组件同样的监听方式,只是方法定义在了 steup 函数中 , 记着需要 return 出去。
注:凡是组件需要使用到的变量和方法,均需要在 setup 函数的最后 return 出去, 否则找不到 !
mixin
纵观 Vue2 的 mixin 混入,说的简单点就是代码合并,存在优先级,变量/方法命名冲突,不语义化等问题,在 Vue3 中,有人说是重用逻辑抽取,纯函数编程,也有人说是自定义 hook , 其实都是对的, 自定义 hook 本身就是重复逻辑的封装,方便处处使用,那我们来实践一下
<template>
<div class="about">
<h3>我是 About</h3>
<h3>Mixin 复用代码</h3>
<input type="number"
v-model="number">
mixin 通过复用方法将输入的数字千分位
<br>
<h4>{{mixinValue}}</h4>
<button @click="mixinFunc(number)">变化</button>
</div>
</template>
<script>
import { ref } from "vue"
import Mixin from "./useMixin.js"
export default {
setup () {
const number = ref();
const { mixinValue, mixinFunc } = Mixin();
return {
number,
mixinValue,
mixinFunc,
};
},
};
</script>
useMixin.js 提供一个将普通数字转为为千分位
import { ref } from "vue"
export default function () {
const mixinValue = ref();
const mixinFunc = (n) => {
mixinValue.value = n.replace(/\d{1,3}(?=(\d{3})+$)/g, function (s) {
return s + ','
})
}
return { mixinValue, mixinFunc }
}
通过上边的一个简单实例,可以看出来,Vue3 中的mixin基本上就是单独定义通用逻辑与方法,甚至说是自定义的一个复用hook,可以拿到其它任何组件使用,与此同时也解决了Vue2中的变量命名冲突,不语义化 等劣势,相比之下,我可能更喜欢 Vue3 的这种写法,更清晰明了更好维护 ;
Vue Router
路由是我们开发过程中必不可少的神器,那Vue官方本身提供的router也是很友好的,在这里 Vue3 跟Vue2 的使用上稍微有点不一样,其实说到底就是 setup 里没有 this , 在 Vue3 中该怎么用而已,我们看一个实例就明白了
<template>
<div class="about">
<h3>我是 About</h3>
<h4>Vue Router</h4>
<router-link to="/copy_about"> router-link 跳转方式 About 2 号</router-link>
<button @click="routerClick">routerClick</button>
</div>
</template>
<script>
import { useRoute, useRouter } from 'vue-router'
export default {
setup () {
// router click event
const router = useRouter();
const route = useRoute();
const routerClick = () => {
// router.push({ path: "/", query: { params: '123456789' } });
router.push({ name: "Home", params: { params: '123456789' } });
}
return {
routerClick
};
},
};
</script>
看到代码,也许很多童鞋就已经明白了,就是按需引入,哪个组件里边需要使用到路由,就单独引入,主要有两个模块 router , route
路由实例和路由对象,这个熟悉Vue2 路由的话,应该是不陌生的,那使用上也是一样的,直接调用路由实例的方法,进行跳转,传参等,截张图来看一下 router 和 route 都有些啥吧
Vuex
状态管理,Vue的标配,Vue3 中除了使用上的不同,其它可以说是一毛一样,话不多说,直接上代码
<template>
<div class="about">
<h3>我是 About</h3>
<h4>Vuex</h4>
vuex store state count 值 {{storeNum}}
<button @click="vuexClick">commit / dispatch count++</button>
</div>
</template>
<script>
import { computed } from "vue"
import { useStore } from "vuex"
export default {
setup () {
// vuex
const store = useStore();
const storeNum = computed(() => {
return store.state.count;
})
const vuexClick = () => {
store.commit("addCount", { attr: "传参,mutations" });
store.dispatch("asyncAddCount", { attr: "传参,actions" });
}
return {
storeNum,
vuexClick
};
},
};
</script>
store 代码
也可以模块分发
import { createStore } from "vuex";
export default createStore({
state: {
count: 1
},
mutations: {
addCount (state, obj) {
console.log(state, obj)
state.count++;
}
},
actions: {
asyncAddCount (context, obj) {
console.log(context, obj)
context.commit("addCount", obj)
}
},
modules: {},
});
看上去跟 Vuex 非常类似,直接一个 useStore hook ,在需要使用的组件里引入 store , store 里的状态,需要使用 computed 计算属性获取,这样才可以触发响应式更新,那唯一修改 state 的 mutation ,还是老样子,需要使用 commit 触发, 所以在组件里就是 store.commit () , actions 也是同理
结语
回顾下来,整个 Vue3 常用的知识点,我们都默默的过了个遍,当然也不排除还一些知识小细节,剩下的就需要大家自行解决了。
Github 实例代码
ok 觉得有帮助的可以关注,点赞,评论,相互吹捧,共同进步