Vue3新增特性

3,460 阅读14分钟

本Vue3笔记是根据jspang.com视频教程完成,全程由ts编写

前置知识

  • Vue2.x
  • TypeScript
  • npm

Vue3项目的搭建

这里使用 Vue-cli 搭建项目

首先全局安装 vue-cli ,版本需在 4.5 以上

npm install -g @vue/cli

找到一个空目录下,打开终端执行 vue create vue3-1 命令 

出现如下对话,这里选择手动自定义模板,因为我们要使用 TypeScript

选择完 手动自定义模板 之后,出现如下对话,这里我们选择 TypeScript ,按 空格 进行选择,按 回车 执行下一步

出现如下对话,我们选择 3.x 的版本,然后 回车 执行下一步

出现如下对话,是否选择编译为 JSX 语法,这里我们选择 No

出现如下选择,直接选择 No 或者 第一项 即可

这里出现选择 单独写入文件 还是写入 package.json ,这里我们选择第一项单独写入文件

出现如下对话,询问是否选择将设置保存起来,这里我们选择 No 

出现如下即表示项目项目构建完成

执行提示命令  启动项目,至此 vue/cli 搭建 vue3 项目就完成了

cd vue3-1
npm run serve

使用vue3自动模板详情请见【前端靓仔】

初始目录结构

node_modules

所有的项目依赖包

public

公共文件夹

  1. favicon.ico
  2. index.html   入口 html 文件

src

所有写的代码

  1. assets  静态文件
  2. components  自定义组件
  3. App.vue  跟组件
  4. main.ts   入口文件
  5. shims-vue.d.ts    类文件/定义文件   定义一个文件让 ts 认识 vue

.browserslistrc

兼容作用,不同前端工具之间功能浏览器对node版本进行配置

.eslintrc.js

eslint的配置文件

.gitignore

配置不上传到git的文件

package.json

{  
    "name": "vue3-1",//项目名称  
    "version": "0.1.0",//项目版本
    "private": true,  
    "scripts": {    
        "serve": "vue-cli-service serve",//本地启动    
        "build": "vue-cli-service build",//打包    
        "lint": "vue-cli-service lint",//代码检测  
    },  
    "dependencies": {//生产环境    
        "vue": "^3.0.0-0"  
    },  
    "devDependencies": {//开发环境    
        "@typescript-eslint/eslint-plugin": "^2.33.0",    
        "@typescript-eslint/parser": "^2.33.0",    
        "@vue/cli-plugin-eslint": "~4.5.0",    
        "@vue/cli-plugin-typescript": "~4.5.0",    
        "@vue/cli-service": "~4.5.0",    
        "@vue/compiler-sfc": "^3.0.0-0",    
        "@vue/eslint-config-typescript": "^5.0.2",    
        "eslint": "^6.7.2",    
        "eslint-plugin-vue": "^7.0.0-0",    
        "typescript": "~3.9.3"  
    }
}

tsconfig.json

ts 的配置文件

src/main.ts 详解

import { createApp } from 'vue' //引入createApp挂载用来App
import App from './App.vue'//根组件
createApp(App).mount('#app') //创建App组件挂载到根节点

setup函数和ref函数

首先打开App.vue,将多余的代码都删除掉

<template></template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  name: 'App'
});
</script>

修改 template 为如下

<template>
  <div>
    <h2>欢迎来到XX课堂</h2>
    <p>请选择学习的语言</p>
    <div><button></button></div> <!--这里是需要循环生成的-->  
</div>
</template>

修改 script 为如下

<script lang="ts">
import { defineComponent,ref } from 'vue';//引入ref函数
export default defineComponent({
  name: 'App',
  //在vue2中使用的是 data(){return{}}   methods:{}   ...
  //在vue3中直接使用setup函数  将template中需要的事件、数据return出去
  setup(){
    const languages = ref(['html','css','javascript']);//定义所有的语言
    return {
      languages //并将数据return出去
    }
  }
});
</script>

然后修改 template 来循环出来 scriptreturn 的数据

