# 1.0之了解vue3的改动
vue3
的版本现在也已经发布了正式版本,这就说明之后不管怎么变动都不会太大,只会打点补丁新增写特性之类的。而其他一些 vue
的相关配件, 如 vuex
, vue-router
, vue-cli
等都处于 beta
状态了。在这个时候, vue3
可能还不会大规模的使用, 但是我们也需要开始学习了。因为 vue3
真的是太棒了。
以下所有 vue2
指 vue2.x版本
,而 vue3
指 vue3.x版本
# 项目初始化
一般来说有两种方式来创建项目。
# 通过 vue-cli
来创建项目
一个是以 vue2
的方式通过 vue-cli
来创建。
# 使用npm
npm install -g @vue/cli
# 使用yarn
# yarn global add @vue/cli
# vue create [project-name]
vue create vue3-project
2
3
4
5
6
7
8
输入命令后按下回车, 会出现让你选择预设,但是可以看到第二个选项是 Default (Vue 3 Preview) ([Vue 3] babel, eslint)
,采用 vue3
的默认模板。我们就可以选择它。
? Please pick a preset: (Use arrow keys)
> Default ([Vue 2] babel, eslint)
Default (Vue 3 Preview) ([Vue 3] babel, eslint)
Manually select features
2
3
4
接下来就会开始自动安装了。然后我们进去这个目录, 并运行项目。
cd vue3-project
yarn serve
2
等待项目编译完成后, 我们就可以在浏览器输入 http://localhost:8080/
(如果你的8080端口没被占用的话,就算被占用了,vue-cli
也会给你分配一个未被占用的端口) 来访问你的项目了。
# 通过 vite
来创建项目
vite
是一个通过原生 ES Module
提供服务的。理论上这个会比使用 webpack
构建来的块, 但是兼容性也是一个问题,所以暂时不使用它。
# 使用npm
npm init vite-app hello-vue3
# 使用yarn
# yarn create vite-app hello-vue3
2
3
4
5
# 这里将列出 vue3
现在的所有新功能或新特性或重大改动
# 1. 最重要的当然是 组合式 API, 也就是 composition API
。
为啥要将原先的 options API
可选项 API,更改为 composition API
组合式 API。
刚实验使用 composition API
的时候我也想过,因为真的太麻烦了。创建响应式数据我需要引入函数,使用生命周期我需要引入函数,使用计算属性我需要引入函数。但是在真的熟练使用后我觉得真的不错,因为相比于原先的数据与这条数据相关的逻辑是分离,现在的 composition API
可以使得数据与这条数据相关的逻辑是在一起的,这将更好维护代码。
假设有这样一段代码:
// 这是 vue2 的代码
export default {
data () {
return {
// 查询列表
tableData: [],
// 新增行
isAddSucess: false,
// 删除行
isDeleteSucess: false
}
},
created () {
this.queryData()
},
methods: {
// 查询列表
queryData () {
this.tableData = [1, 2, 3]
},
// 新增方法
addTable () {
this.isAddSucess = false
setTimeout(() => {
this.tableData.push(...[4, 5, 6])
this.isAddSucess = true
}, 300)
},
// 删除方法
deleteTbale (index) {
this.isDeleteSucess = false
setTimeout(() => {
this.tableData.splice(0 ,1)
this.isDeleteSucess = true
}, 300)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
这个时候我们就会发现一个很严重的问题, 数据在上面, 而跟数据相关的逻辑代码在下面,当整个组件很复杂的时候会发现整个代码是难以维护的。
而 composition API
就是来解决这个问题的
// 这是 vue3 的代码
import { reactive, ref, isReactive } from 'vue'
export default {
name: 'App',
setup () {
let tableData = reactive([])
let isAddSucess = ref(true)
let isDeleteSucess = ref(true)
queryData(tableData)
return {
tableData,
isAddSucess,
isDeleteSucess,
add: () => addTable(tableData, isAddSucess),
deleted: () => deleteTable(tableData, isDeleteSucess)
}
}
}
// 查询列表
function queryData (tableData) {
tableData = reactive([1, 2, 3])
}
// 新增方法
function addTable (tableData, isAddSucess) {
isAddSucess.value = false
setTimeout(() => {
const addData =[4, 5, 6]
tableData.push(...addData)
isAddSucess.value = true
}, 300)
}
// 删除方法
function deleteTable (tableData, isDeleteSucess) {
isDeleteSucess.value = false
setTimeout(() => {
tableData.splice(0, 1)
isDeleteSucess.value = true
}, 300)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
好吧,代码写的并不是很好, 因为我不太会。所以确实有一定的学习成本,并不是一开始就能写一个很好地代码的。
# 2. 新增了一个 Teleport
组件, 传送组件
在 Vue
中, 我们需要将所有 dom
都放置在 <div id="app"></div>
中。这样并不好, 这样会增加 dom
得到层级,而层级越深则性能越差。而且当我们想将某个组件创建在 body
下时就犯难了。
原来的我们会这样做:
这样我们将将一个组件插入到了 body
下了
import Message from './Message.vue'
import Vue from 'vue'
// 创建一个基于 Vue 的子类, 并将其挂载, 通过原生 DOM API 把它插入文档中
let MessageConstructor = Vue.extend(Message)
let instance = new MessageConstructor()
instance.$mount()
document.body.appendChild(instance.$el)
2
3
4
5
6
7
8
现在我们可以这样做了:
使用这个组件,它将自动将组件内容插入到 to
属性中的 dom
节点下
<teleport to="body">
/* 消息组件内容 */
</teleport>
2
3
# 3. 每个组件将不再只能有一个根节点了, vue3
允许你写多个根节点。
# 4. 全局 Vue API
已更改为使用应用程序实例
这句话啥意思呢? 可以看到下面 vue2
和 vue3
通过不同的方式创建了实例。
在 vue2
中,是通过同一个构造函数来创建实例的, 并且是通过这个构造函数来设置全局配置的。这样对于多个实例来说, 这些全局配置是共享的。
// vue2 的写法
import Vue from 'vue'
import App from './App.vue'
const a = new Vue({
el: '#app',
render: h => h(App)
})
const b = new Vue()
Vue.prototype.$http = http
// $http 方法是共享的
console.log(a) // 能找到 $http 方法
console.log(b) // 能找到 $http 方法
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// vue3 的写法
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
const b = createApp(App)
app.mount('#app')
app.config.globalProperties.$http = http
// $http 方法不是共享的
console.log(app) // 能找到 $http 方法
console.log(b) // 不能找到 $http 方法
2
3
4
5
6
7
8
9
10
11
12
# 5. 全局和内部 API
已经被重构为可 tree-shakable
什么叫 tree-shakable
?
如下 utils.js
文件中有 a
,b
,c
三个方法, 但是在 main.js
中我们只用到了 a
,b
方法。
那么最终的打包文件中, c
方法将会被剔除出去,只留下了被使用过的a
,b
方法。这就叫tree-shakable
,留下用过的,没用过的剔除出去。
// utils.js
export const a = () => {}
export const b = () => {}
export const c = () => {}
// main.js
import { a, b } from './utils.js'
a()
b()
2
3
4
5
6
7
8
9
为什么说是重构为可tree-shakable
?
在 vue2
中,我们使用了一些方法
然后发现了一个问题,所有的方法都是绑定到 this
上,导致打包后不管它们是否使用, 都将包含在最终代码中。
// vue2 写法
this.$set(this, 'count', 99)
this.$nexTick(() => {
// todo
})
this.$router.push()
this.$store.commit()
2
3
4
5
6
7
而在 vue3
中,都是作为 ES 模块构建的命名导出进行访问。如:
// vue3 写法
import { nextTick } from 'vue'
nextTick(() => {
// 一些和DOM有关的东西
})
2
3
4
5
6
# 6. 组件上 v-model
用法已更改
在 vue2
中使用自定义组件时:
<table-component v-model="tableData"></table-component>
<!-- 是以下的简写: -->
<table-component :value="tableData" @input="tableData = $event"></table-component>
2
3
4
5
// table-component.vue
// vue2 写法
export default {
props: {
value: Array,
default: () => []
},
methods: {
deleteData () {
// ...todo
this.$emit('input', this.value)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可以发现, 我们必须按照文档上的方式写成属性名为 value
, 和 事件名为 input
。
并且当属性名 value
和 事件名 input
被占用的时候,这时候将非常麻烦,幸好在 vue 2.2
中引入了 model
组件选项,允许组件自定义用于 v-model
的 prop
和事件。但是,这仍然只允许在组件上使用一个 model
。
更改属性或事件名称,需要在 table-component
组件中添加 model
选项:
// table-component.vue
// vue2 写法
export default {
model: {
prop: 'data',
event: 'dataChange'
},
props: {
// 使用 `data` 代替 `value` 作为 model 的 prop
data: Array
},
data () {
return {
// 这将允许 value 用做其他作用
value: null,
}
},
methods: {
deleteData () {
// ...todo
this.$emit('dataChange', this.value)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
现在让我们用 vue3
的方式写一个组件:
- 现在自定义一个组件,
v-model
prop 和事件默认名称已更改:- prop:
value
->modelValue
- event:
input
->update:modelValue
- prop:
(我们将不再受到 value
属性 和 input
事件名冲突的困扰了)
v-bind
的.sync
修饰符和组件的model
选项已移除
(当我们使用 .sync
时,vue
会给我们抛出一个错误, error '.sync' modifier on 'v-bind' directive is deprecated. Use 'v-model:propName' instead
, 它告诉我们,.sync
已被移除, 请使用 v-model:propName
方式 )
(而使用 model
选项时, 它已经不生效了, 但是也没有错误。)
- 现在可以在同一个组件上使用多个
v-model
进行双向绑定
<loading v-model="isloading" v-model:text="loadingText"></loading>
// this.isloading = true
// this.loadingText = 'loading...'
2
3
// vue3 写法
// loading.vue
export default {
props: {
modelValue: Boolean,
text: String
},
emits: ['update:modelValue'],
setup (props) {
console.log(props) // Proxy {modelValue: true}
}
}
2
3
4
5
6
7
8
9
10
11
12
# 7. 使用 <template></template>
元素进行循环输出时,key
属性现在可以绑定在 template
元素上了
vue2
中我们只能将 key
属性绑定在真实的节点上,如下就是 div
上, 如果绑定在 template
元素上则会报错:
<template> cannot be keyed. Place the key on real elements instead
<!-- vue2 写法 -->
<template v-for="(item, index) in arr">
<div :key="index"></div>
</template>
2
3
4
而在 vue3
中,我们可以绑定在 template
元素上。如果绑定在 div
标签上则会报错:
<template v-for> key should be placed on the <template> tag
<!-- vue3 写法 -->
<template v-for="(item, index) in arr" :key="index">
<div></div>
</template>
2
3
4
# 8. v-bind
排序问题
在平常写一个组件的时候,我们可能需要将用户传递过来的 v-bind
进行覆盖组件的属性。
<!-- vue2 写法 -->
<div id="a" v-bind="{ id: 'b' }">
<!-- 或者这种写法 -->
<div v-bind="{ id: 'b' }" id="a">
2
3
4
5
最终渲染出来的结果都是:
<div id="a">
而我们期待的是,id
b 覆盖 id
a。
而在 vue3
中修改了这种写法的逻辑, 将渲染最后出现的属性:
<!-- vue3 写法 -->
<div id="a" v-bind="{ id: 'b' }">
<!-- 渲染结果 ↓ -->
<!-- <div id="b"></div> -->
<div v-bind="{ id: 'b' }" id="a">
<!-- 渲染结果 ↓ -->
<!-- <div id="a"></div> -->
2
3
4
5
6
7
8
9
# 9. vue3
移除了 v-on.native
修饰符
在 vue2
中, 我们想在父组件监听子组件的根节点事件, 我们就需要使用 v-on.native
修饰符。如下:
<!-- vue2 写法 -->
<!--parent.vue 父组件 -->
<child @click.native="handleClick"></child>
2
3
<!-- vue2 写法 -->
<!--child.vue 子组件 -->
<tempalte>
<div class="child"></div>
</tempalte>
2
3
4
5
这时 @click.native
就是监听 class
等于 child
节点的点击事件了。
在 vue3
中移除了该修饰符 (v-on.native
修饰符)。
如果仅仅这样写,那将相当于 @click.native
。
<!-- vue3 写法 -->
<!--parent.vue 父组件 -->
<child @click="handleClick"></child>
2
3
如果加上下面的代码, handleClick
函数将不会触发, 因为它会被认定为 child.vue
的回调函数,而下面的代码中没有定义 click
回调:
// vue3 写法
// child.vue 子组件
export default {
emits: ['click']
}
2
3
4
5
最后我们加上 click
回调, 这样触发 aa
函数的时候, handleClick
也会触发, 但是他不是 click
原生点击事件,只是子组件的回调:
// vue3 写法
// child.vue 子组件
import { getCurrentInstance } from 'vue'
export default {
emits: ['click'],
setup (props) {
const { emit } = getCurrentInstance()
return {
aa: () => aa(emit)
}
}
}
function aa (emit) {
emit('click')
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 10. vue3
中 ref=""
更改
在 vue2
中,我们可以通过往元素标签上添加 ref
属性来获取相应的标签 dom
值。如下:
<!-- vue2 写法 -->
<div ref="div"></div>
<ul>
<li v-for="(item, index) in [1, 2, 3]" :key="index" ref="li">{{ item }}</li>
</ul>
2
3
4
5
// vue2 写法
export default {
mounted () {
console.log(this.$refs.div)
// div 元素
console.log(this.$refs.li)
// [li ,li ,li] li元素的数组
}
}
2
3
4
5
6
7
8
9
而在 vue3
中, ref
接收一个函数,函数的参数就是他的 dom
元素。
<!-- vue3 写法 -->
<div :ref="divFn"></div>
<ul>
<li v-for="(item, index) in [1, 2, 3]" :key="index" :ref="liFn">{{ item }}</li>
</ul>
2
3
4
5
// vue3 写法
export default {
setup () {
const divFn = (el) => {
console.log(el)
// 输出 div 元素
}
const liFn = (el) => {
console.log(el)
// 输出每一个 li 元素
}
return {
divFn,
liFn
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 11. 渲染函数更改
在 vue2
中, 我们可以这样使用渲染函数, 这样我们不需要 html
结构了。
// Vue 2 渲染函数示例
// child.vue
export default {
render(h) {
return h('div')
}
}
2
3
4
5
6
7
从 vue3
开始, h
将是全局导入, 而不是作为参数传递给渲染函数。
// Vue 3 渲染函数示例
// child.vue
import { h } from 'vue'
export default {
render() {
return h('div')
}
}
2
3
4
5
6
7
8
使用 h
函数我们可以轻易的将Vue组件转为 dom
。
import { h, render } from 'vue'
// 传入 Message 组件
import Message from './components/Message.vue'
function createEl (Component, props, children) {
const vnode = h(Component, { ...props }, children)
render(vnode, document.createElement('div'))
return vnode.el
}
const el = createEl(Message)
document.body.appendChild(el)
2
3
4
5
6
7
8
9
10
11
12
13
14
# 12. 自定义指令的生命周期进行了更改
这样一来,指令的生命周期和组件的生命周期 是相似的, 也是更加好理解的。
Vue2.X指令生命周期 | Vue3.X对应的指令生命周期 |
---|---|
bind | beforeMount |
inserted | mounted |
新的 | beforeUpdate |
update | 移除了 |
componentUpdated | updated |
新的 | beforeUnmount |
unbind | unmounted |
当然, vue3.x
的更改不止这些,还有很多很多细节的东西。之后会详细记录我学习这些细节的东西。