不像话的Vue3初体验总结

1,006 阅读7分钟

先放大佬镇个场子

IMG_20220228-200207458.png

官方文档确实是个好东西,隐约记得之前在哪儿看过一句话,判断一个工具好不好用,可以去看它文档写的明不明白。我觉的还是有道理的,自己学习过程中也有所感悟,每看一遍往往都能有新的体会。相比其他文档,Vue文档的中文基因又是更重一点,这不赶紧看还留着过年吗[doge]。 Vuejs.org的最新版文档阅读起来给我的感觉写的很易读易懂 文档还提供了一个选项式/组合式切换开关,可以对比两种不同方式的实现,也很不错👇 IMG_20220301-160302681.png

这既不是对框架原理实现的学习,也不是针对某一特性的深度剖析,只是一个使用了几个月Vue2.X跑来读Vue3.X,然后从新增和修改两方面记录的Vue3初体验😬

由于缺乏大量的实践,本文会将有限的实践过程对常用的API存在的一些避坑的地方一起列出来。

📃新特性

组合式API

📎文档地址

  • 改进

    Vue2组件代码结构

    <template></template>
    
    <script>
    export default {
      components:{},
      props: {},
      data: {},
      computed: {},
      methods: {}
      //...
      //暴露的选项
    }
    </script>
    <style></style>
    

    📍弊端: 代码按照各个选项罗列,而不是各个功能逻辑组成,一个功能逻辑可能由几个选项中的代码组成,造成代码逻辑上很分散,代码庞大时阅读性也会很差; 使用minmix也可以做类似抽离的工作,但它其实并不是真正的抽离,不能单独执行抽离的逻辑,还是要先混入,这就带来可能存在的变量命名冲突、使用多个的话还会有不清楚来源等问题,相比于组合式API,它更像是提供代码层面抽离的功能

    组合式API: 所谓组合式,它就是通过的提供setup 选项,将这些新增的ref响应式API、watch传统的选项、onMounted生命周期钩子等函数API组合起来,可以实现将某些逻辑组合抽离的目的。这一点就跟React Hooks有上那么一点类似了,Vue自己也说这确实是灵感来源之一,而且也做了一些说明,哈哈哈哈。哦,还有一点就是,这种基于函数组合的写法可以更好的结合TS使用类型推断。 使用组合式API之后,代码的结构就可能是这样的

    <script setup>
    //这里就可以借用新增的API来写逻辑代码
    //当然,我们可以将其抽离成单独的`.js`文件
    </script>
    <template>
    //这里正常写标签元素
    //<script>中定义的变量、函数及通过导入的内容都是暴露给模板可以直接引用的
    </template>
    <style>
    //样式也一样
    </style>
    
  • setup

    1. setup

      使用选项式,接受propscontext两个参数,在组件创建前执行,return返回值暴露在组件中,

      • Props

        props 中的值为响应式且只读,可使用toRefs() 进行解构,同ref()返回一个包含value 属性的普通对象

        image-20220211160345452.png

        setup(props){
            const { title } = toRefs(props)
            const title = toRef(props, 'title')
        }
        

        当可选属性为空时toRefs()不会为其创建ref,

        可以用toRef()为其同样创建ref

      • Context

        context也是一个对象,

        image-20220211162908921.png

        可以在setup 中暴露 attrs、slots、emit、expose

        expose 用于子组件通过模板ref属性向父组件暴露属性方法 不同于2.X可以通过给子组件添加ref属性就可以访问子组件的数据,3.X中只能访问子组件主动通过expose暴露的数据

        //子组件 HelloWorld.vue
        <script setup>
        defineExpose({
           hwName: "这是子组件的name",
           hwFunc() {
             console.log("这是子组件的function")
           }
        })
        </script>
        
        //父组件
        const hwRef = const ref(null)
        <HelloWorld ref="hwRef" msg="You did it!" />
        
    2. 生命周期钩子

      通过导入onX 注册,

      image-20220211114914561.png

      钩子函数通过接受一个回调函数,在组件调用时被执行。

    3. watch 响应式更改

      同样提供watch 函数来达到组件中watch 设置侦听器的目的,

      watch(counter, (newValue, oldValue) => {
        //newValue、oldValue 分别时更新后新旧值
      })
      //接受三个参数:
      //1.想要侦听的响应式引用或getter函数
      //2.回调函数
      //3.可选配置项 {immediate:true, deep:true}
      

      watchEffect相比于watch,它没有明确指定的追踪源,自动收集传入函数的数据源依赖,依赖更新自动执行

      watchEffect(async () => {
        const response = await fetch(url.value)
        data.value = await response.json()
      })
      

      仅在可追踪同步代码中的依赖以及异步第一个await前的使用的依赖

    4. computedwatch ,提供组件外部使用计算属性的API

      const twiceTheCounter = computed(() => counter.value * 2)
      //传入回调函数,输出只读响应式
      
  • 使用

    在新版本中,我们可以通过借助<script setup>,也就是文档里说的单文件组合式API语法糖在单文件组件中使用,它跟选项式在prop、context的获取上略有不同,

    练习使用的3.2版本上,直接调用:defineProps、defineEmits、defineExpose、useSlots、useAttrs就可以,defineProps可以直接使用,useAttrs还要导入, 不过使用VSC的同学配合Volor插件(也是官方推荐),它的代码提示跟自动导入都非常好用,根本不需要操心这个,真的巨好用,比之前的vetur好太多了

    还有就是依旧延续上版本中选项式API的风格,借助setup()选项,虽然是过去版本,但是目前没有废弃的计划,会一直作为风格保留,这是不是好像在隔壁哪里见过👀


