社畜自救指南01:Vue3和Vue2对比总结

1,626 阅读6分钟

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进行读写拦截,而后者是创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。

光看上面的声明可能觉得都差不多,我们着重看一下他们之间的区别。

  1. defineProperty 对不存在的属性无法拦截,所以 Vue 2 中所有数据必须要在 data 里声明。
  2. 当被代理的是一个数组的时候,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 函数的第二个参数是 contextcontext 是一个普通 JavaScript对象。可以简单的理解成 this。

reactive

reactive 方法 根据传入的对象 (重点),创建返回一个深度响应式对象响应式对象看起来和传入的对象一样,对这个对象的增删改都会触发响应。

   const state = reactive({
      count:1
    })
    // 改变值
    state.count = 2
    // 新增
    state.name = 'hello'
    // 删除
    delete state.name

ref

ref方法给值类型(StringNumberBooleanSymbol)添加响应式。

// 传入了一个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监听的区别

  1. watchEffect 不需要指定监听的属性,他会自动的收集依赖, 只要我们回调中引用到了 响应式的属性, 那么当这些属性变更的时候,这个回调都会执行,而 watch 只能监听指定的属性而做出变更(v3开始可以同时指定多个)。
  2. watch 可以获取到新值与旧值(更新前的值),而 watchEffect 是拿不到的。
  3. watchEffect 如果存在的话,在组件初始化的时候就会执行一次用以收集依赖(与computed同理),而后收集到的依赖发生变化,这个回调才会再次执行,而 watch 不需要,因为他一开始就指定了依赖。
  4. 都无法监听未被绑定的属性。
<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>

该组件有以下几个职责:

  1. 从假定的外部 API 获取该用户的仓库,并在用户有任何更改时进行刷新
  2. 使用 searchQuery 字符串搜索仓库
  3. 使用 filters 对象筛选仓库

使用 (datacomputedmethodswatch) 组件选项来组织逻辑通常都很有效。然而,当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。

总结下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.xVue3
beforeCreate使用 setup()
created使用 setup()
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted
errorCapturedonErrorCaptured