最近一直有人在问我vue2和vue3有什么区别,今天在这里简单说哈。
根节点
vue2
<template>
<div id="app">
...
</div>
</template>
vue3
vue3中可以存在多个节点
<template>
<div>...</div>
<a>...</a>
<p>...</p>
</template>
全局属性
vue2
- 对于一些第三方插件,vue2中通常使用prototype原型来挂载到vue对象中
import Vue from 'vue'
Vue.prototype.$http=Axiox
Vue.prototype.$echart= Echart
vue3
- vue3中提供了一个名为globalProperties的全局属性配置,可以代替vue2中的prototype
app.config.globalProperties.$http = Axios
app.config.globalProperties.$echart = Echart
- 使用$http
import {getCurrentInstance} from 'vue'
setup () {
const { ctx } = getCurrentInstance();
onMounted(() => {
console.log(ctx.$http)
})
.......
}
异步组件
vue2
component: () => import('@/views/homePage/index.vue'),
vue3
在vue3中引入了一个新的方法 --->defineAsyncComponent定义异步组件,来包裹vue2引入方式里面的内容
import { defineAsyncComponent } from 'vue'
component: defineAsyncComponent(() => import('./NextPage.vue'))
建立data
vue2
export default {
props: {
title: String
},
data () {
return {
username: '',
password: ''
}
}
}
vue3
使用reactive()
方法来声名我们的数据为反应性数据
使用setup()
方法来返回我们的反应性数据,从而我们的template可以获取这些反应性数据
import { reactive } from 'vue'
export default {
props: {
title: String
},
setup () {
const state = reactive({
username: '',
password: ''
})
return { state }
}
}
data渲染
vue2
直接写入data变量渲染
<div>Values: {{ username + ' ' + password }}</div>
export default {
props: {
title: String
},
data () {
return {
username: 'hu',
password: '123'
}
}
}
vue3
vue3中的反应数据(Reactive Data)是包含在一个反应状态(Reactive State)变量中。所以我们需要访问这个反应状态来获取数据值。
<div>Values: {{ state.username + ' ' + state.password }}</div>
import { reactive } from 'vue'
export default {
props: {
title: String
},
setup () {
const state = reactive({
username: 'hu',
password: '123'
})
return { state }
}
}
还可以这样写,引入toRefs
<div>Values: {{ username + ' ' + password }}</div>
import { reactive ,toRefs} from 'vue'
export default {
props: {
title: String
},
setup () {
const state = reactive({
username: 'hu',
password: '123'
})
return { ...toRefs(state) }
}
}
methods
vue2
export default {
//....
methods: {
login () {
// 登陆方法
}
}
}
vue3
我们需要先声名一个方法然后在setup()
方法中返回(return
),这样我们的组件内就可以调用这个方法了。
export default {
//...
setup () {
const login = () => {
// 登陆方法
}
return {
login,
}
}
}
生命周期钩子
vue2
export default {
//...
mounted () {
console.log('组件已挂载')
},
methods: {
login () {
// login method
}
}
}
vue3
在vue3 生周期钩子不是全局可调用的了,需要另外从vue中引入。和刚刚引入reactive
一样,生命周期的挂载钩子叫onMounted
。
引入后我们就可以在setup()
方法里面使用onMounted
挂载的钩子了。
import { reactive, onMounted } from 'vue'
export default {
setup () {
// ..
onMounted(() => {
console.log('组件已挂载')
})
// ...
}
}
Computed
vue2
export default {
// ..
computed: {
num () {
return 2 + 3
}
}
}
vue3
vue3 的设计模式给予开发者们按需引入需要使用的依赖包。这样一来就不需要多余的引用导致性能或者打包后太大的问题。Vue2就是有这个一直存在的问题。
在vue3 使用计算属性,我们先需要在组件内引入computed
import { reactive, onMounted, computed } from 'vue'
export default {
props: {
title: String
},
setup () {
const state = reactive({
a: 2,
b: 3,
num: computed(() => state.a + state.b)
})
// ...
}
Props
vue2
export default {
props: {
title: String
},
mounted () {
console.log('title: ' + this.title)
}
}
vue3
在vue3 中,this
无法直接拿到props属性,emit events(触发事件)和组件内的其他属性。不过全新的setup()
方法可以接收两个参数
props
- 不可变的组件参数
content
- Vue3 暴露出来的属性(emit,slots,attrs)
setup (props) {
// ...
onMounted(() => {
console.log('title: ' + props.title)
})
// ...
}
emit
vue2
login () {
this.$emit('login', {
username: this.username,
password: this.password
})
}
vue3
在 Vue3中,我们刚刚说过this已经不是和vue2代表着这个组件了,所以我们在setup()中的第二个参数content对象中就有emit,这个是和this.$emit是一样的。那么我们只要在setup()接收第二个参数中使用分解对象法取出emit就可以在setup方法中随意使用了。
setup (props, { emit }) {
// ...
const login = () => {
emit('login', {
username: state.username,
password: state.password
})
}
// ...
}
数据绑定
vue2 数据劫持
核心方法: Object.defineProperty
H5方法,所以不兼容IE8以下
初始化Vue时,对data进行了劫持,每个属性都通过Object.defineProperty变成了getter/setter,一旦数据发生改变,就会触发set,然后去更新view
let obj = {},
value = 1
Object.defineProperty(obj,'a',{
get() {
console.log('这里监听到了数据获取')
return value
},
set(newValue, value) {
if(newValue !== value) {
value = newValue
console.log('这里监听到了数据更改')
}
}
})
console.log(obj.a) // 这里监听到了数据获取 1
obj.a = 2 // 这里监听到了数据更改
vue3
Object.defineProperty
有一些缺陷,不仅要遍历data逐个劫持,还不能监听到数组的改变,所以VUE3使用了ES6的Proxy
let data = {
msg: {
a: 10
},
arr: [1, 2, 3]
}
let handler = {
get(target, key) {
// 懒监听,去获取的时候才监听对象里面的对象,而不是直接递归循环监听
console.log('获取key: ' + key)
if (typeof target[key] === 'object' && target[key] !== null) {
return new Proxy(target[key], handler)
}
return Reflect.get(target, key)
},
set(target, key, value) {
let oldValue = target[key]
console.log('更新key: ' + key)
if (oldValue !== value) {
// 通知view更新
}
return Reflect.set(target, key, value)
}
}
let proxy = new Proxy(data, handler)
proxy.arr.push(4)
输出结果
为什么每次都有length,其实Proxy的监听数组实现是把数组变成了一个类数组对象
let arr = {
'0': 1,
'1': 2,
length: 2
}
Proxy除了get,set还有deleteProperty/apply/getOwnPropertyDescriptor等等12个方法,恰好与Reflect对应,所以在这些方法里面可以实现拦截器
set(target, key, value) {
if(key[0] === '_') {
throw new Error('这是私有变量,不能更改')
}
return Reflect.set(target, key, value)
}