Vue3.0新特性与Vue2.0对比学习笔记

为什么要有 Vue3.0

  1. Vue2.0对TypeScript的支持不够友好
  2. Vue2.0中,重复的含有响应式数据的逻辑复用时采用 mixins 方法不够灵活和方便。Options API中,可以看到多个处理逻辑在 data、methods、computed和watch都有涉及,杂糅在了一起。Composition API尤大大暂时将其翻译为“组合API”,每个处理逻辑可以各自成为一个模块,对外暴露API。这些模块模块同普通ES6模块类似,但是其中使用了响应式数据或者Vue3.0内置的方法。这样,我们需要使用某块逻辑的时候,只需要将这些模块组合起来。同时,逻辑修改的时候只需要修改对应的模块即可。

Vue3.0学习笔记Vue3.0学习笔记

Vue3.0 的新特性

Vue3.0 官网地址:https://v3.vuejs.org/

数据响应

我们知道在 Vue3.0 之前的对象数据响应式的原理是 Object.defineProperty(), 数组的响应式原理是拦截了数组的7个方法(包括 push、pop、shift、unshift、 splice、 sort、 reverse)。这种方式存在的问题是对于对象,我们无法直接检测到属性的新增和删除;对于数组我们无法检测到直接去修改数组下标对应的内容以及利用 length 修改数组的长度。

Vue3.0的数据响应的原理是利用 Proxy 实现的。Proxy在ES2015规范中被正式发布,它在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

  • Proxy 可以直接监听对象而非属性。因此对象的属性新增和删除也可以被监听。
  • Proxy 可以直接监听数组的变化。因此数组直接修改下标的内容以及长度也可以被监听。
  • Proxy 有多达13种拦截方法,不限于apply、deleteProperty、has等

diff算法

相比于Vue2.0中diff算法优化在于增加了静态分析,将节点分为静态节点和非静态节点。静态节点指的是没有绑定响应式数据的节点,Vue2.0中diff算法的单位是组件,在Vue3.0中,将组件的渲染模板根据非静态节点划分为一个个block“块”,diff算法的单位是一个个block,渲染只与动态内容的多少有关,与静态内容无关。无论层级嵌套多深,它的动态节点都直接与Block根节点绑定,无需再去遍历静态节点。

对 TypeScript 的友好支持

用过的都说好。

允许一个组件可以有多个根节点

利用 Fragment 实现了一个组件可以有多个根节点。Fragment“碎片”虚拟元素,并不会渲染到html中。

<template>

<div>Hello</div>

<div>World</div>

</template>

Composition API

性能提升

  • 重写了虚拟Dom的实现(且保证了兼容性,脱离模版的渲染需求旺盛)。
  • 编译模板的优化。
  • 更高效的组件初始化。
  • update性能提高 1.3~2 倍。
  • SSR速度提高了 2~3 倍。

Tree shaking support

Tree shaking更好的支持,很多时候,我们并不需要 vue提供的所有功能,在 vue 2 并没有方式排除掉,但是 3.0 都可能做成了按需引入。

zhuanlan.zhihu.com/p/134302690

zhuanlan.zhihu.com/p/191216161

创建一个项目

Vite 方式

本地已经安装过 node.js 后,使用下面的命令创建一个项目:

$ npm init vite-app <project-name>

$ cd <project-name>

$ npm install

$ npm run dev

CLI 方式

先全局更新我们的 Vue-Cli

$ npm install -g @vue/cli

使用 Vue-Cli 命令创建项目:

$ vue create cli-demo

按照默认配置也可以自定义各项配置。我们这里会选择支持 TypeScript 的选项。

我们也可以通过加载 CDN 或者 Webpack 中加载 Vue3.0 去使用。详情 v3.vuejs.org/guide/insta…

Vite 和 Webpack 打包对比

Vite 是尤大神亲自编写的打包工具,我们先来看看 Vite 的特点:

  • 快速的冷启动
  • 即时的模块热更新
  • 真正的按需编译

webpack 会根据各个模块的依赖关系先打包,然后启动开发服务器,浏览器请求页面时直接返回打包后的结果。

Vite 则是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。Vite 的按需编译利用的是现代浏览器本身就支持 ES Module,浏览器会自动向依赖的 Module 发出请求。 Vite 充分利用了这一点。Vite 编译后的文件保留了原生的 Import 的语句,浏览器看到 import 后会再去向服务器请求这些 import 的文件。各个文件的依赖关系由浏览器自动处理的。