<button v-for="(item,index) in languages" :key="index">{{index}}:{{item}}</button>

这时候就可以先预览一下页面内容

然后我们需要先在 template 中添加一行用来显示选择的内容

<div>你选择了【{{selectLan}}】语言</div>  <!--注意:这里有一个selectLan请接着往下看-->

然后在 scriptsetup 函数中添加方法和选择之后的值

setup(){    
    const languages = ref(['html','css','javascript']);    
    const selectLan = ref("");//选择的语言,起初让他为空
    //定义方法,接受一个参数为选择的下标,因为用的是ts,所以参数可以加一个类型注解
    const selectLanFun = (index: number) => { //注意:注解“冒号”后面需要添加空格    
      //重新赋值选择的数据  设置和获取ref值都需要加value
      selectLan.value = languages.value[index]     
    }    
    return {      
        languages,//暴露语言数组,用于在template中循环渲染      
        selectLan,//暴露选择之后的数据
        selectLanFun,//因为template中需要使用该方法,所以需要return出去 
    }  
}

然后给 template 中的 button 添加点击事件

<button 
    v-for="(item,index) in languages" 
    :key="index" 
    @click="selectLanFun(index)"
>{{index}}:{{item}}</button>

reactive函数

该函数主要是优化上一节的代码

  1. 主要优化 setup 函数中的 方法变量 都进行了混淆
  2. 优化了设置获取``ref值都需要用value的形式

首先我们先进行一个小小的改动

去掉引入的 defineComponent  ,并将暴露改成如下写法

import { ref } from 'vue'
export default {
    ...
}

检测之后没有报错程序可以正常运行

接下来我们优化 setup 内部

setup(){    //使用一个data来包装,最后只需要返回data即可
    const data = reactive({
      languages:['html','css','javascript'],//定义数组,template模板中循环需要
      selectLan:'',//被选中的语言
      selectLanFun:(index: number)=>{//选择语言的方法
        data.selectLan = data.languages[index]//这里就不需要使用value,但是赋值是给data下的数据赋值,需要加上data
      }
    })
    // const languages = ref(['html','css','javascript']);
    // const selectLan = ref("");//选择的语言
    // const selectLanFun = (index: number) => {
    //   selectLan.value = languages.value[index]
    // }
    return {
      data
      // languages,
      // selectLan,
      // selectLanFun
     }
  }

修改 template 中的数据,在前面加上 data. 

<div>
  <button 
    v-for="(item,index) in data.languages" 
    :key="index" 
    @click="data.selectLanFun(index)">{{index}}:{{item}}</button>
</div>
<div>你选择了【{{data.selectLan}}】语言</div>

因为我们这里使用了 TypeScript,为了使代码更加严谨,加上 ts 专属的的类型注解

什么是类型注解,指的是对一个变量或者参数进行数据类型的确定,不使用类型推断的形式(javascript自己判断数据类型)

我们在 export default 上面写一个接口的形式定义类型注解

interface DataProps{
  languages:string[];
  selectLan:string;
  selectLanFun:(index:number)=> void;
}

这样的话,我们在 setup 函数内部给 data 添加这个类型注解,则 data 下面的属性都具有确定的数据类型

const data: DataProps = reactive({
    languages:['html','css','javascript'],
    selectLan:'',
    selectLanFun:(index: number)=>{
      data.selectLan = data.languages[index]
    }
})

现在我们不想要在 template 中使用 data 来取值怎么操作呢,请接着看下篇

toRefs函数

首先,我们引入 reactive 的时候引入 toRefs

import { reactive,toRefs } from 'vue';

修改 setup 函数里面的写法,其实就是给 data 使用 toRefs 进行包装

const data: DataProps = reactive({
    languages:['html','css','javascript'],
    selectLan:'',
    selectLanFun:(index: number)=>{
      data.selectLan = data.languages[index]
    }
})
const refData = toRefs(data);

这个时候 return 的时候就需要返回 包装之后的 datarefData,并且需要使用扩展运算符来展开

return{
    ...refData
}

