2020年9月18日,Vue.js发布了3.0版本。时隔一年多,首次体验使用vue3进行项目开发。主要因为此时vue2仍是主流,虽然知道vue3将是未来的趋势,但一个好的产品是需要经过市场的不断打磨,才能以最完美的状态呈现在大众的面前。
一、vu3安装
1、使用 vue-cli 创建工程
官方提供的@vue/cli
在4.5.0版本之后提供了vue3脚手架的快速安装。官方文档
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue3-demo
## 启动
cd vue3-demo
npm run serve
2、使用 vite 创建工程
vite
由vue团队打造,号称下一代前端开发与构建工具,比传统的构建工具更快更轻量。官方文档
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
二、组合式 API (Composition API)
vue3相较于vue2是属于一个大的版本升级,源码进全面重写,除了性能上的提升,还带来了一些新的特性(组合式api、新的内置组件)。最重要的是vue3还向下兼容,vue2中配置项(OptionsAPI
)的写法仍然支持。
1、Setup 函数
setup
是vue3中新增的配置项,为一个函数。它是所有组合式api的基础。
setup函数有两种返回值:
- 返回一个对象,对象中的属性、方法, 在模板中均可以直接使用。
- 返回一个渲染函数,可以自定义渲染内容。
<template>
<div>{{name}}</div>
<button @click="printInfo">打印信息</button>
</template>
<script>
export default {
setup() {
let name = 'bandianjuse';
let printInfo = () => {
console.log('hello vue3!')
}
// 返回一个对象,对象中的属性、方法, 在模板中均可以直接使用。
return {
name,
printInfo
}
},
}
</script>
import { h } from 'vue'
export default {
name: 'App',
setup() {
// 返回一个渲染函数,可以自定义渲染内容。
return () => h('div', ['hello vue3!'])
},
}
setup函数接收两个参数:
props
组件传入的属性。context
上下文对象,包含了一些有用的属性,如attrs、slots、emit等。
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
},
}
export default {
setup(props, context) {
// Attribute (非响应式对象,等同于 $attrs)
console.log(context.attrs)
// 插槽 (非响应式对象,等同于 $slots)
console.log(context.slots)
// 触发事件 (方法,等同于 $emit)
console.log(context.emit)
// 暴露公共 property (函数)
console.log(context.expose)
}
}
注意
-
使用配置项(data、methos、computed...)可以访问setup中的返回的属性、方法,但setup不能访问到配置项(data、methos、computed...)
-
setup函数内部
this不是该活跃实例的引用
,无法同配置项一样使用this。
2、ref、reactive、toRef、toRefs函数
ref
函数用来定义一个响应式数据。
<template>
<!-- 模板中读取数据不需要.value -->
<div>{{counter}}</div>
<button @click="add">点击增加</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const counter = ref(0); // 接收的数据可以是:基本类型、也可以是对象类型。
console.log(counter) // { value: 0 }
console.log(counter.value) // 0
const add = () => {
counter.value++; // 操作数据需要 counter.value
}
return {
counter,
add
}
},
}
</script>
reactive
函数定义一个对象类型
的响应式数据(基本类型不要用它,要用ref
函数)。
<template>
<div>名称:{{person.name}}</div>
<div>年龄:{{person.age}}</div>
<button @click="addAge">点击增加年龄</button>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
// reactive接收一个对象(或数组),返回一个proxy对象
const person = reactive({
name: 'bandianjuse',
age: 18
});
const addAge = () => {
person.age++;
}
return {
person,
addAge
}
},
}
</script>
toRef
函数创建一个 ref 对象,其value值指向另一个对象中的某个属性。主要应用在将响应式对象中的某个属性单独提供给外部使用时。toRefs
函数与toRef函数行为一致,可以批量创建多个 ref 对象
<template>
<div>名称:{{name}}</div>
<div>年龄:{{age}}</div>
<div>工作岗位:{{post}}</div>
<div>工作年限:{{workingYears}}</div>
<button @click="addAge">点击增加年龄</button>
</template>
<script>
import { reactive, toRef, toRefs } from 'vue'
export default {
setup() {
const person = reactive({
name: 'bandianjuse',
age: 18,
job: {
post: '前端工程师',
workingYears: 10
}
});
const addAge = () => {
person.age++;
}
return {
name: toRef(person, 'name'),
age: toRef(person, 'age'),
// age: person.age, // 这种写法将会失去对数据的响应,数据更新了无法反馈到页面
...toRefs(person.job),
addAge
}
},
}
</script>
3、vue3响应式原理
vue2中对象的响应式是通过Object.defineProperty()
对属性的读取、修改进行拦截(数据劫持)。
而数组是通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
Object.defineProperty(data, 'count', {
get () {},
set () {}
})
这种方式存在一些问题
- 新增属性、删除属性, 界面不会更新。
- 直接通过下标修改数组, 界面不会自动更新。
当然vue2也提供了一些解决方案:使用用内置$set、$delete 方式来处理响应式数据。
vue3中通过Proxy(代理)
拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。然后通过Reflect(反射)
对源对象的属性进行操作。
new Proxy(data, {
// 拦截读取属性值
get (target, prop) {
return Reflect.get(target, prop)
},
// 拦截设置属性值或添加新属性
set (target, prop, value) {
return Reflect.set(target, prop, value)
},
// 拦截删除属性
deleteProperty (target, prop) {
return Reflect.deleteProperty(target, prop)
}
})
proxy.name = 'bandianjuse'
4、computed与watch函数
computed
函数与vue2中计算属性一样,根据依赖的值变化创建新的值。
<template>
<div>姓:{{ firstName }}</div>
<div>名:{{ lastName }}</div>
<div>全名:{{ fullName }}</div>
<button @click="updateName">改名换性</button>
</template>
<script>
import { reactive, computed, toRefs } from 'vue';
export default {
setup() {
const person = reactive({
firstName: '张',
lastName: '三'
});
//计算属性——简写
let fullName = computed(() => {
return person.firstName + '-' + person.lastName;
});
//计算属性——完整
/* let fullName = computed({
get() {
return person.firstName + '-'+ person.lastName;
},
set(value) {
const nameArr = value.split('-');
person.firstName = nameArr[0];
person.lastName = nameArr[1];
},
}); */
const updateName = () => {
person.firstName = '李';
person.lastName = '四'
}
return {
...toRefs(person),
fullName,
updateName
}
},
};
</script>
watch
函数与vue2中监听属性一样,用来响应数据的变化。
<template>
<div>{{ sum }}</div>
<div>{{ name }}</div>
<div>{{ person.job }}</div>
<button @click="updateData">更新数据</button>
</template>
<script>
import { ref, watch, reactive } from "vue";
export default {
setup() {
const sum = ref(0);
const name = ref("张三");
const person = reactive({
job: '前端'
})
// 监视ref定义的响应式数据
watch(
sum,
(newValue, oldValue) => {
console.log("sum变化了", newValue, oldValue);
},
{ immediate: true }
);
// 监视多个ref定义的响应式数据
watch([sum, name], (newValue, oldValue) => {
console.log("sum或msg变化了", newValue, oldValue);
});
// 监视reactive定义的响应式数据中的某些属性
watch(
() => person.job,
(newValue, oldValue) => {
console.log("person的job变化了", newValue, oldValue);
},
{ deep: true }
);
const updateData = () => {
sum.value++;
name.value = "李四";
person.job = "后端"
};
return {
sum,
name,
person,
updateData,
};
},
};
</script>
watchEffect
函数跟watch类似,但比watch更智能,它不需要指定监视哪个属性,只要回调中用到哪个属性,它就会监视哪个属性。
// watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。
watchEffect(()=>{
const s = sum.value
console.log('watchEffect配置的回调执行了', s)
})
5、vue3的生命周期钩子
vue3中的生命周期钩子基本上保持跟vue2一致,只是将beforeDestroy
改名为beforeUnmount
,destroyed
改名为unmounted
。
vue3也提供了 Composition API 形式的生命周期钩子,与vue2中钩子对应关系如下:
OptionsAPI | Hook inside setup |
---|---|
beforeCreate | Not needed* |
created | Not needed* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
activated | onActivated |
deactivated | onDeactivated |
计时器案例
// App.vue
<template>
<Child v-if="isUnmount"></Child>
<button @click="handleTimer">{{timeBtnStr}}</button>
</template>
<script>
import { computed, ref } from 'vue';
import Child from './components/Child'
export default {
name: 'App',
components: { Child },
setup() {
let isUnmount = ref(true);
let handleTimer = () => {
isUnmount.value = !isUnmount.value
}
const timeBtnStr = computed(() => {
return isUnmount.value ? '卸载计时器' : '启动计时器'
})
return {
isUnmount,
timeBtnStr,
handleTimer
}
},
}
</script>
// Child.vue
<template>
<div>计数器:{{ num }}</div>
</template>
<script>
import { ref, onBeforeUnmount, onMounted } from "vue";
export default {
setup() {
let num = ref(0);
let timer = null;
onMounted(() => {
console.log('onMounted');
timer = setInterval(() => {
num.value++
}, 1000)
})
onBeforeUnmount(() => {
console.log('onBeforeUnmount');
clearInterval(timer);
})
return {
num
};
},
};
</script>
6、自定义Hook函数
Hook
本质是一个函数,把setup函数中使用的Composition API进行了封装,提高了代码的复用性。类似于vue2的mixin
。
// App.vue
<template>
<div>总数:{{current}}</div>
<button @click="inc">加数</button>
<button @click="dec">减数</button>
<button @click="set(10)">设值</button>
<button @click="reset">重置</button>
</template>
<script>
import useCount from './hooks/useCount'
export default {
name: 'App',
setup() {
const { current, inc, dec, set, reset } = useCount(1, {
min: 1,
max: 15
})
return {
current,
inc,
dec,
set,
reset
}
},
}
</script>
// ------------------
// useCount.js
import { ref, watch } from 'vue'
/**
* 计数器
* @param {number} initialVal 初始值
* @param {Object} range { min: number, max: number }限制范围
* @returns
*/
export default function useCount(initialVal, range) {
const current = ref(initialVal);
// 数值加
const inc = (data) => {
if (typeof data === 'number') {
current.value += data
} else {
current.value += 1
}
}
// 数值减
const dec = (data) => {
if (typeof data === 'number') {
current.value -= data
} else {
current.value -= 1
}
}
// 设置值
const set = (value) => {
current.value = value
}
// 重置值
const reset = () => {
current.value = initialVal
}
// 监听值变化,限定在范围内
watch(current, (newVal, oldVal) => {
if (newVal === oldVal) return
if (range && range.min && newVal < range.min) {
current.value = range.min
} else if (range && range.max && newVal > range.max) {
current.value = range.max
}
})
return {
current,
inc,
dec,
set,
reset
}
}
7、组合式 API 的优势
使用传统Options API
中,新增或者修改一个需求,就需要分别在data,methods,computed里修改。而使用Composition API
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起。
Options API 演示图
Composition API 演示图
从演示图中可以看出Composition API结合Hook函数,将单一功能进行拆分,进一步提高了代码的复用率,使我们可以写出更加清楚优雅的代码。这就是vue3核心思想所在。
三、vue3新增组件
1、Teleport
Teleport
是一种能够将我们的组件html结构移动到指定位置的技术。
借用这种技术,我们可以解决多层级组件嵌套的定位问题,例如封装modals,toast 类型的组件,使用teleport非常方便。
<!-- to: 移动的位置 -->
<teleport to="body">
<div v-if="isShow" class="mask">
<div class="dialog">
<h3>我是一个弹窗</h3>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>
2、Fragment
在发vue2中,组件是必须有一个根标签的。但在vue3中,组件可以没有根标签,因为其内部会将多个标签包含在一个Fragment
虚拟元素中。Fragment在真实DOM中是不存在的,这样的做法可以有效的减少标签的层级,减少内在占用。
<template>
<Fragment>
<div>总数:{{ current }}</div>
<button @click="inc">加数</button>
<button @click="dec">减数</button>
<button @click="set(10)">设值</button>
<button @click="reset">重置</button>
</Fragment>
</template>
3、Suspense
Suspense
组件用来包裹一个异步组件,在等待异步组件时可以渲染一些额外内容,让应用有更好的用户体验。
// 引入异步组件
import {defineAsyncComponent} from 'vue'
const Child = defineAsyncComponent(()=>import('./components/Child.vue'))
// 使用Suspense组件包裹,并配置好`default` 与 `fallback`插槽
<template>
<div class="app">
<h3>我是App组件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加载中.....</h3>
</template>
</Suspense>
</div>
</template>
四、一些全局api和配置的调整
vue2中有许多全局 API 和配置在vue3中做了调整,将Vue.xxx
调整到应用实例(app
)上:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App).mount('#app');
调整对照表:
vue2 全局 API(Vue ) | vue3 实例 API (app ) |
---|---|
Vue.config.xxxx | app.config.xxxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |