Vue3学习手册-入门篇

5,221 阅读10分钟

简介

当你阅读完引子的时候,在我看来你对一些Vue3.0的新特性已经回迫不及待了吧。那么就跟随一起来看下充满魔法的CompositionAPI究竟充满哪些神奇的体验呢。下面诺列出官方三种体验Vue3.0的方式给大家选择。我个人推荐Vite它提供了开箱即用一些配置,和非常快的速度。

  • VueCli:官方脚手架,通过vue add vue-next既可体验。
  • Vite:同样为官方提供,通过yarn create vite-app <project-name>
  • vue-next-webpack-preview:官方提供的预览版webpack脚手架

本次文章所有的案例都使用Vite来进行。

Vue开发手册系列

  • Vue开发手册-引子篇 查看
  • Vue开发手册-入门篇 查看

初始化项目

# 初始化项目
yarn create vite-app vue3-dev-example

# 进入项目
cd ./vue3-dev-example

# 安装依赖
yarn

# 启动项目
yarn dev

当出现如下界面,代表着项目成功运行,在浏览器通过任意一个serve地址都可以访问到Vue的内容。

配置Typescript

使用TypeScript是为了更好的进行类型推导,也有非ts版的代码。所以这里的TS,我就尽可能的简单配置了。

先将main.js 改名为main.ts ,在index.html的引入中,名字对应的也改掉

tsconfig.json

通过以下命令可以快速的生成tsconfig.json,使用命令之前,确保在本地安装了typescript不然将无法使用tsc命令。

# 生成json配置文件
tsc --init

类型声明

现在项目已经准换为了一个ts项目,但随后发现,一些module的引入似乎出现了问题。这是因为还没有定义模块声明,找不到引入的是一个什么东西。在src下创建一个vue.d.ts的声明文件。

将所有.vue的文件进行类型模块声明,那么所有.vue文件,就会对导入的vue文件进行类型声明了。

// vue.d.ts edit
declare module '*.vue' {
  import { ComponentOptions } from 'vue';
  const componentOptions: ComponentOptions;
  export default componentOptions;
}

随后在tsconfig.json中导入配置

# tsconfig 
"include": [
  "src/**/*.ts",
  "src/**/*.tsx"
],
"exclude": [
  "node_modules"
]

随后将项目启动,就会发现

Fragment

新特性之一,在Vue3.0之前,template必须用一个节点将内容包裹起来。因此,templatea组件有且只能由一个根节点。那么在很多时候,这样其实会造成很大的浪费,明明我的组件只要两个盒子,却因为拆分出来又要多套一层,无形中就增加了DOM节点的深度,间接消费了性能。

<template>
  <div>
    vue template 2.0 template
  </div>
</template>
<template>
  <div>vue3</div>
  <div>template fragment</div>
</template>

setup

在Vue3中将Options API大部分属性都放到了setup中,你可以看成是一个集合体。需要注意的是,setup本身只会在初始化的时候执行一次,也可以看作是一个生命周期的钩子。首先会将props进行初始化后,随后开始执行setup函数,setup函数只会在组件初始的时候调用一次。如果组件初始化后,那么setup就不会在执行了。

<script lang="ts">
export default {
  name: 'App',
  props: {},
  setup (props, context) {
    console.log('setUp ... ')
  }
}
</script>

需要注意的是,只有在setup 中主动暴露出去的内容才能被template 所使用,如果不暴露的话,会发出一条该变量未找到,没有初始化的提示信息。使用以下实例,当注释return中的msg的时候,在template是无法使用的。

<script lang="ts">
export default {
  name: 'App',
  props: {},
  setup (props, context) {=
    const msg: string = '我是message'
    return {
      // msg
    }
  }
}
</script>

响应式定义

Composition API中对于基础类型和引用类型的响应式是分开来的,通过refreactive来定义不同的数据响应式,在使用上也是存在很大的区别。下面通过一个实例通俗易懂的给大家

  • 基础类型:ref
  • 引用类型:reactive
<template>
  <div>hello vue composition API ...</div>
  <p>msg:{{msg}}</p>
  <ul>
    <li v-for="value, key in person">
      {{key}}--{{value}}
    </li>
  </ul>
  <button @click="updateMsg('update after click...')">更新msg</button>
  <button @click="updatePerson({ name: 'ZhangSan', age: 88 })">更新Person</button>
