vue3与vue2的区别

vue3和vue2的不同

1、生命周期的变化

整体来看,变化不大,只是名字大部分需要 + on,功能上类似。使用上 Vue3 组合式 API 需要先引入;Vue2 选项 API 则可直接调用,如下所示。

// vue3
<script setup>     
import { onMounted } from 'vue'

onMounted(() => {
  ...
})
// 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不被覆盖
onMounted(() => {
  ...
})
</script>

// vue2
<script>     
   export default {         
      mounted() {             
        ...         
      },           
   }
</script> 

常用生命周期表格如下所示。

Vue2.xVue3
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted

Tips: setup是围绕beforeCreatecreated生命周期钩子运行的,所以不需要显式地去定义。

setup

setup 是 Vue3 新增的一个选项, 他是组件内使用 Composition API的入口。

setup执行时机

export default defineComponent ({
    beforeCreate() {
        console.log("----beforeCreate----");
    },
    created() {
        console.log("----created----");
    },
    setup() {
        console.log("----setup----");
    },
})

图片

setup 执行时机是在beforeCreate之前执行。

::: warning 由于在执行setup 时尚未创建组件实例,因此在 setup 选项中没有this。:::

setup 参数

使用setup时,它接受两个参数:

  1. props: 组件传入的属性
  2. context

setup中接受的props是响应式的, 当传入新的props 时,会及时被更新。由于是响应式的, 所以不可以使用ES6解构,解构会消除它的响应式。

错误代码示例, 这段代码会让props不再支持响应式:

// demo.vue
export default defineComponent ({
    setup(props, context) {
        const { name } = props
        console.log(name)
    },
})

setup接受的第二个参数context,我们前面说了setup中不能访问Vue2中最常用的this对象,所以context中就提供了this中最常用的三个属性:attrsslotemit,分别对应Vue2.x中的 $attr属性、slot插槽 和$emit发射事件。并且这几个属性都是自动同步最新的值,所以我们每次使用拿到的都是最新值。

2、多根节点

Vue3 支持了多根节点组件,也就是fragment(碎片)。

Vue2中,编写页面的时候,我们需要去将组件包裹在<div>中,否则报错警告。

<template>
  <header>...</header>
  <main>...</main>
  <footer>...</footer>
</template>

3、异步组件

Vue3 提供 Suspense组件,允许程序在等待异步组件时渲染兜底的内容,如 loading ,使用户体验更平滑。使用它,需在模板中声明,并包括两个命名插槽:defaultfallbackSuspense确保加载完异步内容时显示默认插槽,并将fallback插槽用作加载状态。

<tempalte>
   <suspense>
     <template #default>
       <todo-list />
     </template>
     <template #fallback>
       <div>
         Loading...
       </div>
     </template>
   </suspense>
</template>

真实的项目中踩过坑,若想在 setup 中调用异步请求,需在 setup 前加async关键字。这时,会受到警告async setup() is used without a suspense boundary

解决方案:在父页面调用当前组件外包裹一层Suspense组件。

4、Teleport (传送门组件)

Vue3 提供Teleport组件可将部分DOM移动到 Vue app之外的位置。比如项目中常见的Dialog组件。

<button @click=dialogVisible = true>点击</button>
<teleport to=body>
   <div class=dialog v-if=dialogVisible>
   </div>
</teleport>

5、组合式API

Vue2 是 选项式API(Option API),一个逻辑会散乱在文件不同位置(data、props、computed、watch、生命周期函数等),导致代码的可读性变差,需要上下来回跳转文件位置。Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起。

除了增强了代码的可读性、内聚性,组合式API 还提供了较为完美的逻辑复用性方案,举个🌰,如下所示公用鼠标坐标案例。

// main.vue
<template>
  <span>mouse position {{x}} {{y}}</span>
</template>

<script setup>
import { ref } from 'vue'
import useMousePosition from './useMousePosition'

const {x, y} = useMousePosition()

}
</script>
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'

function useMousePosition() {
  let x = ref(0)
  let y = ref(0)
  
  function update(e) {
    x.value = e.pageX
    y.value = e.pageY
  }
  
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  
  return {
    x,
    y
  }
}
</script>

解决了 Vue2 Mixin的存在的命名冲突隐患,依赖关系不明确,不同组件间配置化使用不够灵活。

Composition API

图片
① reactive

reactive()接收一个普通对象然后返回该普通对象的响应式代理。等同于 2.x 的 Vue.observable()

<template>
  <div id="app">{ state.count }</div>
</template>

<script>
import { reactive } from 'vue'
export default {
  setup() {
    // state 现在是一个响应式的状态
    const state = reactive({
      count: 0,
    })
  }
}
</script>
② ref

