vue2与vue3的区别

Posted 遥岑.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了vue2与vue3的区别相关的知识,希望对你有一定的参考价值。

目录

生命周期

在 vue3 中,新增了 setup 生命周期函数,它的执行时机是在 beforeCreate 生命函数之前执行,在这个函数中是不能通过 this 来获取实例的。
vue3 在组合式API中使用生命周期钩子时需要先引入,而 vue2 在选项API中可以直接调用生命周期钩子。
在 setup 中挂载生命周期钩子,当执行到对应的生命周期时,就调用对应的钩子函数。

// vue3
<script setup>     
import  onMounted  from 'vue';   // 使用前需引入生命周期钩子
 
onMounted(() => 
  // ...
);
 
// 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不会被覆盖
onMounted(() => 
  // ...
);
</script>
 
// vue2
<script>     
export default          
  mounted()    // 直接调用生命周期钩子            
    // ...         
  ,           

</script> 

多根节点

在 vue2 模板中,使用多个根节点会报错,但在 vue3 中支持多个根节点。

// vue3
<template>
  <header></header>
  <main></main>
  <footer></footer>
</template>

// vue2
// 只能存在一个根节点,需要用一个<div>来包裹着
<template>
  <div>
    <header></header>
    <main></main>
    <footer></footer>
  </div>
</template>

composition API

vue2 中选项API,逻辑会散乱在文件不同位置,比如实现某一个功能代码涉及到 data 、props 、watch 、生命周期等,这样对相同数据进行操作使用的代码会被分割到各个属性内,不利于阅读;选项API大量使用到 this,组件中的 props、data、methods都是绑定到 this 上下文,由 this 去访问,不利于代码逻辑的复用。

<template>
  <button @click="increment">count is:  count </button>
</template>
 
<script>
export default 
  data() 
    return 
      count: 0
    
  ,
  methods: 
    increment() 
      this.count++;
    
  ,
  mounted() 
    console.log(`The initial count is $this.count.`);
  

</script>

在 vue3 组合式API中对这一问题进行了优化,使用组合式API可以将同一逻辑内容写到一起,增强代码可读性和可维护性;组合式API在涉及组件之间提取、复用逻辑时会非常灵活,它打破了 this 的限制,一个合成函数只依赖于它的参数和全局引入的 vue apis,而不是 this 上下文,更利于复用。

<template>
  <button @click="increment">Count is:  count </button>
</template>
 
<script setup>
import  ref, onMounted  from 'vue';
 
const count = ref(0);
 
function increment() 
  count.value++;

 
onMounted(() => 
  console.log(`The initial count is $count.value.`);
)
</script>

setup

setup 是一个新的组合选项,充当组件内部使用 composition API 的入口点。
setup 函数里 this 并不是期望的组件实例,为 undefined。

  <template>
    <div> count   object.foo </div>
  </template>

  <script>
    import  ref, reactive  from 'vue'

    export default 
      setup() 
        const count = ref(0)
        const object = reactive( foo: 'bar' )

        // 必须return,才可以在模版上使用
        return 
          count,
          object
        
      
    
  </script>

reacitive

用于实现对象数据类型的响应式侦测。

// 传入一个普通对象,返回值是一个经过 vue 代理过的响应式对象
  const Counter = 
    setup()
      //reactive实现响应式,适用于一组值
      const state = reactive(
        count:0
      )
      const add =()=>
        state.count ++
      
      //返回一个代理后的对象,用来实现响应式
      console.log(state)
      return 
        state,
        add
      
    ,
    template:`<h1>state.count</h1><button @click="add">+</button>`,
  ;

ref

用于实现基础数据类型值的响应式侦测。
传入一个值,返回一个 vue 内部包装过的对象,并且改变 value 属性可以触发响应式更新,用于模板渲染时,不用.value去访问,内部会自动拆包。

  const Counter = 
    setup()
      //ref实现响应式,适合单个值场景
      const count = ref(0)
      const add =()=>
        count.value ++
      
      console.log(count)
      return 
        count,
        add
      
    ,
    template:`<h1>count</h1><button @click="add">+</button>`,
  ;

toRef toRefs

