Vue3响应式


vue的核心是声明式渲染:通过扩展于标准HTML的模版语法,可以根据JavaScript的状态来描述HTML应该是什么样子的.当状态改变,HTML会自动更新.

响应式基础(核心)

能在改变时触发更新的状态被称作响应式

ref

  1. ref 可以接受任何类型,但是会返回一个包裹的对象,并用.value属性暴露对应的值
  2. 想要模板中使用ref进行声明变量,需要在script setup 中将其返回出来,不需要带有value属性
  3. ref 解决了vue2中SFC组件中data声明数据数据的变更失真的情况(Vue2中有个$set 方法,vue3中不存在,原因就是这样)
  4. 深层次的响应

reactive

  1. 只适用于对象(包括数组和内置类型,Map和Set同样)
  2. 创建的对应都是JavaScript Proxy,行为和普通对象一样
  3. 对于对象而言,该属性声明的变量也是深层次的响应性
  4. ref 声明对象的时候,也是调用的该Api(因此,ref可以声明一切可以声明的东西)
  5. 通过ref或者reactive声明的对象才具有响应性,原始对象是没有响应性的。
  6. 未代理的对象会生成一个代理对象,已经代理的对象,使用该方式则会返回原代理对象
  7. 局限性:
    - 有限的值类型(只能用于对象类型,例如:数组,对象,Set,Map等数据类型)
    - 不能替换整个对象(Vue 的响应是通过对象属性进行追踪的,因此整个响应性对象必须是同一个,如果替换成其他对象,则会使原对象的响应性失效)
    - 解构不太好 (结构之后的变量,会失去响应性,ref的结构也是没有响应性(不清楚为什么官方文档要推荐使用ref,ref声明的变量,只能结构value。结构后,只是做了一层代理))

computed

  1. 官方文档解释,computed 期望接受的是一个getter函数(),这个获取的值是同ref声明变量的值一样,使用时需要带有.value。模板表达式中无需解包(即,无需使用.value进行表示)
  2. 可以接收一个带有get和set函数的对象来创建一个可写的 ref 对象。
  3. 与方法的区别: 缓存(只有对应响应式变更的时候,才会触发computed),时机(方法总是在重渲染后才执行)
  4. 注意,getter函数里面不应该有其他副作用(不要改变其他状态、在 getter 中做异步请求或者更改 DOM!
// TS 类型
// 只读
function computed<T>(
  getter: (oldValue: T | undefined) => T,
  // 查看下方的 "计算属性调试" 链接
  debuggerOptions?: DebuggerOptions
): Readonly<Ref<Readonly<T>>>

// 可写的
function computed<T>(
  options: {
    get: (oldValue: T | undefined) => T
    set: (value: T) => void
  },
  debuggerOptions?: DebuggerOptions
): Ref<T>

// 例子
// 只可读
const count = ref(1)
const plusOne = computed(() => count.value + 1)

console.log(plusOne.value) // 2

// 可读可写
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

readonly

  1. 只读,可接受一个对象(响应式或者原始对象)或一个ref,返回一个只读代理
  2. 属于深层次的,如果想要浅层次的同理使用shallowReadonly。
// 例子
const original = reactive({ count: 0 })

const copy = readonly(original)

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

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

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

watchEffect

  1. 立即执行函数,同时响应式地追踪其依赖,并在依赖更改时重新执行
  2. 两个参数:
    1. 第一个参数就是要运行的副作用函数。这个副作用函数的参数也是一个函数,用来注册清理回调。
    2. 第二参数是一个可选的选项,可以用来调整副作用的刷新时机或调试副作用的依赖。
// TS 类型
function watchEffect(
  effect: (onCleanup: OnCleanup) => void,
  options?: WatchEffectOptions
): StopHandle

type OnCleanup = (cleanupFn: () => void) => void

interface WatchEffectOptions {
  flush?: 'pre' | 'post' | 'sync' // 默认:'pre'
  onTrack?: (event: DebuggerEvent) => void
  onTrigger?: (event: DebuggerEvent) => void
}

type StopHandle = () => void

// 例子

const count = ref(0)

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

count.value++

// 清除回调
watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(id.value)
  // `cancel` 会在 `id` 更改时调用
  // 以便取消之前
  // 未完成的请求
  onCleanup(cancel)
  data.value = await response
})

