Vue-next: Option to not stop watch when component is unmounted

Created on 7 Jul 2020  路  8Comments  路  Source: vuejs/vue-next

What problem does this feature solve?

I have a composition function that is shared between multiple components:

// global shared reactive state
let foo

function useFoo() {
  if (!foo) { // lazy initialization
      foo = ref()
      watch(foo, ...) // <- this is stopped when component that created it is unmounted
      // make some http calls etc
  }
  return foo
}

component1 = {
    setup() {
        useFoo() // lazily initialize
    }
}

component2 = {
    setup() {
        useFoo() // lazily initialize
    }
}

The problem is that watch is stopped when the component that called useFoo first is unmounted.

What does the proposed API look like?

An option for watch

watch(..., {instanceBound: false})

Or a helper that nulls the current instance:

function withNullInstance(cb: Function) {
    const old = getCurrentInstance()
    setCurrentInstance(null)
    cb()
    setCurrentInstance(old)
}

function useFoo() {
    if (!foo) {
        withNullInstance(initFoo)
    }
    return foo
}

Or export getCurrentInstance and setCurrentInstance so this can be solved in user space.

enhancement

Most helpful comment

I think some mechanism for this would be useful for Vuex as well

All 8 comments

I think some mechanism for this would be useful for Vuex as well

So in this case, when do you expect it to be stopped? It seems you will need to manually implement a reference count in order to prevent the watcher from running indefinitely (and potentially lead to memory leaks)

So in this case, when do you expect it to be stopped?

Not stopped at all by Vue. The data is essentially global and lives forever. I could run useFoo from the root component App.vue to achieve the same but:

  • no lazy loading which is nice if the function loads some data using HTTP
  • inconvenient if there are lots of such functions

Watch, watchEffect and computed from vue are wrappers around the lower-level primitives computed and effect exported by the reactivity package.

You can import those to create watches that stop whenever you want.

Converting this into a higher-order function with effect (as proposed above) could potentially solve the problem:

export const createEffectComposable = (ref = ref(null)) => {
  const _effect = effect(() => {
    // ...
  })
  const _stop = () => stop(effect)
  return () => { ref, stop: _stop }
}
export const useFoo = createEffectComposable()
comp1 = {
  setup() {
    const { ref, stop } = useFoo();
  }
}

comp2 = {
  setup() {
    const { ref, stop } = useFoo();
  }
}

composition-api has a solution for this under name activateCurrentInstance https://github.com/vuejs/composition-api/blob/01524a4e4114cbb9443b6d697acb4cfa39f27700/src/utils/instance.ts#L101-L119 . It is private and only changes current instance in composition-api, but the idea is the same.

are wrappers around the lower-level primitives

watch is 300+ lines "wrapper" which is a little bit too much for this task.

@vmihailenco It does a lot of things.
effect from reactivity is basically watchEffect.

RFC #212 provides a solution for this.

Was this page helpful?
0 / 5 - 0 ratings