Vue3 和Vue2的区别
代理拦截
在vue2的时代,vue2的响应机制是依赖于Object.defineProperty()的这个API实现的,Vue3则是使用了Proxy。
let getDouble = n=>n*2
let obj = {}
let count = 1
let double = getDouble(count)
Object.defineProperty(obj,'count',{
get(){
return count
},
set(val){
count = val
double = getDouble(val)
}
})
console.log(double) // 打印2
obj.count = 2
console.log(double)
前者是通过对对象的get和set进行读写拦截,而后者是创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
光看上面的声明可能觉得都差不多,我们着重看一下他们之间的区别。
- defineProperty 对不存在的属性无法拦截,所以 Vue 2 中所有数据必须要在 data 里声明。
- 当被代理的是一个数组的时候,defineProperty对数组的长度的修改等操作还是无法实现拦截,所以还需要额外的 $set 等 API。官方提供了
push(),pop()shift()unshift()splice()sort()reverse()等方法来操作数组。
Proxy就解决了以上的问题:
let proxy = new Proxy(obj,{
get : function (target,prop) {
return target[prop]
},
set : function (target,prop,value) {
target[prop] = value;
if(prop==='count'){
double = getDouble(value)
}
},
deleteProperty(target,prop){
delete target[prop]
if(prop==='count'){
double = NaN
}
}
})
console.log(obj.count,double)
proxy.count = 2
console.log(obj.count,double)
delete proxy.count
// 删除属性后,我们打印log时,输出的结果就会是 undefined NaN
console.log(obj.count,double)
只要是对{}这个属性的修改,Proxy都会进行拦截。至于具体是什么属性,Proxy 则不关心,统一都拦截了。而且 Proxy 还可以监听更多的数据格式,比如 Set、Map,这是 Vue 2 做不到的。
当然了,现在Proxy也会有一些缺点,那就是兼容性问题,不支持IE11以下的版本。
除此之外,还用Vue 3 中 ref 这个 API 的实现。利用了对象的get和set属性。区别是只针对对象的单一属性。
let getDouble = n => n * 2
let _value = 1
double = getDouble(_value)
let count = {
get value() {
return _value
},
set value(val) {
_value = val
double = getDouble(_value)
}
}
console.log(count.value,double)
count.value = 2
console.log(count.value,double)
从Flow.js到TypeScript
起因
从尤大之前的文章看,选择Flow.js选择最根本的还是在于工程上成本和收益的考量。
而尤大自己的原话是 “但是没想到 Flow 烂尾了,而 TS 整个生态越做越好,这个属于就是押错宝了”。
Vue 2.0 本身在初期的快速迭代阶段是用 ES2015 写的,整个构建工具链也沿用了 Vue 1.x 的基于 ES 生态的一套(Babel, ESLint, Webpack, Rollup...),全部换 TS 成本过高,短期内并不现实。
为什么要使用ts
- 更好的类型检测(复杂的类型体操),更方便的提示,可以在编译前之前发现错误。
- 第二点类型系统让代码更健壮。对开发者更友好。
Composition API
setup
// 子组件
// setup 函数中的第一个参数是 props。正如在一个标准组件中所期望的那样,setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。
export default {
props: {
title: String
},
setup(props, context) {
console.log(context.attrs)
console.log(props.title)
}
}
setup 函数中的第一个参数是 props。正如在一个标准组件中所期望的那样,setup 函数中的 props 是响应式的,当传入新的 prop 时,它将被更新。
传递给 setup 函数的第二个参数是 context。context 是一个普通 JavaScript对象。可以简单的理解成 this。
reactive
reactive 方法 根据传入的对象 (重点),创建返回一个深度响应式对象。响应式对象看起来和传入的对象一样,对这个对象的增删改都会触发响应。
const state = reactive({
count:1
})
// 改变值
state.count = 2
// 新增
state.name = 'hello'
// 删除
delete state.name
ref
ref方法给值类型(String,Number,Boolean,Symbol)添加响应式。
// 传入了一个Number类型的值
const state = reactive(1)
// 注意 如果要使用。需要.vaule
console.log(state.value)
toRef、 toRefs
toRef转换响应式对象中某个属性为单独响应式数据,并且转换后的值和之前是关联的(ref 函数也可以转换,但值非关联)。
toRefs 转换响应式对象中所有属性为单独响应式数据,并且转换后的值和之前是关联的。
import { ref , toRef , toRefs } from 'vue'
setup(){
const state = reactive({
age: 18
})
//这里将对象转化成响应性
const ageRef = toRef(state, 'age')
ageRef.value = 20
return {
state ,
ageRef ,
//toRefs针对的是使用了reactive的响应式对象,可以理解为将对象拆分成多个响应式ref,外界可以读取到响应式的所有属性
...toRefs(state)
}
}
watchEffect与watch
watchEffect与watch监听的区别
- watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而 watch 只能监听指定的属性而做出变更(v3开始可以同时指定多个)。
- watch 可以获取到新值与旧值(更新前的值),而 watchEffect 是拿不到的。
- watchEffect 如果存在的话,在组件初始化的时候就会执行一次用以收集依赖(与computed同理),而后收集到的依赖发生变化,这个回调才会再次执行,而 watch 不需要,因为他一开始就指定了依赖。
- 都无法监听未被绑定的属性。
<script>
import {reactive, watchEffect} from 'vue'
export default {
setup() {
const state = reactive({ count: 0, name: 'zs' })
state.count = 1
watchEffect(() => {
console.log(state.count)
console.log(state.name)
}
}
</script>
写法对比
vue2时期的写法Options API
<div id="app">
<h1 @click="add">{{count}} * 2 = {{double}}</h1>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
let App = {
data(){
return {
count:1
}
},
methods:{
add(){
this.count++
}
},
computed:{
double(){
return this.count*2
}
}
}
Vue.createApp(App).mount('#app')
</script>
该组件有以下几个职责:
- 从假定的外部 API 获取该用户的仓库,并在用户有任何更改时进行刷新
- 使用
searchQuery字符串搜索仓库 - 使用
filters对象筛选仓库
使用 (data、computed、methods、watch) 组件选项来组织逻辑通常都很有效。然而,当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。
总结下OptionsApi的问题
- 由于所有数据都挂载在 this 之上,因而 Options API 的写法对 TypeScript 的类型推导很不友好,并且这样也不好做 Tree-shaking 清理代码。
- 新增功能基本都得修改 data、method 等配置,并且代码一多,反复横跳。碎片化使得理解和维护复杂组件变得困难。选项的分离掩盖了潜在的逻辑问题。此外,在处理单个逻辑关注点时,我们必须不断地“跳转”相关代码的选项块。
- 代码不好复用,Vue 2 的组件很难抽离通用逻辑,只能使用 mixin,还会带来命名冲突的问题。
在 Vue 3 中,除了上面这种这个写法,我们还可以采用下方的写法,新增一个 setup 配置:
<div id="app">
<h1 @click="add">{{state.count}} * 2 = {{double}}</h1>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script>
const {reactive,computed} = Vue
let App = {
setup(){
const state = reactive({
count:1
})
function add(){
state.count++
}
const double = computed(()=>state.count*2)
return {state,add,double}
}
}
Vue.createApp(App).mount('#app')
</script>
如果嫌弃需要return过于麻烦。 还可以使用
<div id="app">
<h1 @click="add">{{state.count}} * 2 = {{double}}</h1>
</div>
<script src="https://unpkg.com/vue@next"></script>
<script setup>
const {reactive,computed, ref} = Vue
let state = ref(1)
function add()
{
state.count++
}
let double = computed(()=>state.count*2)
</script>
总结:
Vue3.x 设计重点变了,数据的定义不在严格遵守组件中组件,计算属性的位置,真只能的以“数据”为中心想去哪就去哪(蒙多!),更容易封装,也更加的自由。
自定义渲染器
Vue 2 内部所有的模块都是揉在一起的。响应式只服务于 Vue,Vue 3 的响应式就和 Vue 解耦了,渲染的逻辑也拆成了平台无关渲染逻辑和浏览器渲染 API 两部分 。
意味着在这个架构下,可以在Node 等其他框架下面依赖响应式。
样式新特性
除了常规的scoped 属性。vue3还新增了**:global**。可以在scoped 内部写全局样式。
也可以通过 v-bind 函数,直接在 CSS 中使用 JavaScript 中的变量
<template>
<div>
<h1 @click="add">{{ count }}</h1>
</div>
</template>
<script setup>
import { ref } from "vue";
let count = ref(1)
let color = ref('red')
function add() {
count.value++
color.value = Math.random()>0.5? "blue":"red"
}
</script>
<style scoped>
h1 {
color:v-bind(color);
}
</style>>
生命周期
| Vue2.x | Vue3 |
|---|---|
| beforeCreate | 使用 setup() |
| created | 使用 setup() |
| beforeMount | onBeforeMount |
| mounted | onMounted |
| beforeUpdate | onBeforeUpdate |
| updated | onUpdated |
| beforeDestroy | onBeforeUnmount |
| destroyed | onUnmounted |
| errorCaptured | onErrorCaptured |