​ 接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value

const count = ref(0)
console.log(count.value) // 0

count.value++
console.log(count.value) // 1

如果传入 ref 的是一个对象,将调用 reactive 方法进行深层响应转换。

  • 模板中访问:自动解套,无需在模板内额外书写 .value属性
<template>
  <div>{{ count }}</div>
</template>

<script>
  export default {
    setup() {
      return {
        count: ref(0),
      }
    },
  }
</script>
  • 作为响应式对象的属性访问
const count = ref(1);
const state = reactive({
  count
})
console.log(state.count) // 1
state.count = 222;
console.log(count.value) // 222

如果分配一个新的 ref 给现有的 ref,则替换旧的 ref

const otherCount = ref(10);
state.count = otherCount;
console.log(state.count) // 10
console.log(count.value) // 1

注意当嵌套在 reactive Object 中时,ref 才会解套。从 Array 或者 Map 等原生集合类中访问 ref 时,不会自动解套:

const arr = reactive([ref(0)])
// 这里需要 .value
console.log(arr[0].value)

const map = reactive(new Map([['foo', ref(0)]]))
// 这里需要 .value
console.log(map.get('foo').value)
③ toRefs

toRefs用于将一个reactive对象转化为属性全部为ref对象的普通对象。具体使用方式如下:

<template>
  <div class="homePage">
    <p>{{ year }}</p>
    <p>姓名: {{ nickname }}</p>
    <p>年龄: {{ age }}</p>
  </div>
</template>

<script>
import { defineComponent, reactive, ref ,toRefs} from "vue";
export default defineComponent({
  setup() {
    const year = ref(0);
    const user = reactive({ nickname: "xiaofan", age: 26, gender: "女" });
    setInterval(() =>{
        year.value ++
        user.age ++
    }, 1000)
    return {
        year,
        // 使用toRefs
        ...toRefs(user)
    }
  },
});
</script>
④ computed

使用响应式 computed API 有两种方式:

  1. 传入一个 getter 函数,返回一个默认不可手动修改的 ref 对象。

    const count = ref(1)
    const plusOne = computed(() => count.value + 1)
    
    console.log(plusOne.value) // 2
    
    plusOne.value++ // 错误!
    
  2. 传入一个拥有 getset 函数的对象,创建一个可手动修改的计算状态。

    const count = ref(1)
    const plusOne = computed({
      get: () => count.value + 1,
      set: (val) => {
        count.value = val - 1
      },
    })
    
    plusOne.value = 1
    console.log(count.value) // 0
    
    
⑤ watch

接受 3 个参数:

  • 一个想要侦听的响应式引用或 getter 函数
  • 一个回调
  • 可选的配置选项
import { ref, watch } from 'vue'

const counter = ref(0)
watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
},{immediate:true})
// ---------等同于---------------
watch: {
    counter(newValue, oldValue) {
      console.log('The new counter value is: ' + this.counter)
    },
        immediate:true
  }

情况一 :监视 ref 定义的单个数据

  const name = ref('张三')
  watch(name, (newVal, oldVal) => {
      console.log('name的值变化了',newVal, oldVal)
    },{immediate:true})

情况二:监视 ref 定义的多个数据

  const name = ref('张三')
  //参数一改为数组形式
  watch([name,age], (newVal, oldVal) => {
      console.log(newVal, oldVal)   // //  监听出来结果发现新的值和旧的值都是数组
    },{immediate:true})

情况三:监听reactive所定义的一个响应式数据的全部属性

     vue3当前存在问题:
     1.reactive和ref响应式使用proxy代理无法正确监听并获取对象oldVal
     2.强制开启深度监听,关闭无效(deep配置无效)
     
      watch( obj,(newVal, oldValue) => {
        // newVal=> {name: '老王', age: 52} oldValue=>{name: '老王', age: 52}
        console.log(newVal, oldValue);
      },{ deep: false }
    );

情况四:监听reactive所定义的一个响应式数据的某一个属性,要将箭头函数写成函数return的方式才有效,一定不要踩坑哦!

   const p = reactive({
      name: 'zs',
      age: 18
    })
    //()=>p.name 为要监听的函数
    watch(() => p.name, (newVal, oldVal) => {
      console.log(newVal, oldVal)
    })

情况五:监视reactive所定义的一个响应式数据的某些属性

  watch([()=>data.count,()=>data.num],(newVal,oldVal)=>{
    console.log("data中的count或num数据发生变化",newVal,oldVal); 
  })
⑥ watchEffect

立即执行传入的一个函数,并响应式追踪其依赖,并在其依赖变更时重新运行该函数。

const count = ref(0)

