Vue3.0 中的 effect

6,597 阅读4分钟

前言

本文源自自己研究总结,不教学,不科普。有兴趣的同学欢迎交流研究。 最先接触 Effect 的概念是在 React Hooks 中,可以在其中进行一些副作用操作。你只需要将准确的依赖传入,并且注册好你期望处理的副总用操作即可,React 中是这个样子使用的。

useEffect( () => {
    document.title = a;
} [a])

当 a 变化时,对应的回调函数就会执行。

Vue3.0 Effect

Vue3.0 中的 effect 同样会在响应式数据发生改变时,去执行对象的注册回调。看下面的代码

const a = ref(1)
effect( () => {
    console.log(a.value)
} )
a.value = 2 // 会打印2

虽然写法上有所区别,但是核心没有变,数据改变,执行我们期望中的事情(回调)。真要说区别其实也很明显,一个是显式的写入依赖,一个算半显式的(这取决于你心里有没有把数据当成显式的依赖)。

这里需要注意,effect 在注册完成后,如果没有传入相关参数(下面介绍那些参数),会立即执行一次回调函数用来依赖收集。
但是,今天只是做一个学习 3.0 effect 的记录,并不会展开对比,下面开始进入正题。

effect 的使用

effect 的最基本用发在上面例子中已经做了展示,这里会展示一个稍稍进阶点的用法 ---> 使用 effect 的返回值(本文中统一用 runner 来代替)

import { effect, reactive } from "@vue/reactivity"

let a = null;
let run = false;
const obj = reactive({props: 'value'})
const runner = effect(() => {
    a = run ? obj.props : "other"
})

runner() // a === other

run = true

runner() // a=== value

runner 允许你在合适的时机手动去执行 effect 的回调,这一点给你使用者更多的发挥和想象空间。

注意: 如果传入的回调函数有返回值,将在执行 runner() 时返回对应的返回值。

Effect 的配置参数们

下面的表格展示了配置项和他们的介绍,后面会逐一给出例子方便理解和记忆。

api 类型 参数 默认值 备注
lazy boolean ~~~ false 此值为 true 时,只有在第一次手动调用 runner 后,依赖数据变更时,才会自动执行 effect 的回调,可以理解为 effect 的是在手动调用 runner 后才首次执行
scheduler function runner none 传入此参数,开启了调度模式,只有手动调用 runner 时,才会执行对应的注册回调
onTrack function trackType[](见下面 trackType) none 参数包含了依赖数据的每次更新进行的操作对象
onTrigger function trigger[](见下面 trigger) none 此方法是当依赖的数据被改变是会显示相应值得改变栈
onStop function none none 在调用 stop 停止 effect 时触发执行
interface trackType{
    effect: runner,  // effect 函数的返回值
    target: toRaw(obj),  // 表示的是发生属性变化的数据的源对象
    type: TrackOpTypes.GET,  // 表示此次记录操作的类型。 get 表示获取值(更多类型下面介绍)
    key: 'foo' // 变化的键,可以是任意合法的键
}

interface tigger{
      effect: runner,
      target: toRaw(obj),
      type: TriggerOpTypes.SET,
      key: 'foo',
      oldValue: 1,
      newValue: 2
}

配置代码演示环节

lazy

 import { effect, reactive } from "@vue/reactivity"

const obj = reactive({foo:1})
let a = null;
const runner = effect( () => (a = obj.foo) )
console.log(a) // null
runner() // 1  注意,如果回调函数有返回值,则 runner 函数的返回值与回调函数的返回值相同
obj.foo = 2
console.log(a) //2

scheduler

import { effect, reactive } from "@vue/reactivity"
let runner: any, dummy
const scheduler = _runner => {
  runner = _runner
}
const obj = reactive({ foo: 1 })
const runner1 = effect(
  () => {
    dummy = obj.foo
  },
  { scheduler }
)

console.log(dummy) // 1
// scheduler 将在这里首次被调用
obj.foo++

console.log(dummy) // 1 注意此时的值没有更新

runner() // 手动调用 runner , 在这里调用 runner1 也能达到同样的效果, 因为他们两个是同一个函数 使用 scheduler 方式可以使你的逻辑分离,使 runner 可以传播,已进行数据的更新

console.log(dummy) // 2 此时值更新了

onTrack

import { effect, reactive } from "@vue/reactivity"
    let events = []
    let dummy
    const onTrack = (e) => {
      events.push(e)
    }
    const obj = reactive({ foo: 1, bar: 2 })
    const runner = effect(
      () => {
        dummy = obj.foo
        dummy = 'bar' in obj
        dummy = Object.keys(obj)
      },
      { onTrack }
    )
    console.log('runner', runner)
    console.log('dummy', dummy) // ['foo', 'bar']
    obj.foo++
    console.log("events", events)

    // events 的格式如下
    [
        {
        effect: runner,  // effect 函数的返回值
        target: toRaw(obj),  // 表示的是哪个相应式数据发生了变化
        type: TrackOpTypes.GET,  // 表示此次记录操作的类型。 get 表示获取值
        key: 'foo'
      },
      {
        effect: runner,
        target: toRaw(obj),
        type: TrackOpTypes.HAS,  // has 表示检测属性是否存在
        key: 'bar'
      },
      {
        effect: runner,
        target: toRaw(obj),
        type: TrackOpTypes.ITERATE,  // 表示迭代
        key: ITERATE_KEY
      }
    ]

onTrigger

import { effect, reactive } from "@vue/reactivity"
    let events = []
    let dummy
    const onTrigger = (e) => {
      events.push(e)
    }
    const obj = reactive({ foo: 1 })
    const runner = effect(
      () => {
        dummy = obj.foo
      },
      { onTrigger }
    )

    obj.foo++  // 此时会调用 onTrigger
    console.log(dummy) // 2
    console.log(events[0])
    /**
    {
      effect: runner,
      target: toRaw(obj),
      type: TriggerOpTypes.SET,
      key: 'foo',
      oldValue: 1,
      newValue: 2
    }
    */

    delete obj.foo  // 会执行 onTrigger
    console.log(dummy) // undefined
    console.log(events[1])
    /**
    {
      effect: runner,
      target: toRaw(obj),
      type: TriggerOpTypes.DELETE,
      key: 'foo',
      oldValue: 2
    }
    */