最后去掉 template 中的 data,经检查,发现并无报错,程序可以正常使用即可

vue3的生命周期

  • setup(): 开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method

  • onBeforeMount(): 组件挂载到节点上之前执行的函数。

  • onMounted(): 组件挂载完成后执行的函数。

  • onBeforeUpdate(): 组件更新之前执行的函数。

  • onUpdated(): 组件更新完成之后执行的函数。

  • onBeforeUnmount(): 组件卸载之前执行的函数。

  • onUnmounted(): 组件卸载完成后执行的函数

  • onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。

  • onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。

  • onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数(以后用到再讲,不好展现)。

使用生命周期函数之前,须先引入( setup 除外 )

import {
  onMounted,
  onBeforeMount,
  onBeforeUpdate,
  onUpdated,
} from "vue";

export default {
    name: "App",
    setup() {
        console.log("1-开始创建组件-----setup()");
        const data: DataProps = reactive({
          language: ["html", "css", "javascript"],
          selectLan: "",
          selectLanFun: (index: number) => {
            data.selectLan = data.languages[index]
          },
        });
        onBeforeMount(() => {
           console.log("2-组件挂载到页面之前执行-----onBeforeMount()");
        });
        onMounted(() => {
          console.log("3-组件挂载到页面之后执行-----onMounted()");
        });
        onBeforeUpdate(() => {
          console.log("4-组件更新之前-----onBeforeUpdate()");
        });
        onUpdated(() => {
          console.log("5-组件更新之后-----onUpdated()");
        });
        const refData = toRefs(data);
        return {
          ...refData,
        };
      }
    }
}

还可以在setup()函数之后编写Vue2的生命周期函数,代码如下

beforeCreate() {
  console.log("1-组件创建之前-----beforeCreate()");
},
beforeMount() {
  console.log("2-组件挂载到页面之前执行-----BeforeMount()");
},
mounted() {
  console.log("3-组件挂载到页面之后执行-----Mounted()");
},
beforeUpdate() {
  console.log("4-组件更新之前-----BeforeUpdate()");
},
updated() {
  console.log("5-组件更新之后-----Updated()");
},

Vue2.x 和 Vue3.x 生命周期对比

Vue2--------------vue3
beforeCreate  -> setup()
created       -> setup()
beforeMount   -> onBeforeMount
mounted       -> onMounted
beforeUpdate  -> onBeforeUpdate
updated       -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed     -> onUnmounted
activated     -> onActivated
deactivated   -> onDeactivated
errorCaptured -> onErrorCaptured

onRenderTracked()和 onRenderTriggered()钩子函数的使用

这两个钩子函数是Vue3.x版本新加的两个钩子函数,官方说是用来调试使用的,但是目前还没有给出具体的调试案例

对新旧钩子函数的使用原则

Vue 官方的文档里,明确指出了。如果使用 Vue3,请尽量使用新的生命周期钩子函数,也就是上篇写在setup()函数中带on的这些钩子函数

onRenderTracked 状态跟踪

onRenderTracked直译过来就是状态跟踪,它会跟踪页面上所有响应式变量和方法的状态,也就是我们用return返回去的值,他都会跟踪。只要页面有update的情况,他就会跟踪,然后生成一个event对象,我们通过event对象来查找程序的问题所在

使用onRenderTracked同样要使用import进行引入

引用之后就可以在 setup() 函数中使用了

import { .... ,onRenderTracked,} from "vue";

onRenderTracked((event) => {
  console.log("状态跟踪组件----------->");
  console.log(event);
});

写完后可以到终端中启动测试服务yarn serve,然后看一下效果,在组件没有更新的时候onRenderTracked是不会执行的,组件更新时,他会跟组里边每个值和方法的变化

onRenderTriggered 状态触发

onRenderTriggered直译过来是状态触发,它不会跟踪每一个值,而是给你变化值的信息,并且新值和旧值都会给你明确的展示出来

使用它同样要先用import进行引入

import { .... ,onRenderTriggered,} from "vue";

