一、基础简介
vue3.0版本于2020年9月18日晚11点半发布,更小、更快的全新体系不仅加强了typescript支持,开放了更多的底层功能,提高了可维护能力。
更小:
2.0采用的是面向对象编程的思想,3.0则采用函数是编程的思想,充分利用函数式编程组合大于继承的优势,采用函数式编程更利于逻辑功能的复用,webpack打包时更有利于tree-shaking,更利于代码的压缩,更利于返回值类型校验,压缩后的文件体积更小。
更快:
3.0修改了虚拟dom的算法(即diff算法 - 比对虚拟dom有没有变化); 2.0需要diff所有的虚拟dom节点,而vue3参考了SVELTE框架的思想,先分层次-然后找不变化的层-针对变化的层进行diff,更新速度不会再受template大小的影响,而是仅由可变的内容决定。经过尤雨溪自己的测试,大概有6倍的速度提升。
加强typescript支持:
3.0的源码开始采用了ts进行编写,给开发者也提供了支持ts的开发式。
Api一致性:
3.0的版本可以完美兼容vue2的api。
提高可维护能力:
从源码的层面上提供了更多的可维护能力。
开放更多底层功能:
把更多的底层功能开放出来,比如render、依赖收集功能,我们可以更好的进行自定义化开发,可以写更多的高阶组件。
数据双向绑定:
关于数据双向绑定的实现,vue2 采用了defineProperty,而vue3则采用了proxy。
优点:
1. 使用proxy不污染源对象,会返回一个新对象,defineProperty是注入型的,会破坏源对象
2. 使用proxy只需要监听整个源对象的属性,不需要循环使用Object.defineProperty监听对象的属性
3. 使用proxy可以获取到对象属性的更多参数,使用defineProperty只能获取到监听属性的新值newvalue
二、知识
1. setup
值为一个函数,组件中所用到的数据、方法等均需配置在setup中。
有两种返回值类型:
(1)对象。对象中的属性、方法在模版中可以直接使用。
(2)渲染函数。可以自定义渲染内容。
注意点:
尽量不要与vue2的配置混合使用。其实可以在vue2的配置中(data、methods、computed)访问到setup中的属性和方法。
setup不能时一个async函数,因为其返回值不再是一个对象,而是一个promise,模版看不到return对象中的属性(如果是异步加载组件的情况可以除外)。
vue2与vue3混用出现方法或者数据重名时以vue3为主。
只在beforeCreate之前执行一次,this是undefined。
两个参数:
props:值为对象,包含组件外部传递过来,且组件内部声明接收了的属性。
context:上下文对象。attrs -> 值为对象,包含组件外传递过来,但没有在props中声明的属性,相当于this.$attrs
。slots -> 收到的插槽内容,相当于this.$slots
。emit -> 分发自定义事件的函数,相当于this.$emit
。
2. ref
定义一个响应式数据。js中操作数据,需要XXX.value
才能读取值,模版中则不需要。
接收的数据可以是基本类型,也可以是对象类型(对象尽量用reactive)。
3. reactive
定义一个对象类型的响应式数据。接收一个对象或数组,返回一个代理器对象(proxy的实例对象),且数据是深层次响应式。
4. 计算属性
基本与vue2的保持一致
import { computed } from 'vue'
setup(){
let fullName = computed(()=>{
return person.firstName+'-'+person.lastName
})
let fullName = computed({
get() {
return person.firstName+'-'+person.lastName
},
set(value) {
const nameArr = value.split('-')
person.firstName = nameArr[0]
person.lastName = nameArr[1]
}
})
}
5. 监听
基本与vue2配置功能一致。
注意点:
监视reactive定义的响应式数据时,oldValue无法正确获取,强制开启了深度监视(deep配置失效)。
监视reactive定义的响应式数据中的某个属性时,deep配置有效。
// 情况一: 监视ref定义的响应式数据
watch(sum, (newValue,oldValue) => {
console.log('sum变化了', newValue,oldValue)
},{immediate: true})
// 情况二:监视多个ref定义的响应式数据
watch([sum, msg], (newValue,oldValue) => {
console.log('sum或msg变化了', newValue,oldValue);
})
/*
情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue
若watch监听的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue) => {
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep: false})
// 情况四:监视reactive定义的响应式数据中的某个属性
watch(() => person.job,(newValue,oldValue) => {
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep: false})
/*
情况五:监视reactive定义的响应式数据中的某些数据
*/
watch([()=>person.name,()=>person.age],(newValue,oldValue) => {
console.log('person中的name或者age变化了',newValue,oldValue)
},{immediate:true,deep: false})
// 特殊情况
watch(()=>person.job,(newValue,oldValue) => {
console.log('person的job变化了',newValue,oldValue)
},{deep: true}) // 由于监听的是reactive对象中的某个属性,所以deep配置有效
6. watchEffect函数
watch的套路是:既要指明件事的属性也要指明监视的回调。
watchEffect的套路是:不用指明监视那个属性,监视的回调中用到哪个属性就监视哪个属性。
有点像computed,但computed注重计算出来的值(回调函数的返回值),所以必须写返回值;而watchEffect更注重过程(回调的函数体),所以不用写返回值。
// watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(() => {
const x1 = sum.value
const x2 = person.age
console.log('watchEffect配置回调执行了')
})
7. 生命周期钩子
只有两个更名,其余均继承vue2:
beforeDestroy => beforeUnmount
destroyed => unmounted
Vue3也提供了Composition Api形式的生命周期钩子,与vue2中的生命周期对应如下:
beforeCreate = setup()
created = setup()
beforeMount = onBeforeMount
mounted = onMounted
beforeUpdate = onBeforeUpdate
updated = onUpdated
beforeUnmount = onBeforeUnmount
unmounted = onUnmounted
8. 自定义hook函数
hook本质就是一个函数,把setup函数中使用的composition API进行了封装。类似于vue2中的mixin。自定义hook的优势:复用代码,让setup中的逻辑更加清楚易懂。
9. toRef
作用:创建一个ref对象,其value值指向另一个对象中的某个属性值。
语法:const name=toRef(person,'name')
。
应用:将相应事对象中的某个属性单独提供给外部使用时。
扩展:toRefs
与toRef
功能一致,但可以批量创建多个ref对象,语法:toRefs(person)
。
10. shallowReactive & shallowRef
shallowReactive: 只处理对象最外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理。
使用时机:
如果有一个对象数据,结构比较深,但变化时只是外层属性变化 => shallowReactive。
如果有一个对象数据,后续功能不会修改对象中的属性,而是生成新的对象来替换 => shallowRef。
11. readonly & shallowReadonly
readonly: 让一个响应式数据变为只读(深只读)。
shallowReadonly:让一个响应式数据变为只读(潜只读)。
使用时机:不希望数据被修改时。
12. toRaw & markRaw
toRaw:
将一个由reactive生成的响应式对象转为普通对象。用于读取响应式对象的普通对象,对这个普通对象的所有操作,不会引起页面更新。
markRaw:
标记一个对象,使其永远不会再成为响应式对象。例如复杂的第三方类库,不应被设置为响应式。当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
13. provide & inject
实现祖孙组件间通信。在父组件中国有一个provide选项来提供数据,孙组件有一个inject选项来开始使用这些数据。
// 祖组件
setup(){
let car = reactive({name:'奔驰',price:'40万'})
provide('car',car)
}
// 孙组件
setup(){
const car = inject('car')
return {car}
}
14. 响应式数据判断
isRef: 检查一个值是否为一个ref对象
isReactive: 检查一个对象是否由reactive创建响应式代理
isReadonly: 检查一个对象是否由readonly创建的只读代理
isProxy: 检查一个对象是否由reactive或者readonly方法创建的代理
15. 其他
fragment :组件不在需要有一个根标签,回自动包含在一个Fragment虚拟元素中,减少标签层级,减少内存占用。
teleport:将组件的html结构移动到指定的位置。
// 将mask移动至body上
<teleport to='body'>
<div class='mask' v-if='isShow'>...</div>
</teleport>
customRef: 生成自定义ref方法。
suspense:等待异步组件时渲染一些额外的内容,让应用有更好的体验。
异步引入组件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
使用Suspense包裹组件,并配置好default与fallback
<template>
<div class='app'>
<h3>我是app组件</h3>
<Suspense>
<template v-solt:default>
<Child />
</template>
<template v-solt:fallback>
<h3>加载中。。。</h3>
</template>
</Suspense>
</div>
</template>
三、todolist案例
接下来我们结合一个TODOLIST小案例学习vue3.0的开发:
基础页面版todo list案例 可点此查看
有数据状态:
无数据状态:
首先我们要全局安装最新的vue@cli文件(注:cli文件的版本和创建项目的版本不是一回事哦~小编在写这篇文章时候的@vue/cli 版本是4.5.13),然后通过vue create XXX 来创建自己的项目。
当项目创建成功后,我们就可以来写案例了,话不多说,直接上代码:
如图所示,这是小编创建的文件结构:
views/Home.vue
<template>
<div class="home">
<h2>程序员的自我修养</h2>
<Input @addTask='addTask'/>
<List v-if='taskList.length>0' @delItem='delItem' :taskList='taskList' @changeStatus="changeStatus"/>
<NoData v-else/>
</div>
</template>
<script>
// @ is an alias to /src
import { defineComponent,reactive,toRefs } from 'vue'
import Input from '@/components/input/Input'
import List from '@/components/list/List'
import NoData from '@/components/noData/NoData'
export default defineComponent({
name: 'Home',
components: {
Input,
List,
NoData
},
setup(){
const data=reactive({
taskList:[{
name: '学vue3.0',
isFinish: false
},{
name: '敷面膜',
isFinish: true
},{
name: '熬夜改bug',
isFinish: false
}]
})
function delItem(index){
data.taskList.splice(index,1)
}
function changeStatus(index){
data.taskList[index].isFinish=!data.taskList[index].isFinish
}
function addTask(task){
data.taskList=[{name:task,isFinish:false},...data.taskList]
console.log(data.taskList)
}
return {
...toRefs(data),
delItem,
changeStatus,
addTask
}
}
})
</script>
<style lang="scss" scoped>
.home{
width: 100vw;
height: 100vh;
h2{
width: max-content;
color: rgb(252, 145, 15);
margin: 0 auto;
}
}
</style>
Input子组件
<template>
<div class="nav">
<input type="text" v-model="inputValue" :placeholder="tip" @keyup.enter="add"><button @click='clearInput'>清空内容</button>
</div>
</template>
<script>
import {defineComponent,reactive,ref, toRefs} from 'vue'
export default defineComponent({
name: 'Input', // 组件名
props: ['addTask'], // 接收父组件参数
components: { // 注册组件
},
setup(props,ctx){ // 数据声明写在setup中
let tip = ref('请输入任务~')
const data=reactive({
inputValue: ''
})
function clearInput(){
// console.log(tip.value)
// console.log(data.inputValue)
data.inputValue = ''
}
function add(){
if(data.inputValue===''){alert('主人太皮了,不能是空内容哦~');return}
ctx.emit('addTask',data.inputValue)
clearInput()
}
return {
...toRefs(data),
tip,
clearInput,
add
}
}
})
</script>
<style lang='scss' scoped>
.nav{
width: max-content;
height: 50px;
margin: 20px auto 0;
input{
width: 200px;
height: 30px;
}
button{
width: 90px;
height: 37px;
border: 0;
color: #fff;
background: blue;
}
}
</style>
List子组件
<template>
<div>
<ul class="list">
<li :key='item.name' v-for='(item,index) of listArr'><input :checked='item.isFinish' type="checkbox" @click="check(index)"><p>{{item.name}}</p><p @click='del(index)'>删除</p></li>
</ul>
<p class="total"><span>总任务数:{{listArr.length}}</span><span>已完成:{{listArr.filter(val=>val.isFinish).length}}</span></p>
</div>
</template>
<script>
import {defineComponent, watch, ref, reactive,toRefs} from 'vue'
export default defineComponent({
name:'List',
props:['delItem','taskList','changeStatus'],
setup(props,ctx){
const data=reactive({
listArr:ref(props.taskList)
})
/**
* watch 第一个参数是你要监听的数值
* 第二个参数是回调函数,函数有两个参数,第一个是新值,第二个是老值
* 当watch监听reactive数据会存在拿不到老值的限制,如果想拿到老值做操作
* 则需要将数据声明为ref数据类型
* 当watch监听多个数据的时候,第一个参数为数组,数组里依次放入想要监听的
* 多个数据,函数的参数为多个数组,分别对应前方数据的新老值
* watch([a,b],([nA,oA],[nB,oB])=>{
* console.log(nA,oA,nB,oB)
* })
*/
watch(data.listArr,()=>{
data.listArr=ref(props.taskList)
})
// onBeforeUpdate(()=>{data.listArr=props.taskList})
/**
* 删除任务
* index 任务对应下标
*/
function del(index){
ctx.emit('delItem',index)
}
/**
* 切换是否完成
* index 任务对应下标
*/
function check(index){
ctx.emit('changeStatus',index)
}
return {
...toRefs(data),
del,
check
}
}
})
</script>
<style lang='scss' scoped>
.list{
width: 500px;
height: max-content;
margin: 0 auto;
padding: 0;
list-style: none;
border: 2px solid #000;
li{
width: 100%;
line-height: 30px;
border-bottom: 1px solid #000;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
p:last-child{
width: 40px;
height: 30px;
background: red;
color: #fff;
border-radius: 5px;
text-align: center;
}
}
li:last-child{
border-bottom: 0;
}
}
.total{
width: 500px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
}
</style>
NoData子组件
<template>
<div class='no-data'>
主人比较懒喔,快来定个小目标吧~
</div>
</template>
<script>
import {defineComponent} from 'vue'
export default defineComponent({
name: 'NoData',
})
</script>
<style lang='scss'>
.no-data{
width: 500px;
height: 300px;
border: 3px solid rgb(57, 160, 245);
background: url('../../../public/bg.jpg');
background-size: cover;
background-repeat: no-repeat;
background-position: center;
border-radius: 8px;
color: rgb(245, 46, 212);
text-align: center;
line-height: 300px;
font-size: 24px;
margin: 0 auto;
}
</style>
四、其它知识点
vue3.0快速创建页面基础架构
vbase
使用vue3.0结合vite体验更快速
npm inint vite-app <project-name>
cd <project-name>
npm install
npm run dev