响应式声明

  1. 使用reactive()对对象类型进行响应式声明,由于proxy用于创建对象的代理(不再兼容IE11),替代之前的Object.defineProperty,可以实现对属性属性新增等变化的监听 所以它只对象类型的的变量有效

    const state = reactive({ count: 1 })
    
  2. 使用ref()创建响应式 它不受变量类型的限制,可以为任何类型创建响应式,

    const state1 = ref(2)
    

打印看下

IMG_20220308-235931124.png

返回值是一个带value属性的对象,保存的就是响应式变量的值, 当然如果是对象,对应的value属性值也会换成其代理

IMG_20220309-001656129.png

因为Proxy声明的响应式是作用在声明的对象上的,存在解构或者取值之后 失去响应式的问题,所以通过ref()来声明,它看起来就像额外包了一层的方式,不仅可以给引用类型,还可以给基本类型变量声明响应式,变相做到可以解构传值操作。

💡在模板中使用时,作为最外层属性时,可以自动解包;否则不可以

IMG_20220309-003717123.png

IMG_20220309-003743503.png

IMG_20220309-004053150.png

这时就需要手动取value值,或者借助解构然后直接使用

  1. 还有两个比较有用的API toRef()toRefs() 这两个API可以为响应式对象的属性分别创建ref(),并且保持对源响应式的连接,就是可以实现解构而不是失去响应式,在组合函数中可以很方便的使用; 区别就是

IMG_20220310-000435334.png

IMG_20220310-000631583.png 源对象属性不存在时,前者会返回一个valueundefined可用的ref,而后者直接返回undefined


可组合函数

在Vue2中我们就经常做的复用要么是抽离无状态的工具函数,要么是通过导入.vue文件组件来复用一些组件(SFC),相比于以往拥有完整html/js/css结构的SFC,结合vue新导出的函数API,我们可以实现某一部分有状态处理逻辑的抽离 其更像是把某个单一功能的SFC的<script>部分抽离出来复用,但是又不要html/css,使用起来就像这样

IMG_20220305-175104368.png

IMG_20220305-175205127.png 可组合函数也是建议以use来开头命名,是不是感觉又好像好眼熟🙊。


Teleport

内置组件,可以将组件渲染在DOM中的指定节点位置

<teleport to="#endofbody">
    <></>
</teleport>
//将组件渲染到id为endofbody的组件下

💡HINT:

  • 组件间层级关系依旧保留,只是最终DOM结构变化
  • to指定的组件必须在该组件之前渲染,否在找不到改组件;
  • 一个目标有多个传送,按顺序渲染

片段

组件可以包含多个根节点,就像这样

<template>
    <header></header>
	<main v-bind="$attrs"></main>
	<footer></footer>
</template>

但是对于非Props的attrs属性需要显示指定

IMG_20220315-160405912.png


触发组件选项

