vue3.0学习笔记

1,042 阅读9分钟

一.组合式api

以往组件选项卡痛点

通常一个业务逻辑会散落在不同的选项(生命周期、计算属性等等配合完成)中,复杂的组件会有多个业务逻辑,那么这些不同的业务员逻辑点都会散落在同一个的选项中,例:一个methods选项中可能会有完成不同业务逻辑的方法。
那么在代码阅读上就会很困难,往往一个逻辑的阅读需要在methods、mounted、watch、compouted中来回跳跃,尤其是每个选项内又有复杂且不同的逻辑块时

data(){
  return{

  }
},
methods:{
  // 获取用户列表
  getUserList(){
    // 100行代码
  },

  // 获取角色列表
  getRoleList(){
    // 100 行代码
  }
},
computed:{
  // 用户降序的列表
  orderUsers(){
    // 100行代码
  },

  // 角色分组列表
  RoleGroup(){
    // 100 行代码
  }
},
mounted(){
  // 获取用户列表动作
  // 50 行代码

  // 获取角色列表
  // 50行代码
}

像上面这个组件就是有用户和角色两个逻辑分散在各个选项中,想完成其中一个逻辑的分析阅读会很麻烦,若是比这更复杂的组件,阅读性可想而知

组合式api (拆分逻辑) setup

setup中运用各种函数创建datacomputed、生命周期钩子函数等,所以就可以将完成同一逻辑的各种属性封装到一个单独的模块中,然后在setup中把所有的模块汇总暴漏给组件。

避免使用this

setup的调用在datacomputedmethods被解析之前,所以在setup中无法直接通过this调用到其中的数据

外部调用setup中声明的数据

setup是个函数,将其中创建的响应式数据ref、计算属性computed等最终return返回就可以了,其效果和正常选项卡声明是一样的。

setup中api函数使用方法

1.创建响应式数据(同data选项卡)

  1. ref
  2. reactive
  3. toref
  4. torefs
import { ref, toRef, toRefs, reactive } from 'vue'
// 创建
const name = ref('张三')
const student = reactive({name:'张三'})
// 保持引用连接
const sName = toRef(student,'name')
const {sName} = toRefs(student)

return { name,student }

后面再对以上四个的作用及区别做详细讲解

2.注册生命周期钩子

  1. beforeCreatecreated:不需要在setup中使用,因为 setup 是围绕 beforeCreatecreated 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。 2.beforeDestroy改名为beforeUnmount 3.destroyed改名为unmounted
import { onMounted } from 'vue'
setup(){
  onMounted(()=>{})
}

1adbfeaf95651f8e329fdaf3f369854.png

3.计算属性

computed

import { computed,ref } from 'vue'

const stu = reactive({name:'张三',age:20})
const name = computed(() => stu.name)
const age = computed(() => stu.age)

return { stu, name, age }

4.监听

watch函数接受三个参数

  1. 一个想要侦听的响应式引用或 getter 函数
  2. 一个回调 3.可选的配置选项
import { ref, watch } from 'vue'

const counter = ref(0)
watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
})

5.访问props中的数据

setup接受两个参数:

  1. props
  2. context ( { attrs, slots, emit } )
import { toRefs, computed }

setup(props){
const { name } = props
const comName = computed( () => name)
return comName
}

props中的name发生改变时,comName并不会发生改变

ES6的解构会消除proxy的响应性,解构本身就是一种拷贝,自然就会失去和原本数据引用的连接,成为一个独立的数据,那么从vue层面来看,就像是失去了响应性

可以使用toRefs保留响应连接

import { toRefs, computed }

setup(props){
const { name } = toRefs(props)
const comName = computed( () => name.value)
return comName
}

computed会随着props中的name的改变而改变

6.emit抛出事件

利用setup第二个参数context
context包含attrsslotsemit三个属性

emits:['nameChange'],
setup(props,{emit}){
  emit('nameChange','张三')
}

7.元素ref引用

vue2.x

<div ref="myRef"></div>
this.$refs.myRef
// 如果元素是循环生成的,即多个同名的 ref时,$refs获取到的则是一个数组

vue3.x

<div ref="myRef1"></div>
<button @click="showRef"> showRef </button>
setup(){
 const myRef1 = ref()
 
 function showRef(){
   console.log(myRef1)
 }
 
 return { myRef1,showRef }
}

当多个同名的ref时,使用方式也发生了改变,在本文末有详细介绍。

8.调用路由对象

useRouteruseRoute

import { useRouter, useRoute } from 'vue-router'

setup(){
    const router = useRouter()
    const route = useRoute()
}

需要注意的是,必须在setup顶层使用,例如下面这种就会有问题

import { useRouter, useRoute } from 'vue-router'

setup(){
    function nameChange(){
        const router = useRouter()
        const route = useRoute()
        console.log(router) // undefined
        console.log(route) // undefined
    }
    
    return { nameChange }
}

9.调用vuex对象

vuex4.x版本是支持vue3的,目前在setup中使用vuex无法使用map辅助函数useStore

import { useStore } from 'vuex'
import { computed } from 'vue'