</template>

<script lang="ts">
import { reactive } from 'vue';
import { useRef, useReactive } from './hooks/useMod';

export default {
  name: 'Home',
  props: {},
  setup (props, context) {
    // 获取msg
    const { msg, updateMsg } = useRef()
    // 获取person
    const { person, updatePerson } = useReactive()
    return {
      msg,
      updateMsg,
      person, 
      updatePerson
    }
  }
}
</script>
// @wang useMod
import { Ref, ref, reactive } from 'vue';

export function useRef() {
  // 创建默认ref
  const msg: Ref<string> = ref<string>('define value');
  // 更新msg信息
  function updateMsg(text: string): void {
    msg.value = text
  }
  return {
    msg,
    updateMsg
  }
}

export function useReactive() {
  type IPerson =  {
    name: string
    age: number
  }
  const person: IPerson = reactive<IPerson>({
    name: 'wangly',
    age: 22
  })
  // 更新person信息
  function updatePerson(newPerson: IPerson): void {
    person.name = newPerson.name
    person.age = newPerson.age
  }
  return {
    person,
    updatePerson
  }
}

通过以上实例,分别对一个字符串和一个对象进行了响应式的声明,通过button事件对其进行了更改,来测试在页面上数据是否会跟随响应而改变。

ref

当我们使用ref 初始化基本类型响应式时,当初始化后,需要修改内容的时候,必须使用value属性进行更改。只有通过.value修改的内容才是有效的响应式内容。当然,在template使用的时候,不需要带.value属性来获取和修改某值。

const msg: Ref<string> msg = ref<string>('')
// error
msg = 'update value ....'
// god
msga.value = 'update value ....'

reacitve

reactive初始化的引用类型依旧和以前一样操作,并没有太多的变化,千万不要尝试解构操作,这么做,它会丧失对它的响应式更新,如果需要解构的话,官方提供了两个API对其进行操作。

  • toRef:将Reacitve某个成员将其包裹成为一个ref的响应式
  • toRefs:将Reactive所有成员都包裹成为响应式。
type IPerson =  {
    name: string
    age: number
  }
  const person: IPerson = reactive<IPerson>({
    name: 'wangly',
    age: 22
  })
// toRef
  const name = toRef(person, 'name') => name为一个ref()
  
// toRefs
  const paramsReactive = toRefs(person) => { ...所有成员都是ref  }

判断响应式

为什么会提供响应式的判断?因为在composition API 中,变量的响应式是存在丢失的,同时也是让开发者能够主动的去感知这个状态它究竟是一个普通的变量还是一个响应式的变量。

  • isRef 判断一个变量是否被ref包裹代理
  • isReactive 判断一个变量是否被reactive包裹代理

依旧是第一个小实例,通过useMod.ts中导出的两个内容来做一个小实例。两个API还是非常好理解的

 
    // 获取msg
    const { msg, updateMsg } = useRef()
    // 获取person
    const { person, updatePerson } = useReactive()
    const personList = toRefs(person)
    console.log(`msg是否是ref代理的变量`, isRef(msg))
    console.log(`person是否是ref代理的变量`, isRef(person))
    console.log(`msg是否是reactive代理的变量`, isReactive(msg))
    console.log(`person是否是reactive代理的变量`, isReactive(person))

只读变量

这个是composition API新增的一个东西,它的作用从它的名字就可以看得出来,它的作用主要是生产一个只能被读取而不可以进行更改的变量,如果说,你强行对readonly生成的值进行更改,会收到意外的警告。当你的变量不想被意外的更改的时候,建议可以将其使用readonly包裹。

<script lang="ts">
import { reactive, toRefs, isRef, isReactive, readonly } from 'vue';
import { useRef, useReactive } from './hooks/useMod';

export default {
  name: 'App',
  props: {},
  setup (props, context) {
    // 获取msg
    const { msg, updateMsg } = useRef()
    // 获取person
    const { person, updatePerson } = useReactive()
    const personList = toRefs(person)
    // 将msg包装成只读
    const readOnlyMsg = readonly(msg)
    msg.value = '2222'
    readOnlyMsg.value = '111'
    return {
      msg,
      updateMsg,
      person, 
      updatePerson
    }
  }
}
</script>

判断只读变量