就是新增了 emit 选项,就像是props 一样对其进行校验和触发 在组件中,使用选项 emits: [ '', ''] 或者API defineEmits() 来接收自定义事件,下图方式使用,跟props基本类似

IMG_20220316-102042771.png

HINT: 由于移除了.native修饰符,所以所有未在选项中声明的事件监听器都会默认加到$attrs中,原生事件会绑定到根节点上 所以需要在组件中正确声明所触发的事件,否则容易造成多次触发事件

相关的一点变化: $listeners -> $attrs

在3.X中移除了$listeners,将事件监听放到了$attrs中,变成了以on开头的attribute 包含class、style在内的所有attribute

<MultiRootComVue 
  id="mrcv"
  style="color: red"
  v-model:first-name="firstName" 
  v-model:last-name="lastName" 
  @myEvent="myEvent" 
  :react-status="reactStatus"
>
</MultiRootComVue>

//MultiRootComVue.vue
子组件获取attrs
1.使用 useAttrs()
2.模版中使用 $attrs

IMG_20220327-205229910.png

☝️相比于2.X的变更

📎旧版3.X文档地址

全局API

在2.X中,Vue是没有真正类似app这种概念,现状就是使用Vue提供的类似Vue.directive 全局API进行的配置项会作用到所有通过new Vue() 创建的根实例, 通常凡是这种会作用全局的东西,基本都会带来一些问题,3.X针对这些做了一些改变。

首先是可以通过createApp()来创建应用实例,然后是将那些影响实例行为的API移动到了实例下,

import { createApp } from 'vue'
import MyApp from './MyApp.vue'

const app = createApp(MyApp)
app.mount('#app')

IMG_20220324-103159829.png

这些API的变动带来在诸如创建挂载应用实例、安装插件等使用方法上的改变,具体可以看这里

全局API TreeShaking: 这也是全局API的调整带来的变化之一 2.X中那些全局API默认挂在Vue对象上,因此无论你是否使用,它都在哪里,也无法通过打包工具将实际没有使用的部分shake掉;

而重构之后的全局API通过具名导入,

IMG_20220325-113517009.png import { nextTick } from 'vue'

未导入不能直接调用,从而来优化打包(仅ES构建版本,UMD仍然包含所有API)


模板指令

旧版文档给出的更改:

IMG_20220327-120220763.png

  • v-model指令的变化

    1.自定义组件修改默认绑定值:value -> modelValueinput -> update:modelValue 2.移除.syncmodel选项,v-model:variable可带参数 3.同一组件可使用多个且可自定义修饰符

  • .native

    3.X中取消该修饰符,对于未显示定义的事件,且子组件未设置inheritAttr: false

    <my-component
    v-on:close="handleComponentEvent"
    v-on:click="handleNativeClickEvent"
    />
    
    //MyCompoent.Vue
    <script>
    export default {
    emits: ['close']
    }
    </script>
    

    Vue会将其作为原生事件监听器添加到根元素上


琐碎但需要注意⚠️的

  • data 选项只接受函数,不可直接赋对象

image-20220217171351277.png

data(){
    return  {}
}
  • mixin 合并行为变为浅层次合并,不会深入对象去遍历合并不同属性

  • 移除的一些API

    • 2.X中的数字键码修饰符不再可用
      ❌ <input v-on:keyup.13="submit" />
      
      <!-- Vue 3 在 v-on 上使用按键修饰符 -->
      ✅ <input v-on:keyup.page-down="nextPage"> 
      <!-- 同时匹配 q 和 Q -->
      ✅ <input v-on:keypress.q="quit">
      
    • 移除实例方法$on$off$once,不再支持通过使用Vue实例及事件触发器API来创建 事件总线 ,主要还是考虑到全局事件监听带来的维护问题 还是支持使用,如果要使用的话,可以使用mitt.js外部库
      • prop、event父子传递
      • provide/inject间隔传递
      • 状态管理库
      • 移除filter过滤器选项,不过我好像也从来没使用过😂

ending:在对组内做学习分享的时候,我是很没啥底气的,因为我知道这其实比较粗糙而且也都是表层的东西,但是看到大家的鼓励其实还是很受鼓舞的,很开心,嗯,就这样。 其实自己最近一直都很低落,因素来自很多方面,分享一首让我听完感觉很舒畅的歌在这里,文档可以先不看歌都去听! Harunohi - aimyon

image.png