Vue3.0学习笔记

下图是 Vite 项目请求的资源信息:

Vue3.0学习笔记

下图是 webpack 项目启动后资源请求信息:

Vue3.0学习笔记

对比可以看出,Vite 项目直接请求的我们项目代码文件,而 webpack 则请求的是打包编译后的文件。所以,Vite 启动速度是非常快的,同时也是按需动态编译。按需编译的方式,极大的缩减了编译时间,项目越复杂,模块越多,Vite 的优势越明显。

原理:Vite 内部借用了 koa 来启动一个服务。去代理这些模块。通过拦截请求然后动态编译各种类型的文件。

在 HMR(热更新)方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像 webpack 那样需要把该模块的相关依赖全部编译一次,效率更高。

当需要打包到生产环境时,Vite 使用传统的 rollup 进行打包。因此,Vite 的主要优势在开发阶段。

blog.csdn.net/qq_41499782…

zhuanlan.zhihu.com/p/150083887

磨刀

磨刀不误砍柴工,这里推荐 2 款 VSCode 中好用的插件

ESLint

Vue3.0学习笔记

如果安装后不生效的话,可以在项目根目录下创建 .vscode 文件夹,在里面创建 settings.json 文件,内容如下:

//settings.json

{

"eslint.validate": ["typescript"]

}

重启后生效。我们可以尝试一下,随便定义一个变量,会有 eslint 的提示,如下:

Vue3.0学习笔记

一个是告诉我们变量 a 只是定义了没有使用,另一个是告诉我们 a 没有被重新赋值过,应该使用 const 来定义。使用 eslint 提高我们编写代码的规范。

Vetur

Vue3.0学习笔记

可以让我们的 vue 文件不同颜色显示标签,同时当我们输入的时候给我们智能提示。

语法部分

setup 函数

在 Vue3 中提出了 Composition API 的概念。Vue 2.0 种我们通过 props、data、methods 等来定义组件,

在 Vue3.0 中使用 setup 定义响应式的数据和方法。

Vue3.0学习笔记

<template>

<h3>欢迎来到我的掘金</h3>

<buttonv-for="(item, key) in data.languages":key="key" @click="selectLangFun(key)">{{item}}</button>

<p>你选择的课程是【{{selected}}】</p>

</template>

<scriptlang="ts">

import { reactive, ref} from"vue";

exportdefault {

name: "ProgramSelect",

setup() {

const data = reactive({

languages: ["Java", "Javascript", "C#"]

});

const selected = ref("");

const selectLangFun = (params: number): void => {

selected.value = data.languages[params];

}

return {

data,

selected,

selectLangFun

}

}

}

  • setup(props, context)函数其实有 2 个参数,props 对应 Vue 2 中 props, 里面定义的父组件传递给子组件的变量;setup 函数中没有暴露 this,context 对应于 Vue 2 中 this 对象。更多具体使用方法后面介绍。
  • 所有在 template 模板中使用的变量都需要通过 setup 函数 return 出去。
  • 通过 reactive 和 ref 来创建响应式的变量。两者的区别是 reactive 创建的是对象变量的响应式,而 ref 用来创建基本类型的响应式。
  • ref 创建的变量 selected 修改值的时候要加上 value。Vue3 内部会将简单类型如字符串"hello",动态转为一个对象,具有 value 属性,值是"hello"。
  • 所有在模板中要使用的数据、方法需要 return 出去。

toRef 和 toRefs

上面我们介绍了 reactive 和 ref 的一些基本使用,这里介绍 toRef 和 toRefs 使用技巧。部分代码会使用 TS 语法,不太熟悉的同学,可以先学习下这篇博客 juejin.cn/post/690313…

toRef

toRef() 是一个函数,返回一个 ToRef 对象。使用语法是 const result = toRef(target, "key");

  • ToRef 对象并不是 Ref 对象;我们通过 ref() 函数创建的对象一定是响应式的数据,数据被修改了,会直接反应在页面上。
  • ToRef 对象不一定是响应式的数据,这个要看 target 的数据是否是响应式的。
  • target 如果是响应式对象(reactive 或者 ref 创建的对象)的话,那么 result 也是响应式对象。
  • toRef() 函数是对 target 的一种映射;result 的内容修改的时候,target 也会被修改;反过来也是一样的,target 被修改了,result 内容也会被修改。