在使用onRenderTriggered前,记得注释相应的onRenderTracked代码,这样看起来会直观很多。 然后把onRenderTriggered()函数,写在setup()函数里边

onRenderTriggered((event) => {
  console.log("状态触发组件--------------->");
  console.log(event);
});

对 event 对象属性的详细介绍:

- key 哪边变量发生了变化
- newValue 更新后变量的值
- oldValue 更新前变量的值
- target 目前页面中的响应变量和函数

Vue3中Watch的使用和注意事项

Vue2中也有watch-监听器(侦听器),作用是用来侦测响应式数据的变化,并且可以得到newValue新值和oldValue老值,但在Vue3中watch有了一些细微的变化

这篇来实现一个功能,用来修改页面title

首先在 template 部分添加如下代码

<div><button>【选择完成】</button></div>
<div></div>

使得页面变成如下图所示

然后在 ts 部分引入 refwatch

import { ref,reactive,toRefs,watch  } from 'vue';

setup 函数中 return 前面添加如下代码

const overText = ref("xx课堂") //定义一个属性用来在template中显示
const overAction = () => {
  //定义一个方法用来点击
  overText.value = "点餐完成|" + overText.value;
}
watch([overText,()=>data.selectLan],(newValue,oldValue) => {
  //watch事件监听器,稍后解释
  console.log(newValue,oldValue)
  document.title = newValue[0]; //修改页面的title}
)

setup 函数中 return 出去定义的 overTextoverAction

return {
    ...refData,
    overText,
    overAction
}

然后在 template 中按钮上绑定点击事件,渲染出内容

<div><button @click="overAction">【选择完成】</button></div>
<div>{{overText}}</div>

这里来说一下 watch 事件监听器 

watch是个函数,使用时直接调用,但需要传两个参数

  1. 第一个参数是个  Array 或者 String
  2. 第二个参数是个回调函数,该回调函数也有两个参数,第一个参数为newValue,第二个参数是oldValue

当第一个参数是 Array 的时候,newValueoldValue 分别是数组,对应参数

【注意】:如果监听的参数为 toRefs 包装出来的对象下面的参数时,必须使用 getter ,所以这里使用了 箭头函数来 return 出来,造一个假的 getter

Vue3中模块化介绍

这次我们要做一个功能,需要在页面上显示一个时间,效果如下图

首先打开 App.vue ,先把功能实现,把无用代码全部删除掉,删除完的代码见下方

<template>
  <div></div>
</template>

<script lang="ts">
import { ref } from 'vue'
export default {
  name: 'App',
  setup(){
  }
}
</script>

接下来我们开始实现功能

template 中添加如下

<div>
  <p>{{nowTime}}</p>
  <button @click="getNowTime">显示时间</button>
</div>

在 setup 函数中添加如下

setup(){
    const nowTime = ref('00:00:00'); //定义当前显示的时间
    const getNowTime = () => {
        const now = new Date();
        const hour = now.getHours() < 10 ? '0'+now.getHours() : now.getHours();//获取时
        const minu = now.getMinutes() < 10 ? '0'+now.getMinutes() : now.getMinutes();//获取分
        const sec = now.getSeconds() < 10 ? '0'+now.getSeconds() : now.getSeconds();//获取秒
        nowTime.value = hour+":"+minu+":"+sec;//字符串拼接
        setTimeout(getNowTime,1000)//递归调用
    }
    return{
      nowTime,
      getNowTime
    }
}

代码基本上就实现了,可以点击 显示时间 按钮,显示时间了

但是,很显然,都是在我们 App.vue 中实现的,接下来我们要对时间显示功能以 模块化 的方式来修改

首先在 src 目录下新建一个 hooks 目录,在 hooks 目录下新建一个 useNowTime.ts 文件

App.vuesetup 函数下的内容除 return 外全部拷贝到 useNowTime.ts

此时会报错,因为我们没有 ref , 所以需要将 ref 引入

因为我们要在外面使用该模块,所以需要 export 出去,useNowTime.ts 具体代码如下

import { ref } from 'vue';
const nowTime = ref('00:00:00');
const getNowTime = () => {
    const now = new Date();
    const hour = now.getHours() < 10 ? '0'+now.getHours() : now.getHours();
    const minu = now.getMinutes() < 10 ? '0'+now.getMinutes() : now.getMinutes();
    const sec = now.getSeconds() < 10 ? '0'+now.getSeconds() : now.getSeconds();
    nowTime.value = hour+":"+minu+":"+sec;
    setTimeout(getNowTime,1000)
}

export {nowTime,getNowTime}

接下来我们回到  App.ts 文件中,需要引入 useNowTime.ts 模块暴露出来的 属性方法

return 出去,才可以在 template 中使用

修改之后的 App.vuescript 如下

<script lang="ts">
import {nowTime,getNowTime} from './hooks/useNowTime'; //引入模块里面的暴露出来的属性和方法
export default {
  name: 'App',
  setup(){
    return{  //将模块中的属性和方法暴露到当前文件,才可以在template中使用
      nowTime,
      getNowTime
    }
  }
}
</script>

Vue3中模块化的练习

这次实现一个功能,随机选择一个小姐姐

首先我们在 App.vue 中将多余的代码清空掉,只留下如下代码

<div>
    <h2>随机选择小姐姐</h2>
</div>

<script lang="ts">
export default {
  name: 'App',
  setup(){
  }
}
</script>

接下来我们先使用 npm install axios -s 先进行安装 axios

安装完成之后我们在 hooks 目录下新建一个 useUrlAxios.ts 文件,用来请求数据,代码如下

import {ref} from 'vue'  //因为使用到了ref  所以先引入
import axios from 'axios'  //引入axios//定义一个方法传入参数url
function useUrlAxios(url: string){
    const result = ref(null); //定义结果,默认为null
    const loading = ref(true); //定义加载中状态,默认true
    const loaded = ref(false); //定义是否加载结束,默认false
    const error = ref(null); //定义出错反馈
    axios.get(url).then((res) => {  //使用axios来进行请求接口api,修改定义的四个变量
        loading.value = false;
        loaded.value = true;
        result.value = res.data;
    }).catch(err => {
        error.value = err;
        loading.value = false
    })
    return {  //将四个变量return出去
        result,
        loading,
        loaded,
        error
    }
}

export default useUrlAxios;  //因为该模块中只有一个方法,这里就使用export default来导出

然后我们回到 App.vue

首先引入模块

import useUrlAxios from './hooks/useUrlAxios'

然后在 setup 函数中将模块返回值 return 出去

setup(){
    const url = 'https://apiblog.jspang.com/default/getGirl';
    const { result,loading,loaded } = useUrlAxios(url);
    return{
      result,
      loading,
      loaded
    }
}

最后我们修改 template

<div>
    <h2>随机选择小姐姐</h2>
    <div v-if="loading">loading...</div>
    <img v-if="loaded" :src="result.imgUrl">
</div>

然后就可以实现刷新页面随机出现小姐姐照片

这里的api使用的是jspang教程上的:【apiblog.jspang.com/default/get…

Teleport独立组件的使用

Teleport组件的作用是将一个组件挂载到任意dom节点上,这在以前的vue2.x是做不到的,2.x中所有的组件都是直接挂载在#app下的

首先我们先写一个弹窗框组件,具体点击按钮显示隐藏暂时不做

components 目录下新建 model.vue ,编写组件

<template>
    <div id="center">Hello xueshuai.top !</div>
</template>

<script lang="ts"></script>

<style>
#center {
  width: 200px;
  height: 200px;
  border: 2px solid black;
  background: white;
  position: fixed;
  left: 50%;
  top: 50%;
  margin-left: -100px;
  margin-top: -100px;
}
</style>

