前语

effectScope是个强壮的api,官网原话:创立一个 effect 效果域,可以捕获其中所创立的响应式副效果 (即计算属性和侦听器),这样捕获到的副效果可以一起处理,在RFC对其有更具体的解说,在vueuse库中更有对其的妙用

为什么要EffectScope

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)

铲除

disposables.forEach((f) => f())
disposables = []

上述过程需人工进行,关于杂乱的逻辑收集成本较高,忘掉收集也或许造成内存泄漏等问题。所以设计EffectScope处理该问题,关于开发库的同学更友好

EffectScope运用

function effectScope(detached?: boolean): EffectScope
interface EffectScope {
  run<T>(fn: () => T): T | undefined // 假如效果域不活跃就为 undefined
  stop(): void
}

子父EffectScope

detached表示是否阻断和父级的联络,若为true则表示与父级断开相关,履行父级stop办法时会递归中止子集的监听,但子集detachedtrue时则不会中止,如下:

let nestedScope
let childScope
const parentScope = effectScope()
parentScope.run(() => {
  const doubled = computed(() => counter.value * 2)
  // 与父级断开链接
  nestedScope = effectScope(true /* detached */)
  nestedScope.run(() => {
    watch(doubled, () => console.log('nestedScope', doubled.value))
  })
  // 父级断开监听也会断开
  childScope = effectScope()
  childScope.run(() => {
    watch(doubled, () => console.log('childScope', doubled.value))
  })
  watchEffect(() => console.log('Count: ', doubled.value))
})
// 中止parentScope、childScope监听
parentScope.stop()
// 中止nestedScope监听
nestedScope.stop()

onScopeDispose

中止监听时触发改函数onScopeDispose,效果类似onUnmounted

import { onScopeDispose } from 'vue'
const scope = effectScope()
scope.run(() => {
  onScopeDispose(() => {
    console.log('cleaned!')
  })
})
// 中止监听,触发onScopeDispose
scope.stop()

功能提升

关于特定场景运用EffectScope功能更好更合理

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 }
}

关于useMouse获取坐标假如多个组件运用则会添加多个监听,添加功能成本,这个时候运用EffectScope更友好

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(() => {
    window.removeEventListener('mousemove', handler)
  })
  return { x, y }
}
function createSharedComposable(composable) {
  let subscribers = 0
  let state, scope
  const dispose = () => {
    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

这样无论多少组件运用,只会进行一次监听,不运用时一起铲除监听

灵敏的状况办理

获取坐标demo现已可以表现出EffectScope状况办理的效果,咱们可以模拟store完结一套更灵敏的状况办理又或用于子父组件、兄弟组件、大局通讯

创立函数统一办理状况

// useGlobalState
import { effectScope } from '@vue/composition-api'
export default run => {
  let isChange = false
  let state
  const scope = effectScope(true)
  return () => {
    // 防止重复触发
    if (!isChange) {
      state = scope.run(run)
      isChange = true
    }
    return state
  }
}

创立store

// 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 }
  }
)

在不同组件运用

// A.vue
import useStore from '@/hooks/useStore'
const { count, doubleCount, increment } = useStore()
// B.vue
import useStore from '@/hooks/useStore'
const { count, doubleCount, increment } = useStore()

在A/B组件获取的countdoubleCount数据共同,所以effectScope一起供给新的的状况办理方式,某种场景可以代替vuex

若是为了完结数据耐久化,参加缓存即可。vueuse现已供给这种状况办理方式createGlobalState,若可以满足需求,直接运用即可

结语

时刻准备着