// 停止监听器
const stop = watchEffect(() => {})

// 当不再需要此侦听器时:
stop()

watchPostEffect,watchSyncEffect

属于watchEffect的别名,分别对应post时机,和Sync时机,可以查看上面watchEffect。

不过post这个是将会使侦听器延迟到组件渲染之后再执行。

Sync这个在某些特殊情况下 (例如要使缓存失效),可能有必要在响应式依赖发生改变时立即触发侦听器。

注意: sync方法应谨慎使用,因为如果有多个属性同时更新,这将导致一些性能和数据一致性的问题。

watch

普通的监听函数,与Vue2中类似。

官方回答:

  1. 侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
  2. 默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
  3. watchEffect的比对:
    1. 懒执行副作用;
    2. 更加明确是应该由哪个状态触发侦听器重新执行;
    3. 可以访问所侦听状态的前一个值和当前值。
  4. 接受三个参数:
    1. 侦听器的源 (源的来源有这些:一个函数,返回一个值。一个ref。一个响应式对象。或者是上面组成的数组)
    2. 是在发生变化时要调用的回调函数。该函数接受3个参数,新值,旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用。当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值。
    3. 可选的参数是一个对象。
      1. immediate 立即执行
      2. deep 深层次改变
      3. flush 调整回调函数的刷新时机。
      4. onTrack / onTrigger 调试侦听器的依赖。
      5. once 回调函数只会运行一次。

注意

setup

  1. 有两种形式,第一种:script setup,第二种: setup(){}
  2. 第一种,声明变量和方法类似原生,无需return对应的变量
  3. 第二种,需要return声明的变量

响应式工具

isRef()

检查某个值是否为 ref。

unref()

如果参数是 ref,则返回内部值,否则返回参数本身。

toRef()

也可以基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。

toValue(3.3+)

将值、refs 或 getters 规范化为值。这与 unref() 类似,不同的是此函数也会规范化 getter 函数。如果参数是一个 getter,它将会被调用并且返回它的返回值。

这可以在组合式函数中使用,用来规范化一个可以是值、ref 或 getter 的参数。

import type { MaybeRefOrGetter } from 'vue'

function useFeature(id: MaybeRefOrGetter<number>) {
  watch(() => toValue(id), id => {
    // 处理 id 变更
  })
}

// 这个组合式函数支持以下的任意形式:
useFeature(1)
useFeature(ref(1))
useFeature(() => 1)

toRefs()

将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。

// 这个例子标明了响应性的深入
const state = reactive({
  foo: 1,
  bar: 2
})

const stateAsRefs = toRefs(state)
/*
stateAsRefs 的类型:{
  foo: Ref<number>,
  bar: Ref<number>
}
*/

// 这个 ref 和源属性已经“链接上了”
state.foo++
console.log(stateAsRefs.foo.value) // 2

stateAsRefs.foo.value++
// 进行解构的时候,不失去响应性
function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  // ...基于状态的操作逻辑

  // 在返回时都转为 ref
  return toRefs(state)
}

// 可以解构而不会失去响应性
const { foo, bar } = useFeatureX()

isProxy()

检查一个对象是否是由 reactive()、readonly()、shallowReactive() 或 shallowReadonly() 创建的代理。

isReactive()

检查一个对象是否是由 reactive() 或 shallowReactive() 创建的代理。

isReadonly()

检查传入的值是否为只读对象。只读对象的属性可以更改,但他们不能通过传入的对象直接赋值。主要监测通过 readonly() 和 shallowReadonly() 创建的代理都是只读的

响应式进阶

shallowRef