然后进入 App.vue 中,先将以前的代码删除掉,template 中只留下一个 divscript 中只留下 export default 里面只有一个 name

然后引入 modal 组件并在 components 中注册

import modal from './components/modal.vue';
export default {
  name: 'App',
  components:{
    modal
  }
}

接下来就可以直接在 template 中使用 <modal /> 来用该组件了

但是我们会发现,此时该组件是挂载在 #app 下的,要想让他可以挂载在任意 dom 节点下,我们需要先打开 model.vue,加入 Teleport

<template>
  <!-- 用teleport来包裹自定义编写的组件,to指向需要挂载的dom节点id上 -->
  <teleport to="#modal">
    <div id="center">Hello xueshuai.top !</div>
  </teleport>
</template>

然后需要在 public 目录下的 index.html 文件中添加一个 id 为 modaldiv 节点,这样才可以把弹出框组件挂载到其他 dom 节点下

<div id="app"></div>
<div id="modal"></div>

可以打开浏览器的控制台看效果

这在以前的vue2.x是不能实现的,但是在vue3中就很容易实现了

Suspense-初识异步请求组件

首先我们先把以前写的代码清空,然后重新编写一个 AsyncShow.vue 自定义组件

注意:如果你要使用Suspense的话,要返回一个promise对象,而不是原来的那种JSON对象。

