Vue3新增的Api 学习记录

469 阅读9分钟

Vue3第一次发布时间2020年9月18号。2022年2月7日vue3成为新的默认版本。开发起来感觉像是一个全新的框架,有很多和Vue2不同的地方,本系列我就介绍下在开发遇到的一些问题。

开发前提学习Vue3

Vue3新增好多新的Api,在开发之前肯定是要先学习的了,话不多说直接进入代码学习,如果有Vue2经验同学建议和Vue2对比的学习,这样能加深Vue2的理解还能对Vue3掌握的更牢靠些。

Vue3不在使用object.defineproperty对数据做劫持而是使用了 ProxyReflect,能对数据更多操作和类型做劫持监听

新增的Api

ref() 和 reactive()

ref():将一个原始数据类型(primitive data type)转换成一个带有响应式特性,此对象只有一个指向其内部值的属性 .value
reactive():将复杂数据类型转换成一个带有响应式特性。这个响应式转换是“深层”的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。

const count = ref(0)
console.log(count.value) // 0 
count.value++ 
console.log(count.value) // 1
//
const userInfo = reactive({ name: '树哥', age: 18 })

computed () 和 watch()

computed():接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value 暴露 getter 函数的返回值。它也可以接受一个带有 get 和 set 函数的对象来创建一个可写的 ref 对象。这个Api和Vue2\color{red}{Vue2}computed类似支持回调函数和对象,不同的点是返回一个ref对象。
watch():侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。

第一个参数是侦听器的。这个来源可以是以下几种:

  • 一个函数,返回一个值
  • 一个 ref
  • 一个响应式对象
  • ...或是由以上类型的值组成的数组

第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。

当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。

第三个可选的参数是一个对象,支持以下这些选项:

  • immediate:在侦听器创建时立即触发回调。第一次调用时旧值是 undefined
  • deep:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。
  • flush:调整回调函数的刷新时机。
  • onTrack / onTrigger:调试侦听器的依赖。
const count = ref(1)
const plusOne = computed(() => count.value + 1) 
console.log(plusOne.value) // 2 
plusOne.value++ // 错误
//---------------------------分隔符-----------------------------------
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   set & 成功
console.log(plusOne.value) // 1 get & 获取
//---------------------------分隔符-----------------------------------
//侦听一个 getter 函数
const state = reactive({ count: 0 })
watch( () => state.count, (count, prevCount) => { /* ... */ } )
//侦听一个 ref:
const count = ref(0) 
watch(count, (count, prevCount) => { /* ... */ })
//侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:
const fooRef=ref(0)
const barRef=ref(1)
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => { /* ... */ })
//---------------------------监听回调函数返回的Proxy对象-----------------
/*当使用 getter 函数作为源时,回调只在此函数的返回值变化时才会触发。
如果你想让回调在深层级变更时也能触发,你需要使用 `{ deep: true }` 强制侦听器进入深层级模式。
在深层级模式时,如果回调函数由于深层级的变更而被触发,那么新值和旧值将是同一个对象。*/
const state = reactive({ count: 0 }) 
watch( () => state, (newValue, oldValue) => { 
// newValue === oldValue
},
{ deep: true } 
)
//当直接侦听一个响应式对象时,侦听器会自动启用深层模式:
const state = reactive({ count: 0 }) 
watch(state, () => { /* 深层级变更状态所触发的回调 */ })

watchEffect() 和 watchPostEffect() ,watchSyncEffect()

