Bootstrap

Vue3.0

一、基础简介

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')
应用:将相应事对象中的某个属性单独提供给外部使用时。
扩展:toRefstoRef功能一致,但可以批量创建多个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

;