vue3较vue2的改动点总结

209 阅读5分钟

vue3较vue2的改动点总结

1.响应式系统升级

​ Vue3.0相比于2.x版本做了响应式系统的升级,在Vue2.x版本中是通过defineProperty去设置响应式,但在3.0中采取了Proxy的方式,这样做有三个优点

  • 可以监听动态添加对象的属性
  • 可以监听删除的属性
  • 可以监听数组的索引以及数组的length属性

2.Composition API

​ 随着Vue组件的增大,组件内代码变得越来越难以理解和维护。其中的一些可以复用的代码很难被抽离出来。同时 Vue2.0还缺少 TS支持。在Vue2中,逻辑概念(功能)被管理在组件中,但是功能和组件并不是一对一关系。一个功能可以被多个组件使用同时一个组件可以有多个功能。在Vue中,一个功能可能需要依赖多个Options(components、props、data、computed、methods及生命周期方法),在 Composition API中提供了 setup 方法。

核心API

  • reactive
  • ref
  • computed
  • readonly
  • watchEffect
  • watch

Fragments

Vue3中不在要求模版的跟节点必须是只能有一个节点。跟节点和和render函数返回的可以是纯文字、数组、单个节点,如果是数组,会自动转化为 Fragments。

Teleport

对标 React Portal。可以做一些关于响应式的设计,如果屏幕宽度比较宽的时候,加入某些元素,屏幕变窄后移除。

Suspense

等待嵌套的异步依赖。再把一个嵌套的组件树渲染到页面上之前,先在内存中进行渲染,并记录所有的存在异步依赖的组件。只有所有的异步依赖全部被resolve之后,才会把整个书渲染到dom中。当你的组件中有一个 async的 setup函数,这个组件可以被看作是一个Async Component,只有当这个组件被Resolve之后,再把整个树渲染出来

  • async setup()
  • Async Component

Typescript

Vue3源码使用 TS重写,但不意味着vue3的项目也要使用TS。但Vue3会对 TS有更好的支持

  • 支持 TSX
  • 支持 Class componen

生命周期的变化

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured

setup

setup 相当于是组件的入口了,可以调用所有组合函数。最后的return,可以作为出口,确认要暴露给模板哪些内容。

setup接收两个参数,props和context

setup(props, context) { 
  return {} 
}
  • props:跟 2.x 的 props 一样,接受父组件传过来的值。
  • context:是一个上下文对象,包含了一些2.xthis中的属性。如:
attrs: Object // => this.$attrs
emit: f() // => this.$emit
isServer: false // 是否服务端渲染
listeners: Object // => this.$listeners
parent: VueComponent // => this.$parent
refs: Object // => this.$refs
root: Vue // => main.js 中的全局唯一的 vue 实例
slots: {} // => this.$slots
ssrContext: {} // => 服务端渲染

reactive

对于响应式数据,我们可以通过reactive来创建。响应式转换是基于es6中的proxy实现的,返回的是一个代理后的对象,并不等于原始对象。

<template>
  <div class="add">
    <h1>{{ state.count }}</h1>
    <button @click="addCount">add</button>
  </div>
</template>

<script>
import { reactive } from vue'
export default {
  setup() {
    const state = reactive({
      count: 0
    })
    const addCount = () => {
      state.count++
    }
    return { state, addCount }
  }
}
</script> 

ref

接受一个内部值,返回一个响应式的、可更改的 ref 对象,此对象只有一个指向其内部值的属性 .value

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

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

toRefs

上面的例子中,我们在模板中使用的是state.count这种方式,获取响应式的数据。如果要把{{ state.count }}写成{{ count }},就需要用toRefs了。

import { reactive, toRefs } from 'vue'
export default {
  setup() {
    const state = reactive({
      count: 0
    })
    return {...toRefs(state)}
  }
}

computed

接受一个 getter 函数,返回一个只读的响应式ref对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 getset 函数的对象来创建一个可写的 ref 对象。

创建一个只读的计算属性 ref:

const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

plusOne.value++ // 错误

创建一个可写的计算属性 ref:

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

watch用来监听一个或多个数据的变化,并在回调中执行副作用。

setup() {
  const state = reactive({
    count: 0,
    msg: 'ha',
  })
  // 监听一个数据的变化
  watch(
    () => state.count,
    (count, preCount) => {
      console.log(count, preCount)
    },
  )
  // 监听多个数据的变化
  watch([() => state.count, () => state.msg], ([count, msg], [preCount, preMsg]) => {
    console.log(count, msg, preCount, preMsg)
  })

  const addCount = () => {
    state.count++
  }

  return { ...toRefs(state), addCount }
}