reactive 返回的代理对象在组合函数的传递过程中必须保持对返回对象的引用,保证它的响应式,不能被ES6解构。

   // toRef
    const pos = reactive(
      x:0,
      y:0
    )
    //将响应式对象某一个属性转化为ref
    const xRef = toRef(pos,'x')
    const yRef = toRef(pos,'y')
   // toRefs
    const pos = reactive(
        x:0,
        y:0
      )
      //将整个响应式对象的全部属性转化为ref,装在一个普通对象中
      const posRefsObj = useRefs(pos)
      //等价于
      const posRefsObj = 
          x:toRef(pos,'x')
          y:toRef(pos,'y')
      

computed

传入一个计算函数,返回一个包装后的响应式引用对象。

  const Counter = 
        setup()
            const state = reactive(
              count:0
            )
            //计算count是否是偶数,字体切换红绿颜色
            let isOdd = computed(()=>state.count%2 === 0)
            const add =()=>
              state.count ++
            
            return 
              state,
              isOdd,
              add
            
          ,
          template:`<h1 :style="'color':isOdd?'red':'green'">state.count</h1><button @click="add">+</button>`,
      ;

watch

主动监测响应式数据,数据改变后执行用户传入的回调。

  const Counter = 
    setup()
      //reactive实现响应式,适用于一组值
      const state = reactive(
        count:0
      )
      //计算count是否是奇数,字体切换红绿颜色
      let isOdd = computed(()=>state.count%2 === 0)

      watch(isOdd,(newValue)=>
        alert(newValue?'偶数':"奇数")
      )
      const add =()=>
        state.count ++
      
      return 
        state,
        isOdd,
        add
      
    ,
    template:`<h1 :style="'color':isOdd?'red':'green'">state.count</h1><button @click="add">+</button>`,
  ;

依赖注入

provide和inject提供依赖注入,功能类似2.x的provide/inject。两者都只能在当前组件的setup()中调用。

<template>
  <div>
    <Article></Article>
  </div>
</template>

<script>
import 
  ref,
  provide
 from "vue";
import Article from "./components/Article";
export default 
  setup() 
    const articleList = ref([
       id: 1, title: "1" ,
       id: 2, title: "2" ,
       id: 3, title: "3" 
    ]);
    /* 
      provide 函数允许你通过两个参数定义 property:
      property 的 name (<String> 类型)
      property 的 value
    */
    provide("list",articleList);
    return 
      articleList
    ;
  ,
  components: 
    Article
  
;
</script>
<template>
  <div>
    articleList[0].title
  </div>
</template>

<script>
import  inject  from "vue";
export default 
  setup() 
    const articleList = inject('list',[]);
    return articleList;
  ,
;
</script>

异步组件

vue3 提供 suspense 组件,允许程序在等待异步组件加载完成前渲染兜底内容,使用户体验更完整。
使用它需要在模板中声明,包括两个命名插槽:default 和 fallback。在加载完异步内容时显示默认插槽,将 fallback 插槽用作加载状态。

<tempalte>
  <suspense>
    <template #default>
      <List />
    </template>
    <template #fallback>
      <div>
        Loading...
      </div>
    </template>
  </suspense>
</template>

teleport

vue3 提供 Teleport 组件可将部分 DOM 移动到 Vue app 之外的位置。

<button @click="dialogVisible = true">显示弹窗</button>
<teleport to="body">
  <div class="dialog" v-if="dialogVisible">
    我是弹窗,我直接移动到了body标签下
  </div>
</teleport>

响应式原理

响应式是实现数据驱动视图的第一步,监听数据的变化,使用户在设置数据时,可以通知 vue 内部进行视图更新。
vue2 响应式原理基础是 Object.defineProperty,它的作用是直接在一个对象上定义一个新属性或者修改一个已经存在的属性。

// obj 需要定义属性的当前对象
// prop 当前需要定义的属性名
// desc 属性描述符
Object.defineProperty(obj, prop, desc)
// Object.defineProperty基本用法
let obj = ;
let name = 'leo';
Object.defineProperty(obj, 'name', 
  enumerable: true,   // 可枚举(是否可通过 for...in 或 Object.keys() 进行访问)
  configurable: true,   // 可配置(是否可使用 delete 删除,是否可再次设置属性)
  // value: '',   // 任意类型的值,默认undefined
  // writable: true,   // 可重写
  get() 
    return name;
  ,
  set(value) 
    name = value;
  
);
// 实现对原始类型和对象的响应式监听,数据变化时,会在数据更新后进行试图更新
function bindReactive (target, key, value) 
    // 对象类型进行递归遍历
    reactive(value)
    Object.defineProperty(target, key, 
        get () 
            return value
        ,
        set (newVal) 
            if (newVal !== value) 
                // 改变值的对象类型
                reactive(newVal)
                value = newVal
                // 触发视图更新
                renderView()
            
        
    )