<template>

<p>{{a}} <button @click="changeA">changeA</button></p>

<p>{{objRef}} <button @click="change">change</button></p>

<p>{{reactiveRef}} <button @click="changeReactive">changeReactive</button></p>

</template>

<scriptlang="ts">

import { toRef, reactive, ref} from'vue'

exportdefault {

setup(){

//普通对象

const obj = {name: "zhangsan", age: 12}

const objRef = toRef(obj, "name");

//响应式对象

const a = ref("hello");

//响应式对象

const reactiveObj = reactive({name: "Willim", age: 6});

const reactiveRef = toRef(reactiveObj, "name");

const changeA = function(){

//会响应到页面上

a.value = "Vue 3.0";

}

const change = function(){

//会影响 obj 的值,但是不会响应到页面上

objRef.value = "lisi";

console.log(obj);

}

const changeReactive = function(){

//会影响 reactiveObj 的值,但是不会响应到页面上

reactiveRef.value = "lisi";

console.log(reactiveObj);

}

return{

objRef,

reactiveRef,

change,

changeReactive,

a,

changeA

}

}

}

</script>

Vue3.0学习笔记

点击 changeA 会立即响应到页面上;点击 change 并不会立即响应到页面;当我们点击 changeReactive 按钮的时候由于改变了响应式的数据,所以会导致 update 页面上的数据,所以 zhangsan 和 Willim 都被更新。

Vue3.0学习笔记

  • 上面的截图是我们打印 objRef 和 reactiveRef 两个变量的值看到的结果。可以看到 result 的自定义属性 _object 就是 target,它们是通过 _object 保持了 新值 result 和 原始值 target 的映射。
  • 同时可以看到 reactiveObj 是响应式的对象,在 _object 属性上是 Proxy 对象。

总结一下:toRef() 的设计初衷应该是想和 Ref() 函数类似,Ref() 是将一个基础类型的变量进行响应式化(返回一个具有 value 属性的对象),而 toRef() 则是从对象中剥离出属性来(返回一个具有 value 属性的对象),如果原始对象是响应式的,那么剥离出的属性也是响应式的;如果原始数据不是响应式,那么剥离出来的属性就是普通对象了,不具响应式的作用。

toRefs

先思考一个问题:Proxy 对象结构后是什么样子的呢?

const target = {name: 'zs'};

const handler = {

get: function(obj, prop){

return prop in obj ? obj[prop]: 18

}

}

const p = newProxy(target, handler)

const pp = {...p};

console.log(target, pp, p);

我们定义了 p 是 Proxy 对象,但是经过 ... 解构以后的新对象 pp 已经不是 Proxy 对象了。反映到 Vue 3.0 中, reactive() 函数会返回 Proxy 响应式的对象,但如果直接解构 reactive() 创造的变量的话,那返回的数据也将不是 Proxy 对象了,导致其不具有响应式的特点了。toRefs() 的出现解决了这个问题哦。

Vue3.0学习笔记

  • toRefs() 函数与 toRef() 很相似。toRef() 剥离的是对象的某个属性,toRefs() 剥离的是对象的所有属性,返回一个新对象。
  • toRefs() 函数用法是 const result = toRefs(target); target 的每个属性都会被转换为 ToRef 对象。
  • 同 toRef() 一样,如果原始数据 target 具有响应式的,那么 result 也是响应式;如果 target 是普通对象,那么 result 也是普通对象,不具有响应式。
  • 由于 toRefs() 函数返回的是一个对象,对象每个属性都是 ToRef 对象。我们知道 ... 解构对象是浅拷贝,所以使用 toRefs() 函数,我们可以对响应式的数据进行解构。来一个例子

<template>

<p>我是 {{name}},年龄{{age}} <button @click="change">change</button></p>

</template>

<script>

import {reactive, toRefs} from'vue';

exportdefault {

setup(){

const data = reactive({

name: "Willim",

age: 6,

change: ()=>{

data.age++;

}

})

return {

...toRefs(data)

}

}

}

</script>

Vue3.0学习笔记

computed