watchEffect(() => console.log(count.value))
// -> 打印出 0

setTimeout(() => {
  count.value++
  // -> 打印出 1
}, 100)

  1. 停止侦听

watchEffect 在组件的 setup() 函数或生命周期钩子被调用时, 侦听器会被链接到该组件的生命周期,并在组件卸载时自动停止。

在一些情况下,也可以显式调用返回值以停止侦听:

const stop = watchEffect(() => {
  /* ... */
})

// 之后
stop()
  1. 清除副作用:侦听副作用传入的函数可以接收一个 onInvalidate 函数作入参

6、响应式原理

Vue2 响应式原理基础是Object.defineProperty;Vue3 响应式原理基础是Proxy

1. Object.defineProperty

基本用法:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

Tips: writablevaluegettersetter 不共存。

实现响应式:通过defineProperty 两个属性,getset

get:属性的 getter 函数,当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this 对象(由于继承关系,这里的this并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值

set:属性的 setter 函数,当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this 对象。默认为 undefined

let obj = {}
let name = '瑾行'
Object.defineProperty(obj, 'name', {
  enumerable: true, // 可枚举(是否可通过for...in 或 Object.keys()进行访问)
  configurable: true, // 可配置(是否可使用delete删除,是否可再次设置属性)
  // value: '', // 任意类型的值,默认undefined
  // writable: true, // 可重写
  get: function() {
    return name
  },
  set: function(value) {
    name = value
  }
})

小结:

  • 无法监听对象或数组新增、删除的元素

  • 需要对每个属性进行遍历监听,如果嵌套对象,需要深层监听,造成性能问题。

    Vue2 方案:针对常用数组原型方法pushpopshiftunshiftsplicesortreverse进行了hack处理;提供Vue.set监听对象/数组新增属性。对象的新增/删除响应,还可以new个新对象,新增则合并新属性和旧对象;删除则将删除属性后的对象深拷贝给新对象。

2. Proxy

Proxy是ES6新特性,通过第2个参数handler拦截目标对象的行为。Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了。相较于Object.defineProperty提供语言全范围的响应能力,消除了局限性。但在兼容性上放弃了(IE11以下)

基本用法:创建对象的代理,从而实现基本操作的拦截和自定义操作。

同时Proxy 并不能监听到内部深层次的对象变化,而 Vue3 的处理方式是在getter 中去递归响应式,这样的好处是真正访问到的内部对象才会变成响应式,而不是无脑递归。

Proxy可以直接监听数组的变化(pushshiftsplice

总结:

Object.defineProperty 只能遍历对象属性进行劫持

proxy 直接可以劫持整个对象,并返回一个新对象,我们可以只操作新的对象达到响应式目的

7、打包优化

tree-shaking:模块打包webpackrollup等中的概念。移除 JavaScript 上下文中未引用的代码。主要依赖于importexport语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用。

nextTick为例子,在 Vue2 中,全局 API 暴露在 Vue 实例上,即使未使用,也无法通过tree-shaking进行消除。

import Vue from 'vue'

Vue.nextTick(() => {
  // 一些和DOM有关的东西
})
复制代码

Vue3 中针对全局 和内部的API进行了重构,并考虑到tree-shaking的支持。因此,全局 API 现在只能作为ES模块构建的命名导出进行访问。

import { nextTick } from 'vue'

nextTick(() => {
  // 一些和DOM有关的东西
})
复制代码

通过这一更改,只要模块绑定器支持tree-shaking,则 Vue 应用程序中未使用的api将从最终的捆绑包中消除,获得最佳文件大小。

8、自定义渲染API

Vue3 提供的createApp默认是将 template 映射成 html。但若想生成canvas时,就需要使用custom renderer api自定义render生成函数。

// 自定义runtime-render函数
import { createApp } from './runtime-render'
import App from './src/App'

createApp(App).mount('#app')

ue.nextTick(() => {
// 一些和DOM有关的东西
})
复制代码


Vue3 中针对全局 和内部的API进行了重构,并考虑到`tree-shaking`的支持。因此,全局 API 现在只能作为ES模块构建的命名导出进行访问。

```js
import { nextTick } from 'vue'

nextTick(() => {
  // 一些和DOM有关的东西
})
复制代码

通过这一更改,只要模块绑定器支持tree-shaking,则 Vue 应用程序中未使用的api将从最终的捆绑包中消除,获得最佳文件大小。

8、自定义渲染API

Vue3 提供的createApp默认是将 template 映射成 html。但若想生成canvas时,就需要使用custom renderer api自定义render生成函数。

// 自定义runtime-render函数
import { createApp } from './runtime-render'
import App from './src/App'

createApp(App).mount('#app')
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值