components 目录下新建 AsyncShow.vue 组件,编写如下代码

<template>
  <h1>{{ result }}</h1>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
    setup() {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                return resolve({ result: "XueShuai" });
            }, 2000);
        });
    }
});
</script>

defineComponent是用来解决 TypeScript 情况下,传统的Vue.extends无法对组件给出正确的参数类型推断的。也就是说在 TypeScript 环境中如果参数类型推断不正常时,用defineComponent()组件来进行包装函数

然后我们new了一个Promise对象,然后用setTimeout等待两秒后返回XueShuai文字就可以了

接下来需要把这个组件放置到App.vue

首先在 App.vue引入注册 组件

import AsyncShow from './components/AsyncShow.vue'
export default {
  name: 'App',
  components:{
    AsyncShow
  }
}

然后在 template 中使用组件

这里使用 suspense 组件包裹,该组件内有两个 template 插槽

  1. 插槽 default 内包裹的内容是异步请求成功时显示的内容
  2. 插槽 fallback 内包裹的内容是异步请求失败时显示的内容
<div>
    <suspense>
      <template #default>
        <AsyncShow />
      </template>
      <template #fallback>
        <h1>loading...</h1>
      </template>
    </suspense>
</div>

Suspense-深入学习 真实请求

上一篇我们其实是使用 setTimoout 来模拟请求,这篇我们来写一个真实的请求

先给出jspang提供的随机小姐姐API:【apiblog.jspang.com/default/get…

首先在 components 目录下新建一个 GirlShow.vue 组件,并写下如下代码

上一篇返回的是 promise 对象,这篇来使用 asyncawit 的写法,后者是前者的语法糖

<template>
    <img :src="result && result.imgUrl"  />
</template>
<script lang="ts">
import axios from 'axios'
import { defineComponent } from 'vue'
export default defineComponent({
    async setup() {  //promise 语法糖  返回之后也是promise对象
        const rawData = await axios.get('https://apiblog.jspang.com/default/getGirl')
        return {result:rawData.data}
    }
})
</script>

接下来在 App.vue 引入该组件并使用 suspense

import GirlShow from './components/GirlShow.vue'
export default {
  name: 'App',
  components:{
    GirlShow  
  }
}

template 中使用 suspense

<div>
    <suspense>
      <template #default>
        <girl-show />
      </template>
      <template #fallback>
        <h1>loading...</h1>
      </template>
    </suspense>
</div>

至此,suspense 的真实请求已经实现了,接下来有一个 异常的捕获

vue3.x的版本中,可以使用onErrorCaptured这个钩子函数来捕获异常。在使用这个钩子函数前,需要先进行引入

import {ref,onErrorCaptured} from 'vue'

钩子引用完毕之后我们就可以在 setup 函数中使用它

钩子函数要求我们返回一个布尔值,代表错误是否向上传递

setup() {
    onErrorCaptured((error) => {
      console.log(`error====>`,error)
      return true  
    })
    return {};
}

个人博客:点击进入