setup () {
    const store = useStore()
    const userName = computed(() => store.state.user.userName )
    return { userName }
  }

同样需要注意的是,必须在setup顶层使用,例如下面这种就会有问题

import { useStore } from 'vuex'

setup(){
    function nameChange(){
        const store = useStore()
        console.log(store) // undefined
    }
    
    return { nameChange }
}

ref、reactive、toRefs、toRef的区别以及作用

1.添加响应状态

1.ref

ref是将单独的(基本类型) 包装进一个有value属性的可变响应式对象中

<template>
  <div>{{ name }}</div>
  <div>{{ name.value }}</>
</template>

import {ref} from 'vue'

const name = ref('张三')
console.log(name.value)  // 张三

return {name,stu:{name}}

在模板中访问时,浅层次ref会被解包,无需.value。而嵌套的深层次结构则需要.value

2.reactive

reactivejs对象创建响应式状态

<template>
  <div>{{ stu.name }}</div>
</template>

import { reactive } from 'vue'

const stu = reactive( {name: '张三'} )
console.log( stu.name )  // 张三

return {stu}

区别:

ref运用与基本数据类型响应式数据的创建,reactive运用于对象响应式数据的创建。

为什么说ref运用基本类型,reactive运用对象?

const stu = ref({name:'张三'})
console.log(stu)

首先,上面的代码是没有问题的,最起码从语法上来讲,是允许的。我们先看一下会输出什么

c5ba67b26d53308dad25475aa3eb967.png
可以看到stu本身是Ref类型,没问题,但是.value的值却是Proxy。vue3的双向绑定的实现手法就是利用ES6的Proxy代理实现的,reactive内部也是使用同样的方法实现响应式状态的添加。
由此可以看出,若ref函数传入的是Object,其最终也是调用了reactive
另外,若reactive传入的是基本数据类型则不会具备响应性,数据发生更改,视图不会发生更改。
所以基本数据类型使用ref,复杂数据类型(Array的最终原型也是Object)使用reactive

2.保持响应连接

toReftoRefs

  <template>
    <div>{{ showAge }}</div>
    <button @click='changeAge'>changeAgeBtn</button>
  </template>
  
  setup: () => {
    const stu = reactive({
      name:{
        value:'张三'
      },
      age:24
    })
    let {age} = stu
    const changeAge = function(){
      age += 1
    }
    const showAge = computed(()=>{
      return '我的年龄是' + age
    })
    return { showAge,changeAge }
  }

虽然stu是使用reactive添加的响应式状态,但是ES6的解构会消除响应状态,即age只是将值拷贝了,但是消除了响应性,当调用changeAge改变age的值时,并不会触发computed以及视图的更新。

