MartinMalinda / vue-concurrency

A library for encapsulating asynchronous operations and managing concurrency for Vue and Composition API.
https://vue-concurrency.netlify.app/
MIT License
352 stars 15 forks source link

Accessing component reference/state inside running task #80

Open mdcarreira opened 2 years ago

mdcarreira commented 2 years ago

I've read the open issue that presents a problem similar to mine, but the discussion digressed and I couldn't find a solution to the problem. So I'm opening this new issue.

I used ember-concurrency when developing in Ember, and was very happy when I found that this port exists for vue.

In Ember, we can use this to access component's state while performing a task. Unfortunately, I couldn't find a way to do the same in vue-concurrency. This would be very useful to access variables, functions, using this.$emit to emit events, etc.

I could implement it by making the task receive the component instance as an argument, but this feels a bit weird... Passing the component instance as argument does not feel right.

I found that there is a useAsyncTask() that could be defined using the arrow function and therefore would run in the same context. But for some reason this is undefined while executing the task.

image

Is there any other way to access the component instance while a task is being executed that I missed? Or this is not possible at all?

MartinMalinda commented 2 years ago

Hey @mdcarreira!

I can see how you want to access this if you're coming from Ember. But in the setup() function this is undefined as you see in the useAsyncTask(). So there's no way to fix this for vue-concurrency it's rather a Vue Composition API issue.

It's probably WAI that this is undefined. With composition API you should be fine with

There should be no need to access the component instance, unless you use the Option API. vue-concurrency kind of requires you to go all in on Composition API, so I advise to read up on it more.

mdcarreira commented 2 years ago

Hey @MartinMalinda! Thank you so much for your fast reply :)

I'm actually using the Vue Class Component library. I've seen people using the Composition API with class components, but I would prefer to continue using the class style syntax as I come from the Java world.

Can you think of a good way to go around this using the class component syntax?

Maybe I'm being overly perfectionist by not wanting to pass this as an argument to the task... But one thing that bothers me is that this keyword won't work in the template. If I have someTask.perform(this) in the template, the task will receive a null argument (??).

If I have a function in the script that performs the task with someTask.perform(this), the component instance is correctly passed to the task. This means that I would need to have a function just to perform the task, for every task... which is some boilerplate code that I would like to avoid.

I'm still a Vue newbie, so probably I'm making some mistake :)

MartinMalinda commented 2 years ago

I'd definitely recommend to experiment with Composition API more. It's not OOP at all, although you could do some custom OOP in the context of the setup function, but it's the most idiomatic and fleixble way to get things done in Vue for the future.

In setup function you can reuse code the most effective way, you don't have to rely on mixins and such and you can install librarie like vue-use and quickly use many useful hooks from other people.

But for the time being, if you hold on to classes I'd try to use the SetupContext.

export default {
  setup(props, context) {
    // Attributes (Non-reactive object, equivalent to $attrs)
    console.log(context.attrs)

    // Slots (Non-reactive object, equivalent to $slots)
    console.log(context.slots)

    // Emit events (Function, equivalent to $emit)
    console.log(context.emit)

    // Expose public properties (Function)
    console.log(context.expose)

   const task = useTask(function * () {
     yield fetch(....);
     context.emit('someEvent'); // if youre using events
     props.onSuccess('some value'); // if youre using callback props
   });
  }
}

But one thing that bothers me is that this keyword won't work in the template. If I have someTask.perform(this) in the template, the task will receive a null argument (??).

Yeah I'm afraid you're figting the framework at this point :(

Vue is evolving away from OOP, mixins, inheritance and all that. If you really wish to keep this style (I'm not saying it's wrong in general, just not suitable in Vue), maybe vue-concurrency is not the best option. Perhaps it would have to be forked so it's more suitable for the Options API.

mdcarreira commented 2 years ago

Thank you @MartinMalinda for your valuable insights. You are probably right, maybe I'm better off switching to Composition API for the long term.

In the meanwhile, I wanted to try the solution you suggested, but if I export default the object you suggest, then my class exportation will have to be named: @Component export class MyClassComponent, and then everything breaks. What am I missing?

Thanks again for all the help :)