和响应式的情况相同,对于readonly 生成的变量也配套了一个判断函数``isReadonly`,传递一个值,会鉴别是否是一个可读属性。

const readOnlyMsg = readonly(msg)
console.log('msg是否是可读的', isReadonly(msg))
console.log('readOnlyMsg是否是可读的', isReadonly(readOnlyMsg))

计算属性

composition API对计算属性改动很小,它和Methods方法一样,定义后暴露给template就能够使用了,基本和以前没有什么大的变化,只是从以前的声明被computed包裹住了罢了。return返回的内容就是计算属性最后的值了。

<template>
    <span v-for="item in list" :key="item">{{item}}</span>
    <br>
    <span v-for="item in computedList" :key="item">{{item}}</span>
</template>

<script lang="ts">
import { reactive, computed, ComputedRef } from 'vue';
import { useRef, useReactive } from './hooks/useMod';

export default {
  name: 'App',
  props: {},
  setup (props, context) {
    const list: Array<string> = reactive<Array<string>>(['my ', 'name ', 'is ', 'wangly'])
    // 计算属性
    const computedList: ComputedRef<string>  = computed((): string => {
      return `我是计算属性`
    })
    return {
      list,
      computedList
    }
  }
}
</script>

监听器

Vue3中提供了和watch类似的一个方法函数,主要是用来做数据监听的记录,方便开发者对某个变量的变化进行一个组件的检测。但是对比Vue2watch 新的composition API的话使用方式并没有太多的变化 。支持同时进行多个变量的监听操作。

监听单个变量

setup() {
    const count: Ref<number> = ref<number>(0)
    setTimeout(() => {
      count.value ++
    }, 5000)
    watch(count, (newVal, oldVal) => {
      console.log('变化后的值', newVal)
      console.log('变化前的值', oldVal)
    })
    return {
      count
    }

监听多个变量

只需要在第一个参数中,将需要监听的变量都放在数组中,全部推给watch,那么你将根据数组中的变量依次获取到更改后和更改前的结果集

<script lang="ts">
import { ref, onMounted, provide, watchEffect, Ref, watch } from 'vue'

export default {
  name: 'Home',
  props: {},
  setup() {
    const count: Ref<number> = ref<number>(0)
    const sum: Ref<number> = ref<number>(100)
    setTimeout(() => {
      count.value ++,
      sum.value = 1000
    }, 5000)
    watch([count, sum], (newVal, oldVal) => {
      console.log('变化后的值', newVal)
      console.log('变化前的值', oldVal)
    })
    return {
      count
    }
  }
}
</script>

关于Reactive

关于对象的监听就变得非常有意思了,需要对你进行一个函数包裹才能被watch进行识别,同样的,也支持多个属性的监听,但是看起来其实也蛮怪的。

export default {
  name: 'Home',
  props: {},
  setup() {
    const { person } = useReactive()
    setTimeout(() => {
      person.name = '嬴政'
      person.age = 1000
    }, 5000)
    watch([() => person.name, () => person.age], (newVal, oldVal) => {
      console.log('变化后的值', newVal)
      console.log('变化前的值', oldVal)
    })
    return {
    }
  }
}
</script>

组合监听

什么是组合监听,意思是说,你可以进行多组变量在一个watch下,那么就将传递进去的多组数据进行一个监听,对于监听结果集,其实属性堆是一一队列的。这样就可以将每一个数组元素都作为一个独立的监听来进行数据的处理。

<script lang="ts">
import { ref, onMounted, provide, watchEffect, Ref, watch } from 'vue'
import { useReactive, useRef } from './hooks/useMod'

export default {
  name: 'Home',
  props: {},
  setup() {
    const { msg } = useRef()
    const { person } = useReactive()
    setTimeout(() => {
      person.name = '嬴政'
      person.age = 1000
      msg.value = 'update ...'
    }, 5000)
    watch([() => person.name, () => person.age, msg], ([foo, bar, msg], [prevFoo, prevBar, prevMsg]) => {
      console.log(foo, bar, prevFoo, prevBar, msg, prevMsg)
    })
    return {
    }
  }
}
</script>

新的生命周期

新的生命周期优化了命名规则,变得更加的好识别了。

生命周期名称作用
setup作为初始化组件时候的一个生命周期,作用类似于以created和beforeCreated的结合体
onBeforeMount对比2.x的beforeMount生命周期,挂载DOM前
onMounted对比2.x的mounted声明周期,此时可以访问到DOM了
onBeforeUpdate对比2.xbeforeUpdate,组件产生更新之前的钩子
onUpdated对比2.xupdated,组件发生更新时的钩子
onBeforeUnmount对比2.xbeforeDestroy,组件卸载之前的钩子
onUnmounted对比2.xdestroyed,组件取消挂载销毁的钩子

一系列的新钩子都在前面加上了on的修饰符,作为一个标注。同时将状态也分为了挂载,更新,卸载三个阶段。改变并不是非常大,对于属于生命周期的同学来说,无非就是熟悉下新API的使用罢了。没有多少的学习消费。

import { reactive, watch, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';
import { useRef, useReactive } from './hooks/useMod';

export default {
  name: 'Home',
  props: {},
  setup (props, context) {
    // 获取msg
    const { msg, updateMsg } = useRef()
    // 获取person
    const { person, updatePerson } = useReactive()
    
    onBeforeMount(() => {
      console.log('onBeforeMount')
    })
    onMounted(() => {
      console.log('mounted!')
    })
    onBeforeUpdate(() => {
      console.log('onBeforeUpdate')
    })
    onUpdated(() => {
      console.log('updated!')
    }) 
    onUnmounted(() => {
      console.log('unmounted!')
    })

新的依赖注入

新的依赖注入功能非常强大。在旧版本中,provideinject两兄弟承担的角色非常的低调,属于路人甲一类的。然而在Vue3 中,却有屌丝逆袭高富帅的味道了。增强了通信功能后,它对开发者在制作某些插件注入的时候更加的方便。甚至在一定程度上来说,它们就是为了多功能特色的插件提供的一个刻刀,开发者可以合理的去进行Plugin的开发,有了更多的选择。

新的Ref DOM

旧版本中,this.$refs可以获取到当前节点中所有的ref属性挂载的值。其实,这样对开发者感知的反应是存在问题的。那么this.refs中,我们并不知道很好的感知this.refs中,我们并不知道很好的感知`this.refs中究竟有哪些东西,大部分时间都在怀疑我的refDOM究竟有没有拿到 。在composition API 中,界面所有的ref都是通过用户主动暴露出去的ref响应式来进行生成的,那么久从一个Vue暴露出的行为转换成为了开发者自己的行为。从本质上进行了变化。配合依赖注入可以获取子节点下的节点ref属性,从而拿到子组件绑定refsetup`中暴露出的内容。进行一些“主动”操作。

<template>
  <p ref="title"></p>
</template>

<script lang="ts">
import { ref, onMounted } from 'vue'

export default {
  name: 'Home',
  props: {},
  setup () {
    const title = ref(null)
    onMounted(() => {
      console.log(title)
    })
    return {
      title
    }
  }
}
</script>

nextTick

vue2.0中,我们用作于判断当前template dom是否挂载完毕都是使用this.$nextTick 来进行的,在Vue3.0nextTick拆分出来一个单独的API的供大家使用,这样当你不适用它的时候,它也是会被tree shaking 掉的,避免页面中存在多余无用的API。

<script lang="ts">
import { nextTick } from 'vue'

export default {
  name: 'Home',
  props: {},
  setup() {
    nextTick(() => {
      console.log('dom已经挂载')
    })
  }
}
</script>

后话

入门篇的内容实在是较浅。composition API 为了让用户过度到新版本做了很多东西,在API语法这一块其实并没有大的改动,只是换了一定的方式去实现一样。就像function 和箭头函数。虽然它们的形式不一样,但是本质上依旧是一个函数。万变不离其中,所以有一定基础的同学建议直接看官方文档,官方文档介绍了绝大部分的一个更新内容。如果你阅读了本文章后大致上了解一个印象概念,在细细的看下官方给出的compositionAPI文档,会发现上手非常的快。

在进阶篇中,会逐步的将新指令,逻辑拆分,合并后的v-model,还有某些高级特性讲一遍。最近因为辞职,所以一部分精力放在了复习面经上面。更新的稍微有点慢了。也希望如果文章对你有用,给我点一个小手赞一下吧。

对Vue3的一些基本语法算是一个随笔的东西,建议看完后再去过官方文档,衔接无缝的话,Vue3语法并不是很难,只是需要从以前的编程方式中进行转换一下。