此问题只有解构出的是基本类型时会出现,若解构获取的是name。因为引用类型的特性,会指向stu,即会触发stu的响应状态,视觉上来看,相当于name保留了响应状态
可以使用toReftoRefs,将我们要解构的数据包装成ref,保留对源对象的引用连接及响应状态
修改上述代码

  <template>
    <div>{{ showAge }}</div>
    <button @click='changeAge'>changeAgeBtn</button>
  </template>
  
  setup: () => {
    const stu = reactive({
      name:{
        value:'张三'
      },
      age:24
    })
    let {age} = toRefs(stu)
    // let age = toRef(stu,'age')
    const changeAge = function(){
      age.value += 1
    }
    const showAge = computed(()=>{
     return '我的年龄是' + stu.age
     // return '我的年龄是' + age.value
    })
    return { showAge,changeAge }

此时改变age.value那么stu.age也会发生变化,无论它俩谁发生改变。都会触发computed以及改变视图

区别:

toRefs可以包装多个数据,但是不会创建不存在的property

const { name,age } = toRefs(stu)

toRef 指定某个property,即使property不存在,也会返回一个可用的ref

const name = toRef(stu,'name')
const addres = toRef(stu,'addres') // 即使不存在 也会返回一个带有value属性的对象

自定义事件 $emit

vue3多了一个emit选项卡,必须在其中显示定义要抛出的事件名,这将是区分自定义组件原生事件和自定义事件的关键所在

emit:['changeAge']

1.自定义组件v-model

与vue2.X最大的改变,就是组件可以绑定多个v-model了

vue2.x时的v-model

vue2.X时期,当一个封装好的组件有多个数据需要v-model这种双向数据流通需求时,我们大部分有三种选择:

  1. sync修饰符,使prop在子组件可更改。
  2. 利用model&emit&props实现自定义组件v-model(可选择默认事件名props,也可显示指定)
  3. 子组件内部对数据进行操作后,利用$emit抛出事件并携带参数,然后父组件捕捉该事件后,将获取的参数再次赋值给子组件定义的prop(自定义组件v-model的实现原理也是这样,只是vue帮我们写好了一个语法糖) 下面为自定义组件v-model和手动父子传值的实现 sub.vue
<template>
  <div>
    <!-- v-model -->
    <input type="text" :value="name" @input="emit('modelName',$event.target.value)">
    <!-- 另类v-model -->
    <input type="text" :value="name" @input="emit('changeAge',$event.target.value)">
  </div>
</template>
<script>
export default {
  props:{
    name:String,
    age:Number
  },
  model:{
    prop:'name',
    event:'modelName'
  },
}
</script>

parent.vue

<template>
  <sub v-model="name" :age="age" @changeAge="changeAge" />
</template>
<script>
export default {
  data(){
    return {
      name:'张三',
      age:20
    }
  },
  methods:{
    changeAge(param){
      this.age = param
    }
  }
}

vue3的v-model

1.修改v-model默认的事件名prop,并且不再需要model选项

parent.vue

<my-component v-model:title="parTitle"></my-component>

myComponent.vue

<input type:"text" :value="title" @input="$emit('update:title',$event.target.value)" />

props:{
  title:String
},
emit:['update:title']

子组件抛出update:prop事件,父组件绑定v-model时传参v-model:prop。就完成了指定propv-model。在emit选项注册自定义要抛除的事件,是因为native修饰符移除后,用来区分原生事件和自定义事件的。

2.多个v-model

在上面的基础之上,指定多个prop就可以了 parent.vue

<my-component v-model:title="parTitle" v-model:age="parAge"></my-component>

myComponent.vue

<input type:"text" :value="title" @input="$emit('update:title',$event.target.value)" />
<input type:"text" :value="age" @input="$emit('update:age',$event.target.value)" />

props:{
  title:String,
  age:Number
},
emit:['update:title','update:age']

v-model修饰符

vue3除了原先硬编码的那三个v-model修饰符外,增加了自定义修饰符 parent.vue

<my-component v-model:title.show="myText"></my-component>

myComponent.vue

props:{
  title:String,
  titleModifiers:{
    default: () => ({})
  }
},
emit:['update:title'],
methods:{
  emitValue( value ){
    if(this.titleModifiers.show){
      this.$emit('update:title', value)
    }
  }
}

修饰符的值在[prop]Modifiers命名的prop中获取,具体看上例:
title的修饰符show,在命名为titleModifiersprop中获取

二、script setup

script setup是组合式api基础上的语法糖
在3.0版本是实验阶段,随着3.2版本的更新,script setup正式宣布可以稳定使用。 具体内容参考:script setup官方文档

1.script setup与普通script的关系

script setup最终会被编译成setup()函数,在官方文档中有提到,一个组件可以有多个script块,和一个script setup块,其原因就是一个组件不可能同时存在两个同名的函数,也就是两个setup()函数。

在官方文档中script setup与普通script混合使用的描述中并不清晰,他们的确可以混合使用,但是在书写顺序上,普通的script要在script setup之上,不然就会报错,vue文档中的顺序是正确的,但是没有提到这一点。

另外script setup也会顶掉普通script中的setup()。普通script中的setup()并不会执行。其他选项卡内代码是互不影响的。

defineProps、defineEmits、defineExpose

它们不需要被导入!直接使用即可,遇到问题,解决方案看下方坑位

 他们俩是不需要
```js
<script></script>
<script setup></script>

其他细节改动

1.native修饰符移除

现在在子组件上触发未被emit选项中定义过的事件,都会被当作原生事件处理。

2.template v-for

现在建议将template v-forkey直接写在template标签上

3.多个ref

vue2.x

<div v-for="v in list" :key="v.key" ref="myRef"></div>

this.$refs.myRef[0]

会将相同的ref转化为数组获取
vue3.x

<div v-for="v in list" :key="v.key" :ref="setRef"></div>

setup(){
  let refList = []
  const setRef = el => {
    refList.push(el)
  }
  return { setRef }
}

现在是为其绑定一个函数,在函数中将dom灵活存储

坑位

1.自定义组件v-show无效

vue3template支持了多根节点,但是需要注意的是

v-show 作用在多根节点组件上时会失效,v-show需要一个顶级的根节点,来通过样式来控制显示隐藏,如果是多个根节点的话,显然是有问题的。v-if 可以正常使用,因为其直接控制的是组件的加载与否

2.script setup与普通script混合使用时,会顶掉普通函数内的setup()

3. defineProps/defineEmits/defineExpose is not defined

这是eslint的报错,只需要在eslint的配置项里(eslintrc.js),配上这三个的全局变量就可以了

globals: {
    defineProps: true,
    defineEmits: true,
    defineExpose: true,
  },

4.vue3.2更新的style新特性(使用js变量)问题

4d05587921d6576928b39372639368c.png 这个问题主要是由于vue文档的疏忽导致的,经过一段时间未看到vue有更改,所以就提一下吧。
vue3.2中,style可以使用<script>中声明的变量了,并且也是可以响应式更改的,具体的语法就是在js中声明变量,然后stylev-bind(nameColor)引用。问题在于响应性这里,文档中在介绍script setupstyle结合使用时,变量并没有采用reactiveref设置响应性,故如果采用文档中的代码,时不会响应更改的.
script setup中需要使用reactive等为数据设置响应性。

结束语

vue3的更改远不如此,本笔记仅记录使用层面的更改,强烈建议将vue2迁移vue3指南的文档详细看一遍。 v3.cn.vuejs.org/guide/migra…