vue3 effectScope解析

什么是effectScope

  • 用于收集在其中所创建的副作用,并能对其进行统一的处理

为什么会有effectScope

对于@vue/reactivity相关的Api,比如ref、computed、reactive、effect、watch等,根据当前的执行环境是否在组件上下文中,有以下两种情况:

  • 在vue的setup中,那么在组件初始化的时候,它们在调用过程中产生的所谓的effect,会被自动收集且绑定到当前组件实例上,在组件卸载时(onUnmounted),effect也会随之卸载掉,这也是一些api提供了stopHandle,但不需要手动调用的原因
// 组件实例被创建的时候也会创建一个scope
export function createComponentInstance(...args) {
    // ...
    const instance = {
        // ...
        vnode,
        type,
        scope: new EffectScope(true /* detached */),
        // ...
    }
    return instance
}
// 组件卸载时会调用 stop 方法
const unmountComponent = (...args) => {
    // ...
    scope.stop()
    // ...
}

// watchEffect 和 watch 返回的 stop 方法
function doWatch() {
    // ...
    return () => {
        effect.stop()
        if (instance && instance.scope) {
            remove(instance.scope.effects!, effect)
        }
    }
}
  • 但我们在组件外使用或者编写一个独立的包时,这会变得不一样,这种情况该如何取消响应式依赖呢
    • 需要开发者手动去消除依赖,对于依赖较多的场景,则会显得很麻烦,甚至会忘掉一些隐蔽性强的依赖造成数据泄露、状态不一致等问题
//(参考 vue-RFC 示例代码)
const disposables = []

const counter = ref(0)
const doubled = computed(() => counter.value * 2)

//需要手动消除依赖
disposables.push(() => stop(doubled.effect))

const stopWatch1 = watchEffect(() => {
  console.log(`counter: ${counter.value}`)
})

disposables.push(stopWatch1)

const stopWatch2 = watch(doubled, () => {
  console.log(doubled.value)
})

disposables.push(stopWatch2)
  • 总结一下:对于现在版本的vue,将 @vue/reactivity 即响应式单独拆分出来了,意味着可以脱离vue环境使用,当不在组件中执行时,也就意味着失去了vue所带来的自动卸载effect的能力,所以开发者需要手动去管理这些effect:
    • 创建scope环境收集effect
    • 适当的时机去除effect,即stop,随之配套的还有 onScopeDispose 来监听 scope 的销毁、getCurrentScope() 获取当前活跃的 scope

使用场景

  • 避免随着组件生命周期重复创建某些监听

// 使用到的组件都会重复创建监听器
function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function handler(e) {
    x.value = e.x
    y.value = e.y
  }

  window.addEventListener('mousemove', handler)

  onUnmounted(() => {
    window.removeEventListener('mousemove', handler)
  })

  return { x, y }
}
  • 通过effecScope创建独立的scope
function useMouse() {
  const x = ref(0)
  const y = ref(0)

  function handler(e) {
    x.value = e.x
    y.value = e.y
  }

  window.addEventListener('mousemove', handler)
  // 通过onScopeDispose替换onUnmounted,意味着可以脱离组件使用
  onScopeDispose(() => {
    window.removeEventListener('mousemove', handler)
  })

  return { x, y }
}

function createSharedComposable(composable) {
  let subscribers = 0
  let state, scope

  const dispose = () => {
    // 通过闭包进行计数,当subscribers为0时,stop掉该scope
    // 如果在组件中使用,则onUnmounted就意味着subscribers-1
    if (scope && --subscribers <= 0) {
      scope.stop()
      state = scope = null
    }
  }

  return (...args) => {
    subscribers++
    if (!state) {
      scope = effectScope(true)
      state = scope.run(() => composable(...args))
    }
    onScopeDispose(dispose)
    return state
  }
}

const useSharedMouse = createSharedComposable(useMouse)

export default useSharedMouse
  • 简易的状态管理

// useGlobalState
import { effectScope } from '@vue/composition-api'

export default run => {
  let state
  const scope = effectScope(true)
  return () => {
    // 防止重复触发
    if (!state) {
      state = scope.run(run)
    }
    return state
  }
}

// store.js
import { computed, ref } from '@vue/composition-api'
import useGlobalState from './useGlobalState'

export default useGlobalState(
  () => {
    // state
    const count = ref(0)
    // getters
    const doubleCount = computed(() => count.value * 2)
    // actions
    function increment() {
      count.value++
    }
    return { count, doubleCount, increment }
  }
)

和useSyncExternalStore区别

  • effecScope 和 React 18的useSyncExternalStore都能做一个简易的状态管理,倒不如说二者都具有收集发布的作用
    • useSyncExternalStore 需要手动订阅,而 effecScope 帮你做了这件事
    • 个人觉得二者最大的区别在于各自框架实现响应式的细节不一样,但最上层订阅发布的思路都差不太多
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值