我们可以使用 computed 对响应式的变量进行处理,返回一个 ComputedRef 类型的变量。

let doubleCount = computed(()=>{

return data.count * 2;

})

watch

Vue 3.0 中的 watch 函数可以监听多个变量哦,监听某个变量变化时增加需要处理的逻辑(可以理解为这个变量变化的副作用);或者监听多个变量的变化。用法示例如下:

//监听一个变量

watch(selected, (newVal, oldVal)=>{

console.log("newVal", newVal);

console.log("oldVal", oldVal);

document.title = newVal;

});

//监听多个变量

watch([selected, ()=>data.count], (newVal, oldVal)=>{

console.log("newVal", newVal);

console.log("oldVal", oldVal);

document.title = newVal[0] +","+ newVal[1];

});

  • watch(arg, (newVal, oldVal)=>{}) 可以只监听一个响应式的变量 arg,第二个参数是回调函数,当 arg 改变的时候,副作用的函数逻辑。newVal 表示变量修改后的值,oldVal 表示变量的旧值。
  • watch([arg1, arg2,...], (newVal, oldVal)=>{}) 也可以监听多个响应式的变量 arg1、arg2 等,第二个参数是回调函数,当 arg1、arg2...任意一个改变的时候,副作用的函数逻辑。newVal 表示变量修改后的值,这个时候以数组的形式显示,与变量 arg1、arg2...一一对应;oldVal 表示变量的旧值,以数组的形式显示,与变量 arg1、arg2...一一对应
  • selected 是 ref 函数创建的变量,我们可以直接监听。
  • data 是 reacitve 函数创建的变量,我们只想监听某个属性(count),需要写成函数获取的形式。

生命周期

使用了 setup 函数,大家一定很好奇 Vue2.0 中的声明周期函数都去哪了呢?

其实,Vue2.0 原来的声明周期函数我们仍然可以在 Vue3.0 中使用,同时,Vue3.0 也定义了一套生命周期函数与 Vue2.0 一一对应。

Vue3.0学习笔记

Options APIHook inside (setup)
beforeCreateNot needed*异步请求初始化的数据
createdNot needed*
beforeMountonBeforeMount组件挂载到节点上之前执行的函数
mountedonMounted组件挂载完成后执行的函数
beforeUpdateonBeforeUpdate组件更新之前执行的函数
updatedonUpdated组件更新完成之后执行的函数
beforeUnmount (beforeDestroy)onBeforeUnmount组件卸载之前执行的函数
unmounted (destoryed )onUnmounted组件卸载完成后执行的函数
errorCapturedonErrorCaptured当捕获一个来自子孙组件的错误时被调用
renderTracked(+)onRenderTracked((event)=>{})状态跟踪,setup函数中定义的响应式的数据任意一个发生变化时执行,event包含所有的响应式的变量
renderTriggered(+)onRenderTriggered((event)=>{})状态跟踪,setup函数中定义的响应式的数据任意一个发生变化时执行,event 只包含发生变化的数据变量

  • setup 是around beforeCreate and created 中运行,所以我们之前需要在 vue2.0 中 beforeCreate and created 定义逻辑可以直接定义在 setup 中。
  • onX 要先 import 后再 setup 函数中定义。
  • Options API 与 setup() 函数是同级的,并且不需要导入

import { onMounted, onUpdated, onUnmounted } from'vue'

const MyComponent = {

setup() {

onMounted(() => {

console.log('mounted!')

})

onUpdated(() => {

console.log('updated!')

})

onUnmounted(() => {

console.log('unmounted!')

})

},

created(){

console.log("created")

},

beforeCreate(){

console.log("beforeCreate")

},

beforeMount(){

console.log("beforeMount")

},

mounted(){

console.log("mounted")

}

}

  • Vue2.0 和 Vue3.0 的加载顺序是:setup() -> beforeCreate -> created -> onBeforeMount -> beforeMount -> renderTracked -> onMounted -> mounted
  • Vue2.0 和 Vue3.0 的更新顺序是:renderTriggered -> onBeforeUpdate -> beforeUpdate -> renderTracked -> onUpdated -> updated
  • Vue2.0 和 Vue3.0 的组件卸载顺序是:onBeforeUnmount -> beforeUnmount -> onUnmounted -> unmounted

Composition API