function reactive (target) 
    // 首先,不是对象直接返回
    if (typeof target !== 'object' || target === null) 
        return target
    
    // 遍历对象,对每个key进行响应式监听
    for (let key in target) 
        bindReactive(target, key, target[key])
    

const reactiveData = reactive(data)

虽然对原始类型和普通对象进行响应式监听,但它无法对数组的更改进行监听。在 vue2 中,通过 Object.create(prototype) 创建一个对象,使它的原型指向参数 prototype。

// 数组的原型
const prototype = Array.prototype
// 创建一个新的原型对象,他的原型是数组的原型(于是newPrototype上具有所有数组的api)
const newPrototype = Object.create(prototype)
const methods = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']
methods.forEach(method => 
    newPrototype[method] = () => 
        prototype[method].call(this, ...args)
        // 视图更新
        renderView()
    
)


// 入口方法
function reactive (target) 
    // 首先,不是对象直接返回
    if (typeof target !== 'object' || target === null) 
        return target
    
     // 对于数组,原型修改
    if (Array.isArray(target)) 
        target.__proto__ = newPrototype
    
    // 遍历对象,对每个key进行响应式监听
    for (let key in target) 
        bindReactive(target, key, target[key])
    

在 vue2 响应式实现中,Object.defineProperty 无法对数组进行响应式监听,实现中也有深度嵌套数据消耗性能。
而在 vue3 中,改用 ProxyReflect 代替 Object.defineProperty,对 vue2 响应式存在的问题有了更好的解决。

Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义。也就是在目标对象的外层搭建一层拦截,外界通过目标对象的某些操作必须通过这层拦截
Reflect 是一个内置对象,提供拦截js操作的方法

// target 用proxy包装的目标对象
// handler 一个对象,属性是执行一个操作时定义代理的行为的函数
let proxy = new Proxy(target, handler)
// reflect对象常用方法
Reflect.get(): 获取对象身上某个属性的值
Reflect.set(): 将值分配给属性的函数。返回一个Boolean,如果更新成功,则返回true
Reflect.has(): 判断一个对象是否存在某个属性
Reflect.deleteProperty(): 作为函数的delete操作符
function bindReactive (target) 
    if (typeof target !== 'object' || target == null) 
        // 不是对象或数组,则直接返回
        return target
    
    
    // 传给Proxy的handler
    const handler = 
        get(target, key) 
            const reflect = Reflect.get(target, key)
            // 当我们获取对象属性时,Proxy只会递归到获取的层级,不会继续递归子层级
            return bindReactive(reflect)
        ,
        set(target, key, val) 
            // 重复的数据,不处理
            if (val === target[key]) 
                return true
            
            // 这里可以根具是否是已有的key,做不同的操作
            if (Reflect.has(key)) 
               
             else 
            
            
            const success = Reflect.set(target, key, val)
            // 设置成功与否
            return success 
        ,
        deleteProperty(target, key) 
            const success = Reflect.deleteProperty(target, key)
             // 删除成功与否
            return success
        
    
    // 生成proxy对象
    const proxy = new Proxy(target, handler)
    return proxy

// 实现数据响应式监听
const reactiveData = bindReactive(data)

虚拟DOM

相比于 vue2 ,虚拟DOM上增加了 patchFlag 字段。
1 代表节点为动态文本节点,在 diff 过程中,只需要比对文本内容,无需关注 class、style 等。-1为静态节点,都会保存为一个变量进行静态提升,可在重新渲染时直接引用,无需重新创建。

/ patchFlags 字段类型列举
export const enum PatchFlags  
  TEXT = 1,   // 动态文本内容
  CLASS = 1 << 1,   // 动态类名
  STYLE = 1 << 2,   // 动态样式
  PROPS = <

以上是关于vue2与vue3的区别的主要内容,如果未能解决你的问题,请参考以下文章

在 Vue3 成为默认版本后,盘点了 Vue3 与 Vue2 的区别

Vue2与Vue3的生命周期-区别讲解

简单体验Vue2和Vue3开发组件的区别-案例

vue2与vue3的区别

vue2与vue3的区别

vue2与vue3的区别