ref 属于深层次响应,也就是说使用ref声明的变量在使用的时候,如果接收的值是复杂类型,当对应的属性有变动时,都会触发对应的响应式(watchEffect 进行监听)。

而shallowRef 属于浅层次响应,但是对应的内容还是会进行变更。(如果下图不存在的话:可自行打印一下ref和shallowRef声明变量时的结构。变量套用多层对象形式。)

因此,对于大型数据、以及外部库管理其内部状态的情况,请使用shallowRef进行声明,这样会减少响应性的追踪。
ref和shallowRef声明变量的不同打印不同值

import {ref, shallowRef, watchEffect} from 'vue'
const list = ref([1,2,3])
const shallowList = shallowRef([1,2,3])

list.value[0] = 4
// list => [4,2,3]

// 响应性不会变动
shallowList.value[0] = 4
// shallowList => [4,2,3] 界面上会进行变化
// 响应性会进行变动
shallowList.value = []
// 这里可以使用立即执行函数可以看到响应性是否进行变动
watchEffect(() => {
console.log(shallowList.value)
})

triggerRef

这个是配合上面的shallowRef进行使用的,它主要的作用就是强制触发依赖于一个浅层 ref 的副作用,这通常在对浅引用的内部值进行深度变更后使用。 副作用:一般来说指的是响应性。

const shallow = shallowRef({
  greet: 'Hello, world'
})

// 触发该副作用第一次应该会打印 "Hello, world"
watchEffect(() => {
  console.log(shallow.value.greet)
})

// 这次变更不应触发副作用,因为这个 ref 是浅层的,但是值是进行变化的,打印可以看出来的
shallow.value.greet = 'Hello, universe'

// 打印 "Hello, universe"
triggerRef(shallow)

customRef

创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。

customRef() 预期接收一个工厂函数作为参数,这个工厂函数接受 track 和 trigger 两个函数作为参数,并返回一个带有 get 和 set 方法的对象。

一般来说,track() 应该在 get() 方法中调用,而 trigger() 应该在 set() 中调用。然而事实上,你对何时调用、是否应该调用他们有完全的控制权。

shallowReactive()

浅层的reactive()

shallowReadonly()

浅层的readonly()

toRaw()

根据一个 Vue 创建的代理返回其原始对象。

toRaw() 可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象。

这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。

// 类型
function toRaw<T>(proxy: T): T

// 例子
const foo = {}
const reactiveFoo = reactive(foo)

console.log(toRaw(reactiveFoo) === foo) // true

markRaw()

将一个对象标记为不可被转为代理。返回该对象本身。

// 类型
function markRaw<T extends object>(value: T): T

// 例子
const foo = markRaw({})
console.log(isReactive(reactive(foo))) // false

// 也适用于嵌套在其他响应性对象
const bar = reactive({ foo })
console.log(isReactive(bar.foo)) // false

effectScope()

创建一个 effect 作用域,可以捕获其中所创建的响应式副作用 (即计算属性和侦听器),这样捕获到的副作用可以一起处理。

// 类型
function effectScope(detached?: boolean): EffectScope

interface EffectScope {
  run<T>(fn: () => T): T | undefined // 如果作用域不活跃就为 undefined
  stop(): void
}
// 例子
const scope = effectScope()

scope.run(() => {
  const doubled = computed(() => counter.value * 2)

  watch(doubled, () => console.log(doubled.value))

  watchEffect(() => console.log('Count: ', doubled.value))
})

// 处理掉当前作用域内的所有 effect
scope.stop()

getCurrentScope()

如果有的话,返回当前活跃的 effect 作用域。

// 类型
function getCurrentScope(): EffectScope | undefined

onScopeDispose()

在当前活跃的 effect 作用域上注册一个处理回调函数。当相关的 effect 作用域停止时会调用这个回调函数。

这个方法可以作为可复用的组合式函数中 onUnmounted 的替代品,它并不与组件耦合,因为每一个 Vue 组件的 setup() 函数也是在一个 effect 作用域中调用的。

// 类型
function onScopeDispose(fn: () => void): void
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值