watchEffect

立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。

watchEffect(() => {
  console.log(state.count)
})

watch 与 watchEffect 的区别

  • watchEffect 在组件初始化时,立即执行传入的一个副作用函数。并且在副作用函数中使用的属性有变化时,会重新执行。需要注意,当副作用函数中执行的函数,若该函数又改变了响应式的数据,可能会造成死循环问题。
  • watch 是监听指定的属性,当指定属性变化时,才会执行回调。watch 可以接收指定的一个或多个属性。 watch中可以获取状态变化前后的值。

readonly

接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理。

const original = reactive({ count: 0 })

const copy = readonly(original)

watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)
})

// 更改源属性会触发其依赖的侦听器
original.count++

// 更改该只读副本将会失败,并会得到一个警告
copy.count++ // warning!

<script setup>语法糖

<script setup>是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。当同时使用 SFC 与组合式 API 时该语法是默认推荐。相比于普通的 <script> 语法,它具有更多优势:

  • 更少的样板内容,更简洁的代码。
  • 能够使用纯 TypeScript 声明 props 和自定义事件。
  • 更好的运行时性能 (其模板会被编译成同一作用域内的渲染函数,避免了渲染上下文代理对象)。
  • 更好的 IDE 类型推导性能 (减少了语言服务器从代码中抽取类型的工作)。

顶层的绑定会被暴露给模板

当使用 <script setup> 的时候,任何在 <script setup> 声明的顶层的绑定 (包括变量,函数声明,以及 import 导入的内容) 都能在模板中直接使用:

<script setup>
// 变量
const msg = 'Hello!'

// 函数
function log() {
  console.log(msg)
}
</script>

<template>
  <button @click="log">{{ msg }}</button>
</template>

import 导入的内容也会以同样的方式暴露。这意味着我们可以在模板表达式中直接使用导入的 helper 函数,而不需要通过 methods 选项来暴露它:

<script setup>
import { capitalize } from './helpers'
</script>

<template>
  <div>{{ capitalize('hello') }}</div>
</template>

响应式

响应式状态需要明确使用响应式 API来创建。和 setup() 函数的返回值一样,ref 在模板中使用的时候会自动解包:

<script setup>
import { ref } from 'vue'

const count = ref(0)
</script>

<template>
  <button @click="count++">{{ count }}</button>
</template>

使用组件

<script setup>范围里的值也能被直接作为自定义组件的标签名使用:

<script setup>
import MyComponent from './MyComponent.vue'
</script>

<template>
  <MyComponent />
</template>

defineProps()和 defineEmits()

为了在声明 propsemits 选项时获得完整的类型推导支持,我们可以使用 definePropsdefineEmits API,它们将自动地在 <script setup> 中可用:

<script setup>
const props = defineProps({
  foo: String
})

const emit = defineEmits(['change', 'delete'])
// setup 代码
</script>

defineExpose

使用 <script setup> 的组件是默认关闭的——即通过模板引用或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。

可以通过 defineExpose 编译器宏来显式指定在 <script setup> 组件中要暴露出去的属性:

<script setup>
import { ref } from 'vue'

const a = 1
const b = ref(2)

defineExpose({
  a,
  b
})
</script>

顶层 await

<script setup>中可以使用顶层 await。结果代码会被编译成 async setup()

<script setup>
const post = await fetch(`/api/post/1`).then((r) => r.json())
</script>

3.Composition API中 Vue Router 的使用

新建目录src/router,在该目录新建文件index.js

//导入路由方法
import { createRouter, createWebHistory } from 'vue-router'
//导入组件
import Home from '@/views/home'
import About from '@/views/about'
//路由规则
const routes = [
  { path'/'componentHome },
  { path'/about'componentAbout }
]
//路由创建
const router = createRouter({
  historycreateWebHistory(),
  routes
})
export default router

组建中使用:

useRoute

返回当前路由地址。相当于在模板中使用 $route。必须在 setup() 中调用。

useRouter

返回 router实例。相当于在模板中使用 $router。必须在 setup() 中调用。

<script setup>
    import { useRoute, useRouter } from 'vue-router' 
	const route = useRoute();
	const router = useRouter();
</script>

4.Composition API 中使用vuex

创建index.js

import { createStore } from 'vuex'; //引入vuex
import app from './modules/app'; //引入modules的方法;
import getters from './getters' //引入getters
const store = createStore({
    modules: {
        app
    },
    getters
})

export default store; //导出

使用:

<script setup>
    import { useStore } from "vuex";
    const store = useStore();
</script>

官方推荐在composition API中使用pinia来实现vuex的功能pinia.vuejs.org/introductio…