Vue3 快速上手
注意:本文可能为了突出重点,有些例子是代码片段,而不是完整的代码。
改变❗
v-on
不再支持使用数字作为修饰符。❌$on
、$off
和$once
实例方法被移除。❌- 过滤器
filters
移除不再支持,建议使用 计算属性 或 方法代替。❌ <transition-group>
不再默认渲染根元素,但仍可以使用tag
prop创建一个根元素。template
支持在v-for
时绑定key
。$attr
已包含class
和style
属性。- 过渡
class
名修改。v-enter
改为v-enter-from
,v-leave
改为v-leave-from
。在<transition>
组件leave-class
改为leave-from-class
或leaveFromClass
,enter-class
改为enter-from-class
或enterFromClass
。 - 在 vue 2.x 中
v-for
比v-if
优先。vue 3.x 版本中 相反。 v-modal
prop 和 事件默认名称 分别修改为modalValue
和update:modalValue
。.sync
修饰符和 组件的modal
选项移除,随之代替的是:title.sync="xxx"
等价于v-modal:title="xxx"
。
生命周期
生命周期选项被重命名为destroyed
unmounted
生命周期选项被重命名为beforeDestroy
beforeUnmount
Vue2 | Vue3 |
---|---|
beforeCreate | setup |
created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeDestory | onBeforeUnmount |
destoryed | onUnmounted |
注意:setup
在 vue3 版本代替了 beforeCreate
和 created
,可以理解为 setup
执行在它们之间。
同时 setup
支持两个参数,分别为 props
和 context
。注意事项,代码中有注释。而且 setup
内部是没有 this
,打印出来是 undefined
。因此不能像 Vue 2.x 那样,通过 this.data
这种写法,所以创建了新的API,下文有说到。
setup
支持 return
一个对象,它可供模板直接使用,例如 return { count }
则 <div></div>
// .vue文件代码片段
<script>
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from "vue";
export default {
name: "Life",
props: {
title: String,
},
/**
* @description:
* @param {object} props 不能使用ES6解构,否则会破坏响应式。可以使用 toRef() 进行解构
* @param {object} context 非响应式,可使用ES6解构,包含attr、emit、slots
* @return {*}
*/
setup(props, context) {
// 注意:在 setup() 内部,this 不会是该活跃实例的引用,因为 setup() 是在解析其它组件选项之前被调用的,所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这在和其它选项式 API 一起使用 setup() 时可能会导致混淆。
onBeforeMount(() => {
console.log(props);
console.log(context);
console.log("传入的title是" + props.title);
console.log("挂载前执行");
});
onMounted(() => {
console.log("挂载后执行");
});
onBeforeUpdate(() => {
console.log("更新前执行");
});
onUpdated(() => {
console.log("更新后执行");
});
onBeforeUnmount(() => {
console.log("卸载前执行");
});
onUnmounted(() => {
console.log("卸载后执行");
});
return {}
}
}
</script>
获取标签元素
在 Vue2 中,我们获取元素都是通过给元素一个 ref
属性,然后通过 this.$refs.xx
来访问的,但这在 Vue3 中已经不再适用了。
Vue 3 版本获取元素标签,有点类似 React 的感觉。
<template>
<div>
<div ref="el">div元素</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
export default {
setup() {
// 创建一个DOM引用,名称必须与元素的ref属性名相同
const el = ref(null)
// 在挂载后才能通过 el 获取到目标元素
onMounted(() => {
el.value.innerHTML = '内容被修改'
})
// 把创建的引用 return 出去
return {el}
}
}
</script>
注意
在 Vue 2.x 版本,可以使用 this.$children
直接访问当前实例的子组件。在 Vue 3版本中移除,建议使用 $refs
。
Vue 2.x中,在 v-for
中使用 ref
是返回数组形式的 $refs
,而在 Vue 3 中不再是创建一个 $refs
数组,而是需要自己 从单个 ref
绑定获取多个 ref
。
<template>
<div v-for="item for list" :key="item" :ref="setItemsRef"></div>
</template>
<script>
export default {
data() {
return {
itemRefs: []
}
},
methods: {
setItemsRef(el) {
el && (this.itemRefs.push(el))
}
},
beforeUpdate() {
this.itemRefs = []
}
}
</script>
自定义指令
在 vue 3中主要改变的是自定义指令的钩子函数(为了与生命周期统一)。
Vue2 | Vue3 | 解析 |
---|---|---|
bind | beforeMount | 指令绑定到元素后发生。只发生一次。 |
inserted | mounted | 元素插入父 DOM 后发生 |
/ | beforeUpdate | 新的!这是在元素本身更新之前调用的 |
update | 移除 | 当元素更新,但子元素尚未更新时,将调用此钩子 |
componentUpdated | updated | 一旦组件和子级被更新,就会调用这个钩子 |
/ | beforeUnmount | 新的!它将在卸载元素之前调用。 |
unbind | unmounted | 一旦指令被移除,就会调用这个钩子。也只调用一次 |
<template>
<input v-focus />
</template>
<script>
export default {
directives: {
focus: {
mounted(el) {
el.focus();
}
}
}
}
</script>
Data
data
组件选项声明不再接收纯 object
,而需要 function
声明。
(为了根实例与vue模板声明同步)
// Object 声明,Vue3中不适用
const app = new Vue({
data: {
a: 1
}
})
// 而是全部改为 function 声明
const app = new Vue({
data() {
return {
a: 1
}
}
})
注意
在 Vue 3 中 Mixin 合并的时候,data
不再是深层合并,而是浅层合并。
对之前版本项目深层合并影响有点大。
const Mixin = {
data() {
return {
a: {
b: 1,
c: 2
}
}
}
}
const Comp = {
mixins: [Mixin],
data() {
return {
a: {
b: 2
}
}
}
}
// 在vue 2.x中,合并后的data
{
a: {
b: 2,
c: 2
}
}
// 在vue 3中,合并后的data
{
a: {
b: 2
}
}
🆕emits
Vue3 新增一个emits
选项,类似组件中的 props
选项,用来定义组件可以向其父对象发出的事件。
支持数组形式,也支持一个对象形式,定义传入事件参数的验证器。
简单来说就是声明哪些事件可以给父组件,强烈建议使用。
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ['text'],
emits: ['accepted']
}
</script>
注意
❗❗ Vue 3 中已移除 .native
修饰符,一切未在 emits
中声明的事件监听器都算入 $attr
,且默认绑定到 组件根节点上。
<!-- app.vue -->
<template>
<my-button @click="handleTap" />
</template>
<script>
export default{
methods: {
handleTap(e) {
// 会输出两次,一次是原生事件监听器监听到,一次是 $emit 触发
console.log(e)
}
}
}
</script>
<!-- MyButton.vue -->
<template>
<div>
<button @click="$emit('click', $event)">我的按钮</button>
</div>
</template>
模板
在 Vue 2.x 中 是不支持多个根节点的,往往需要我们在外层使用 div
包裹。
🆕**在 Vue 3支持多个根节点了,同时需要我们显式定义 $attr
分布 **。
<!-- vue 2.x -->
<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
<!-- vue 3 支持-->
<template>
<header>...</header>
<main v-bind="$attr">...</main>
<footer>...</footer>
</template>
创建一个实例⁉️
在 Vue 2.x 版本只支持全局导入,进而很多时候一些没用到的模块也进行导入,导致项目体积增大。 而在 Vue 3.x 版本支持按需导入。
// 3.x
import { createApp } from 'vue'
// 允许分开,也允许链式
createApp(App).mount('#app')
// 2.x
import Vue from 'vue'
new Vue({
el: '#app',
render: h => h(App),
store,
router
})
在Vue 2.x 版本中,当我们要使用全局API 的时候,往往我们是这么做👇
Vue.mixin(...)
Vue.directive(...)
// 但这样如果有两个根实例话,则会互相污染
const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })
而在 Vue3 版本中,任何全局改变 Vue 的行为都移动到实例上。
import { createApp } from 'vue'
const app = createApp(App).mount('#app')
app.mixin(...)
app.directive(...)
2.x 全局 API | 3.x 实例 API (app ) |
---|---|
Vue.config | app.config |
Vue.config.productionTip | removed |
Vue.config.ignoredElements | app.config.isCustomElement |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
TIP
vue3中,如果想要在多个应用中共享配置,可以使用工厂函数来创建实例。
const createApp = options => {
const app = createApp(options)
app.directive(...)
return app
}
Vue.prototype
替换为 config.globalProperties
// 在 vue 2.x 中,往往我们这样使用来达到可以在每个组件访问某个 property
Vue.prototype.$xxx = xxx
// 在 vue 3.x 版本中修改为
const app = createApp({})
app.config.globalProperties.$xxx = xxx
渲染函数
在 2.x 中, render
函数将自动接收 h
函数 (它是 createElement
的常规别名) 作为参数:
// Vue 2 渲染函数示例
export default {
render(h) {
return h('div')
}
}
在 3.x 中,h
现在是全局导入的,而不是作为参数自动传递。
// Vue 3 渲染函数示例
import { h } from 'vue'
export default {
render() {
return h('div')
}
}
挂载方式改变
// in vue 2.x
new Vue({
template: <div>这是一个模板</div>
}).$mount('#app')
// 模板代替了 挂载DOM
// 结果会渲染为
<div>这是一个模板</div>
// in vue 3.x
Vue.createApp({
template: <div>这是一个模板</div>
}).$mount('#app')
// 可以理解为在挂载的 DOM 上使用了 innerHTML
// 结果会渲染为
<div id="app">
<div>这是一个模板</div>
</div>
模板语法🈚
模板语法与2.x一致。
附:2.6.0版本新增动态参数
<a v-bind:[attributeName]="url"> ... </a>
// attributeName 会被动态求值,若值为null或一切非字符串数据最终都会报警告
// 建议使用计算属性, 避免使用大写字符来命名键名
新增API⁉️
reactive
返回对象的响应式副本,它是深层的转换。
一般用于引用类型object、array。
(因为
proxy
转换只会对当前的对象其作用,而嵌套的对象时不会发生响应式的,则需要遍历。因此这个 API也是这个原理,遍历对象中的所有属性) 详细可以查看一下我之前写的proxy实现响应式原理提醒:Vue 2.x 版本使用的是
Object.defineProperty
来实现响应式
import { reactive } from 'vue'
const obj = {
a: 1,
b: {
c: 2,
d: {
e: 3,
}
}
}
const state = reactive(obj)
console.log(state)
console.log(state.b)
console.log(state.d)
// 结果如下
readonly
获取一个对象 (响应式或纯对象) 或 ref
并返回原始 proxy
的只读 proxy
。它也是深层的,但都只是只读。
<template>
<div>
<div>可读写{{ state.a }}</div>
<div>只读{{ readState.a }}</div>
<!-- 当你点击 修改可读写读 按钮,两个div都会变化,当你点击 修改只读,控制台会报一个警告 Set operation on key "a" failed: target is readonly. -->
<button @click="add">修改可读写读</button>
<button @click="add2">修改只读</button>
</div>
</template>
<script>
import { readonly, reactive } from "vue";
export default {
setup() {
const state = reactive({ a: 1 });
const readState = readonly(state);
function add() {
state.a++;
}
function add2() {
readState.a++;
}
return {
state,
readState,
add,
add2
};
},
};
</script>
isProxy
检查对象是否是由 reactive
或 readonly
创建的 proxy。
isReactive
检查对象是否是 reactive
创建的响应式 proxy。
注意
如果 proxy
是 readonly
创建的,并且 readonly
是由普通对象创建的,则返回 false
,若 readonly
是由响应式 proxy
创建的,则为 true
const state = reactive({ a: 1 })
const demo1 = readonly({ a: 2 })
console.log(isReactive(state)) // true
console.log(isReactive(demo1)) // false
console.log(isReactive(readonly(state))) // true
isReadonly
检查对象是否是由 readonly
创建的只读 proxy。
toRaw
返回 reactive
或 readonly
的原始对象。即改为非响应式,谨慎使用。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo)
markRaw
标记一个对象,使其永远不会转换为 proxy
。返回对象本身。即退出响应式,深层的。
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false
shallowReactive
创建一个响应式 proxy
,跟踪其自身 property
的响应性,但不执行嵌套对象的深度响应式转换 。简单来说就是浅层。就是我一开始说的 reactive
需要遍历,则它就不遍历。
<template>
<div>
<p>响应式a:{{ state2.a }}</p>
<p>非响应式b:{{ state2.b.c }}</p>
<p>
首先点击修改非响应式,点击后视图并没有发生修改,只有点击修改响应式后视图才监听到修改
</p>
<button @click="add">修改响应式</button>
<button @click="add2">修改非响应式</button>
</div>
</template>
<script>
import { reactive, shallowReactive, toRaw } from "vue";
export default {
setup() {
const obj = {
a: 1,
b: {
c: 2,
d: {
e: 3
}
}
}
const state2 = shallowReactive(obj); // shallowReactive 接受一个参数,使之成为响应式对象,但是不执行深度响应式转换,即嵌套的对象不做proxy。其实了解proxy就明白reactive 和 shallowReactive的区别
console.log(state2);
console.log(state2.b);
console.log(state2.b.d);
function add() {
state2.a++;
}
function add2() {
state2.b.c++;
// 当触发这个的时候,值已经改变了,但 shallowReactive 只会把自身属性改为响应式,而嵌套属性是非响应式的。
console.log(state2);
}
return {
state2,
add,
add2,
};
},
};
</script>
打印如下:
shallowReadonly
创建一个 proxy,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换。和上面同理,都是浅层的。
ref
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值的单个 property .value
。简单来说就是用来包装一个值成为响应式。
注意
在 setup
内部使用的时候,需要显示加上 .value
。但在模板中不需要。
可以理解为 ref(any) => reactive({value: any})
。
一般用于基本类型string、number、boolean。
<!--
* @Author: kim
* @Date: 2020-11-30 15:08:04
* @LastEditors: kim
* @LastEditTime: 2020-11-30 16:51:21
* @Description:
-->
<template>
<div>
<p>{{ count }}</p>
<button @click="add">点击</button>
</div>
</template>
<script>
import {
ref,
reactive,
} from "vue";
export default {
setup(props, context) {
// ref 和 reactive 都是用来包装数据为一个响应式
const count = ref(0); // 每次访问的时候需要加上 .value,但在模板中不需要。一般用于基本类型string、number、boolean。
const add = () => {
count.value++;
}
return { count, add };
},
};
</script>
toRef
可以用来为源响应式对象上的 property 新创建一个 ref
。然后可以将 ref
传递出去,从而保持对其源 property 的响应式连接。
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
为什么会有这个API呢?
在 setup
中有提到,props
不能使用 ES6 进行解构,因为会破坏其响应式。不妨碍我们想一下解构的原理,解构是复制出来,即破坏了引用关系。
这个 API 就是用来代替解构,并且保持响应式的。
toRefs
将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的ref
。很好理解,toRef
对单属性,toRefs
对整个对象。简单来说就是原本是对象可以响应式的,后来对象不是响应式,但其下所有属性都是响应式,同时返回的对象支持 ES6 解构。
我们回想一下 reactive
打印出来是怎么样的👇
通过 toRefs
转换后👇
const obj = {
a: 1,
b: {
c: 2,
d: {
e: 3,
},
},
}
const state = reactive(obj)
const state2 = toRefs(state)
console.log(state2);
shallowRef
shallowReactive
是监听对象第一层的数据变化用于驱动视图更新,那么 shallowRef
则是监听 .value
的值的变化来更新视图的。
<template>
<p>{{ state.a }}</p>
<p>{{ state.first.b }}</p>
<p>{{ state.first.second.c }}</p>
<!-- 点击改变2,你会发生先数据变了,但视图没有改变,只有点击改变1,视图才发生变化 -->
<button @click="change1">改变1</button>
<button @click="change2">改变2</button>
</template>
<script>
import {shallowRef} from 'vue'
export default {
setup() {
const obj = {
a: 1,
first: {
b: 2,
second: {
c: 3
}
}
}
const state = shallowRef(obj)
console.log(state);
function change1() {
// 直接将state.value重新赋值
state.value = {
a: 7,
first: {
b: 8,
second: {
c: 9
}
}
}
}
function change2() {
state.value.first.b = 8
state.value.first.second.c = 9
console.log(state);
}
return {state, change1, change2}
}
}
</script>
triggerRef
手动执行与 shallowRef
关联的任何效果。简单来说就是手动刷新。
如上面那个例子中,我们在change2
加入一句代码 triggerRef()
视图也会发生改变。
provide&inject
与 Vue 2.x 中的 provide
和 inject
作用相同。只不过 Vue 3 支持了组合式API,所以有了新的写法。
provide&inject 支持在组件中传递数据。众所周知父组件传递到子组件是使用 props,但如果嵌套这很深的组件,我们一层一层传递下去则非常烦人。
因此provide&inject 允许我们在父组件提供数据,所有子组件都可以获取到这个数据,不管层次有多深,且无需一层层传递下去。
但不要过度使用,否则会很难追踪到数据来源。当数据量多的时候建议使用 vuex 代替
<!-- 在vue 2.x中 -->
<!-- 在父组件或祖先组件中注入 -->
<script>
export default {
provide: {
location: 'North Pole',
geolocation: {
longitude: 90,
latitude: 135
}
}
}
</script>
<!-- 在子组件或子孙组件中使用 -->
<script>
export default {
inject: ['location', 'geolocation']
}
</script>
在 Vue3
中允许我们显示导入 provide
方法和 inject
方法。
provide
方法,接受两个参数name
键名value
值inject
方法,也接受两个参数name
键名(provide设置的)defaultValue
默认值(可选)
<!-- 比如现在有三个组件 Father.vue、Son.vue、Grandson.vue -->
<!-- Father.vue -->
<template>
<div>
<h1>我是Father</h1>
<Son/>
</div>
</template>
<script>
import { provide } from "vue";
import Son from "./Son.vue";
export default {
setup() {
provide("title", "hello world");
return {}
},
components: {
Son
}
};
</script>
<!-- Son.vue -->
<template>
<div>
<h2>我是Son</h2>
<Grandson />
</div>
</template>
<script>
import Grandson from "./Grandson.vue";
export default {
components: {
Grandson
}
};
</script>
<!-- Grandson.vue -->
<template>
<div>
<h2>我是Grandson</h2>
<div>我是来自祖父的值---{{ title }}</div>
</div>
</template>
<script>
import { inject } from "vue";
export default {
setup() {
const title = inject("title", "这是默认值");
return { title };
},
};
</script>
效果如下👇
watch&watchEffect
watch
和 watchEffect
都是用来监视某项数据变化从而执行指定的操作的,但用法上还是有所区别。
watch:watch( source, cb, [options] )
参数说明:
source
:可以是表达式或函数,用于指定监听的依赖对象cb
:依赖对象变化后执行的回调函数options
:可参数,可以配置的属性有 immediate(立即触发回调函数)、deep(深度监听)
Tip
watch
API 与 this.$watch
、watch
选项是同一样东西,只是新语法而已。
默认 watch
方法默认是浅层的监听我们指定的数据,即如果监听的数据是嵌套多层,则深层的数据改变并不会触发监听回调,如果想要监听到,则需要传入第三个参数 deep
。
- 与
watchEffect
比较,watch
允许我们:- 惰性地执行副作用;
- 更具体地说明应触发侦听器重新运行的状态;
- 访问侦听状态的先前值和当前值。
watch
方法会返回一个stop
方法,若想要停止监听,便可直接执行该stop
函数
// 当监听 ref 类型时
setup() {
const state = ref(0)
watch(state, (newValue, oldValue) => {
console.log(`原值为${oldValue}`)
console.log(`新值为${newValue}`)
})
}
// 监听 reactive 类型时
setup() {
const state = reactive({cout: 0})
watch(() => state.count, (newValue, oldValue) => {
console.log(`原值为${oldValue}`)
console.log(`新值为${newValue}`)
})
}
// 监听多个值的时候
setup() {
const state = reactive({cout: 0, a: 2})
watch([() => state.count, () => state.a], ([newCount, newA], [oldCount, oldA]) => {
console.log(oldCount)
console.log(newCount)
console.log(oldA)
console.log(newA)
})
}
watchEffect
,它与 watch
的区别主要有以下几点:
- 不需要手动传入依赖
- 每次初始化时会执行一次回调函数来自动获取依赖
- 无法获取到原值,只能得到变化后的值
简单来说它会自动搜索监听回调中的依赖项,只要依赖项发生改变则会自动触发。
<script>
import {reactive, watchEffect} from 'vue'
export default {
setup() {
const state = reactive({ count: 0 })
watchEffect(() => {
// 比如在这里它会自动监听state.count
console.log(state.count)
})
setTimeout(() => {
state.count ++
}, 1000)
}
}
</script>
getCurrentInstance
因为 setup
函数中 this
的指向是 undefined
,但我们在 Vue 2.x中经常使用的 this
,代表当前组件实例没有了吗?
getCurrentInstance
就是返回当前组件实例的。但不建议使用。
const instance = getCurrentInstance()
console.log(instance)
BUG❗
当你在测试环境下,console.log
打印 const { ctx } = getCurrentInstance()
,大概是长这样的👇
然而当你打包后,在正式环境下打印,却长这样👇
然后接下来就是把 ctx
当成 this
使用的代码一顿报错。
临时解决方案
使用
const { proxy } = getCurrentInstance()
来代替ctx
,但还是建议不要使用getCurrentInstance
useStore
在 Vue2 中使用 Vuex
,有时候通过 this.$store
来与获取到 Vuex
实例,但上面也说了原本 Vue2 中的 this
的获取方式不一样了,并且在 Vue3 的 getCurrentInstance().ctx
中也没有发现 $store
这个属性,那么如何获取到 Vuex
实例呢?这就要通过 vuex
中的一个方法了,即 useStore
。
// store 文件夹下的 index.js
import Vuex from 'vuex'
const store = Vuex.createStore({
state: {
name: '至安'
},
mutations: {
……
},
……
})
// example.vue
<script>
// 从 vuex 中导入 useStore 方法
import {useStore} from 'vuex'
export default {
setup() {
// 获取 vuex 实例
const store = useStore()
console.log(store)
}
}
</script>