用于公共逻辑的提取,提高可复用性。在 Vue2.0 中我们可以使用mixins的方式提取一些公共处理逻辑。以记录用户点击屏幕位置为例来说明,显示每次用户鼠标点击屏幕的位置。不考虑代码的复用性,我们会直接写出下面的代码:

<template>

<p>X: {{x}}, Y: {{y}}</p>

</template>

<scriptlang="ts">

import { defineComponent, reactive, toRefs, onMounted, onUnmounted } from'vue';

exportdefault defineComponent({

name: 'App',

setup(){

const pointer = reactive({

x: 0,

y: 0

});

const updateMouse = (e: MouseEvent)=>{

pointer.x = e.pageX;

pointer.y = e.pageY;

};

onMounted(()=>{

document.addEventListener("click", updateMouse)

})

onUnmounted(()=>{

document.removeEventListener("click", updateMouse)

})

return{

...toRefs(pointer)

}

}

});

</script>

在Vue3.0中,我们可以考虑把这块获取当前时间的逻辑提取出来,成为一个公共的模块。这个模块与普通Javascript模块类似,只是代码里使用了Vue3.0响应式的变量和Vue3.0内置的方法,通常会将响应式的变量返回出去。实现方法如下:

//新建文件夹 hooks,并新建文件 useMousePosition.ts

import { reactive, onMounted, onUnmounted } from'vue';

functionuseMousePosition(){

const pointer = reactive({

x: 0,

y: 0

});

const updateMouse = (e: MouseEvent)=>{

pointer.x = e.pageX;

pointer.y = e.pageY;

};

onMounted(()=>{

document.addEventListener("click", updateMouse)

});

onUnmounted(()=>{

document.removeEventListener("click", updateMouse)

});

return pointer;

}

exportdefault useMousePosition;

那么我们在 App.vue中只需要导入 useMousePosition 方法,并使用即可。

<template>

<p>X: {{x}}, Y: {{y}}</p>

</template>

<scriptlang="ts">

import { defineComponent, reactive, toRefs} from'vue';

//导入 useMousePosition

import useMousePosition from'./hooks/useMousePosition';

exportdefault defineComponent({

name: 'App',

setup(){

const pointer = useMousePosition();

return{

...toRefs(pointer)

}

}

});

</script>

Teleport组件

teleport翻译过来是“(被)远距离传送”。Teleport组件设计的目的是将某个组件挂载在指定的元素上。在 Vue3.0 以前,我们所有的组件都被挂载在根节点上如id是app的div,其他组件会一层层嵌套在整个html节点树上。 Vue3.0 的Teleport组件可以让我们将某个组件挂载在指定节点上,比如跟根节点app同级的节点上,让代码的实现更符合我们的习惯,而不是层层嵌套在app节点上。以弹出框组件为例说明Teleport组件使用。

<!-- index.html文件-->

<body>

<divid="app"></div>

<!-- 新增modal节点-->

<divid="modal"></div>

</body>

  • 新建 Modal.vue 文件,Vue3.0的组件将 emits 抽出来,使逻辑更加清晰;并能通过 payload.type校验传递进来的数据。
  • teleport中的 to 指定将要挂载的元素,这里是id为modal的元素。

// Modal.vue 文件

<template>

<teleportto="#modal">

<divid="center"v-if="isOpen">

<h1><slot>标题</slot></h1>

<button @click="close">close</button>

</div>

</teleport>

</template>

<scriptlang="ts">

import {defineComponent} from'vue'

exportdefault defineComponent({

props: {

isOpen: Boolean

},

emits: {

'close-modal': (payload: any)=>{

return payload.type === "close"

}

},

setup(props, context){

const close = function(){

context.emit("close-modal", {type: "close"});

}

return {

close

}

}

})

</script>

<stylescoped>

#center{

position: fixed;

top: 0;

bottom: 0;

left: 0;

right: 0;

width: 200px;

height: 200px;

border: 1px solid #cccccc;

background: white;

margin: auto;

}

</style>

在任意组件中使用 Modal 组件。

<template>

<p>是我<button @click="showModal">显示弹出框</button></p>

<Modal:isOpen="openFlag" @closeModal="close">

<slot>this is title</slot>

</Modal>

</template>

<scriptlang="ts">

import {ref} from'vue'

import Modal from"./Modal.vue";

exportdefault {

components: {

Modal

},

setup(){

const openFlag = ref(true);

const close = function(){

openFlag.value = false;

}

const showModal = function(){

openFlag.value = true;

}

return{

openFlag,

close,

showModal

}

}

}

</script>

Vue3.0学习笔记

Suspense组件

Suspense 是Vue3推出的一个内置特殊组件,用来定义具有异步请求数据的组件的显示。如果使用 Suspense,要 setup函数中需要返回一个 promise。

例1

新建 AyncShow.vue 文件,setup函数需要返回一个Promise对象。

<template>

<h1>{{result}}</h1>

</template>

<scriptlang="ts">

exportdefault {

setup(){

returnnewPromise((resoluve)=>{

setTimeout(()=>{

resoluve({result: 100})

}, 2000)

})

}

}

</script>

在App.vue文件中使用

<template>

<Suspense>

<template #default>

<div>

<AsyncShow/>

</div>

</template>

<template #fallback>

<h1>Loading...</h1>

</template>

</Suspense>

</template>

<scriptlang="ts">

import { defineComponent, ref, toRefs} from'vue';

import AsyncShow from'./components/AsyncShow.vue';

exportdefault defineComponent({

name: 'App',

components: {

AsyncShow

}

});

</script>

  • Suspense组件内置了两个具名插槽slot,一个是default,用来显示异步组件请求成功的内容;一个是fallback用来显示异步组件请求响应前页面显示的内容。
  • default插槽可以有多个组件,但是需要有一个根节点。

例2

模拟发送异步请求完成后,显示组件。这里先介绍一个好用的后端请求接口:https://dog.ceo/dog-api/ 来随机获取狗狗的图片。首先我们先封装一个ajax的get请求方法:

//ajax.ts

functiongetAjax(url: string){

returnnewPromise((resolve, reject) => {

const xhr = new XMLHttpRequest();

xhr.open('GET', url, true);

xhr.send();

xhr.onreadystatechange = function(){

if(xhr.readyState === 4){

if(xhr.status === 200){

resolve(xhr.response)

}else{

reject(xhr.response);

}

}

}

xhr.onerror = ()=>{

reject(xhr.response);

}

});

}

exportdefault getAjax;

再封装一个请求狗狗图片的组件:

//DogShow.vue

<template>

<img:src="rawData">

</template>

<scriptlang="ts">

import {defineComponent} from'vue';

import getAjax from'../ajax';

exportdefault defineComponent({

asyncsetup(){

const p = getAjax("https://dog.ceo/api/breeds/image/random");

let rawData;

await p.then((result) =>{

const tmp = JSON.parse(result as string);

if(tmp.status==="success"){

rawData = tmp.message;

}

})

return {

rawData

}

}

})

</script>

在 App.vue 中使用

<template>

<Suspense>

<template #default>

<div>

<AsyncShow/><DogShow/>

</div>

</template>

<template #fallback>

<h1>Loading...</h1>

</template>

</Suspense>

<p>{{error}}</p>

</template>

<scriptlang="ts">

import { defineComponent, ref, toRefs, onErrorCaptured} from'vue';

import AsyncShow from'./components/AsyncShow.vue';

import DogShow from'./components/DogShow.vue';

exportdefault defineComponent({

name: 'App',

components: {

AsyncShow,

DogShow

},

setup(){

const error = ref(null);

onErrorCaptured((e: any)=>{

error.value = e;

returntrue;

})

return{

error,

}

}

});

</script>

Vue3.0学习笔记

在onErrorCaptured函数中监听了错误,比如我们的url故意写错验证一下。onErrorCaptured的时候不能在 DogShow 组件中catch异常,不然在父组件App.vue中无法捕获。

Vue3.0学习笔记

defineComponent

defineComponent为了支持 TypeScript 存在的。defineComponent并没有实现特殊逻辑,可以将传入的对象获得对应的类型。我们使用defineComponent定义的组件可以很好的支持 setup、props等的类型提示。

感谢

如果本文有帮助到你的地方,记得点赞哦,这将是我持续不断创作的动力~

Keep smiling and never give up, even when things get you down.

即使现实令你沮丧,也要保持微笑,永不言弃。

以上是 Vue3.0新特性与Vue2.0对比学习笔记 的全部内容, 来源链接: utcz.com/a/118484.html

回到顶部