Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(app): support app level effect scope #8801

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

Conversation

Mini-ghost
Copy link
Contributor

close #8787

Copy link
Member

@antfu antfu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general this feature makes sense to me.

Copy link
Member

@posva posva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember we intentionally stuck to only inject and provide.

That being said I think this is really useful and did propose it too but wanted to avoid adding too much right away to runWithContext() 🤪. The router and pinia could benefit from this too.

Could you add a test?
This could probably be moved to createApp() too

@posva
Copy link
Member

posva commented Aug 9, 2023

This would potentially also enable an app.onUnmount() with just one line

app.onUnmount = (fn) => app.runWithContext(() => onScopeDispose(fn))

Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com>
@Mini-ghost
Copy link
Contributor Author

Thank you for your feedback. I have a couple of questions I would like to confirm:

  1. In feat(runtime-core): app.onUnmount() for registering cleanup functions (close: #4516) #4619, the implementation of adding app.onUnmount functionality is located in runtime-core/src/apiCreateApp.ts instead of runtime-dom/src/index.ts. I'm not sure if this PR should follow this approach.

  2. Currently, Vue internally utilizes the new EffectScope(true) approach. Would it be advisable for this PR to adopt a class-based approach instead of using effectScope(true)?

Thank you! Your invaluable guidance means a lot to me.

@posva
Copy link
Member

posva commented Aug 9, 2023

  1. I think yes, I don't see why this should be in runtime-dom instead of core
  2. I'm not sure but I don't think it matters

@Mini-ghost
Copy link
Contributor Author

I have moved the implementation to runtime-core and added a test case. Could you please review?

@Mini-ghost Mini-ghost requested a review from posva August 9, 2023 17:53
Copy link
Member

@posva posva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. It would be nice to have feedback from @yyx990803 to know if he can think of any reason not to have an effect like this.

One thing work noting is that any user doing this:

const myEffect = effectScope()

myEffect.run(() => app.runWithContext(fn))

Will have to rewrite it to

app.runWithContext(() => myEffect.run(fn))

Which is technically a breaking change. I haven't checked the usage of this in the wild though but I will have to update pinia before this comes out as I currently have the code shown above 😆

Comment on lines +214 to 215
const scope = effectScope(true)
const context = createAppContext()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes me wonder if the effectScope should be in app context 🤔

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@posva In the latest commit (a7141d4), I added scope to the app. Currently, I have handled it as a private property. Should it be made public for user access?

packages/runtime-core/src/apiCreateApp.ts Outdated Show resolved Hide resolved
Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com>
@skirtles-code
Copy link
Contributor

I'm wondering about whether this could cause memory leaks, and whether that matters.

I've put together an example using Vue Router:

Steps to reproduce:

  1. Navigate between the two routes a few times by clicking the links.
  2. Take a Heap Snapshot (make sure you select the main VM, not the workers).
  3. Filter the snapshot by the class VTrackLeak.
  4. On main you'll just find a single instance, but with this PR you'll find an extra instance per navigation.

The key parts of the code are:

router.beforeEach(() => {
  const { doubled } = useBlah()

  if (doubled.value) {
    // ...
  }
})

with:

function useBlah() {
  const raw = ref(1)
  const doubled = computed(() => raw.value * 2)

  raw.marker = new VTrackLeak()

  return { raw, doubled }
}

The callback for beforeEach is wrapped in app.runWithContext by Vue Router. That will capture the effect for the computed in useBlah, preventing it from being GCed until the application is unmounted, which may never happen.

@posva
Copy link
Member

posva commented Jun 13, 2024

@skirtles-code I'm surprised they don't both leak. I would consider that usage wrong, but it does show a distinction between being able to inject and being able to use reactive effects is important. I think it makes more sense to keep things separate and adding an effectScope to the app would allow to have app.scope.run() to track reactive effects, keeping both APIs separate and forcing libraries like the router to use any they feel are relevant.

@haoqunjiang haoqunjiang added need guidance The approach/solution in the PR is unclear and requires guidance from maintainer to proceed further. ready for review This PR requires more reviews scope: reactivity labels Jun 17, 2024
@Mini-ghost
Copy link
Contributor Author

@skirtles-code

I studied the issue and found that it is likely caused by the ReactiveEffect inside computed. In the example, if computed is not used, the issue does not occur.

I am unsure if the usage in the example is intended. Should we discourage users from using ref this way? Also, the example causes memory leaks in Nuxt too.

I am not sure how to fix this issue. I would greatly appreciate any guidance or suggestions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
need guidance The approach/solution in the PR is unclear and requires guidance from maintainer to proceed further. ready for review This PR requires more reviews scope: reactivity
Projects
Status: Needs further expertise
Development

Successfully merging this pull request may close these issues.

onScopeDispose will not be called in runWithContext
5 participants