watch(): 是懒执行的:当数据源发生变化时,才会执行回调。但在某些场景中,我们希望在创建侦听器时,立即执行一遍回调。当然使用 immediate 选项也能实现。
watchEffect() 相当于将 watch 的依赖源和回调函数合并,当任何你有用到的响应式依赖更新时,该回调函数便会重新执行。不同于 watchwatchEffect 的回调函数会被立即执行(即 { immediate: true }

const num=ref(0)
watch(num,()=>{console.log(num.value)},{immediate:true});
// 初始化会立即执行需要,当 num 发生变化也会执行,无法获取到原值,只能得到变化后的值
watchEffect(() => console.log(num.value))

与 watch 不同的一点是,在 watchEffect 中依赖源会被重复执行,动态新增加的依赖也会被收集,例如

const counter = ref(0)
const enabled = ref(false) 
watchEffect(() => { 
if (enabled.value) 
    console.log(counter.value) 
})
// (以下忽略 nextTick)
// watchEffect 会被立即执行,因为 “enabled“ 为 false, 
// 此时仅收集到 “enabled“ 依赖
counter.value += 1 // 无反应 enabled.value = true
// Effect 触发,控制台出 "1" 
counter.value += 1 // “counter“ 被作为新的依赖被收集,控制台出 "2"
enabled.value = false // 函数被重新执行,无输出
counter.value += 1   // 函数被重新执行,无输出 (虽然 counter 已经没有用了,但是作为依赖还是会触发函数)

默认情况下,侦听器将在组件渲染之前执行。设置 flush: 'post' 将会使侦听器延迟到组件渲染之后再执行。在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。这可以通过设置 flush: 'sync' 来实现。然而,该设置应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。

watchPostEffect()watchEffect() 使用 flush: 'post' 选项时的别名。 watchSyncEffect()watchEffect() 使用 flush: 'sync' 选项时的别名。

toRef() 和 toRefs()

toRef():基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。如果不是响应式对象使用该方法页面不会更新。
toRefs():相当于对对象内每个属性调用toRef,toRefs返回的对象内的属性使用时需要加.value,主要是方便我们解构使用。如果不是响应式对象使用该方法同样页面不会更新。

const state = reactive({ foo: 1, bar: 2 }) 
const fooRef = toRef(state, 'foo') 
// 更改该 ref 会更新源属性 
fooRef.value++ 
console.log(state.foo) // 2 
// 更改源属性也会更新该 ref 
state.foo++ 
console.log(fooRef.value) // 3
//----------------分隔符------------
const state = reactive({ foo: 1, bar: 2 }) 
const stateAsRefs = toRefs(state)
// 这个 ref 和源属性已经“链接上了”
state.foo++ 
console.log(stateAsRefs.foo.value) // 2
stateAsRefs.foo.value++ 
console.log(state.foo) // 3
//--------------分隔符------------

provide() 和 inject()

provide():提供一个值,可以被后代组件注入。

provide() 接受两个参数:第一个参数是要注入的 key,可以是一个字符串或者一个 symbol,第二个参数是要注入的值。当使用 TypeScript 时,key 可以是一个被类型断言为 InjectionKey 的 symbol。InjectionKey 是一个 Vue 提供的工具类型,继承自 Symbol,可以用来同步 provide() 和 inject() 之间值的类型。

inject():注入一个由祖先组件或整个应用 (通过 app.provide()) 提供的值。

第一个参数是注入的 key。Vue 会遍历父组件链,通过匹配 key 来确定所提供的值。如果父组件链上多个组件对同一个 key 提供了值,那么离得更近的组件将会“覆盖”链上更远的组件所提供的值。如果没有能通过 key 匹配到值,inject() 将返回 undefined,除非提供了一个默认值。

// 提供静态值 
provide('foo', 'bar') 
// 提供响应式的值 
const count = ref(0)
provide('count', count) 
// ----------------------分隔符---------------------------
// 注入值的默认方式
const foo = inject('foo') 
// 注入响应式的值
const count = inject('count')
// 通过 Symbol 类型的 key 注入 
const foo2 = inject(fooSymbol) // 注入一个值,若为空则使用提供的默认值 const bar = inject('foo', 'default value') // 注入一个值,若为空则使用提供的工厂函数 const baz = inject('foo', () => new Map()) // 注入时为了表明提供的默认值是个函数,需要传入第三个参数 
const fn = inject('function', () => {}, false)

第二个参数是可选的,即在没有匹配到 key 时使用的默认值。它也可以是一个工厂函数,用来返回某些创建起来比较复杂的值。如果默认值本身就是一个函数,那么你必须将 false 作为第三个参数传入,表明这个函数就是默认值,而不是一个工厂函数。

与注册生命周期钩子的 API 类似,inject() 必须在组件的 setup() 阶段同步调用。

当使用 TypeScript 时,key 可以是一个类型为 InjectionKey 的 symbol。InjectionKey 是一个 Vue 提供的工具类型,继承自 Symbol,可以用来同步 provide() 和 inject() 之间值的类型。

与注册生命周期钩子的 API 类似,provide() 必须在组件的 setup() 阶段同步调用。

生命周期

Vue2 和 Vue3 对比(不算keep-alive)

Vue2生命周期 image.png Vue3生命周期 image.png 生命周期对比如下

image.png

Vue3的Composition Api

Vue2的OptionsApi

Options API,即大家常说的选项API,即以vue为后缀的文件,通过定义methodscomputedwatchdata等属性与方法,共同处理页面逻辑**

OptionsAPI缺点.gif Vue3

在 Vue3 Composition API 中,组件根据逻辑功能来组织的,一个功能所定义的所有 API 会放在一起(更加的高内聚,低耦合)即使项目很大,功能很多,我们都能快速的定位到这个功能所用到的所有 API

compositionapi演示.gif

大家会说了在Vue2有mixins,也可以完成这些操作,但是mixins有以下问题

  • 变量来源不明确(隐式传入),不利于阅读,使代码变得难以维护。 ...
  • 多个mixins的生命周期会融合到一起运行,但是同名属性、同名方法无法融合,可能会导致冲突。 ...
  • 3.mixins和组件可能出现多对多的关系,复杂度较高(即一个组件可以引用多个mixins,一个mixins也可以被多个组件引用)。

总结

1,以上是部分Vue3知识,其他的Api我会在后面的文章中总结。
2,在我的项目中目前用的这些知识比较多,我就优先罗列出来了。
3,在项目中没有使用Options的操作了,全部使用的setup的语法,组合式的。
4,欢迎大家多多评论,有问题请指出积极校正。