Skip to content

This <3 Vue #168

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

Closed
jods4 opened this issue Apr 28, 2020 · 6 comments
Closed

This <3 Vue #168

jods4 opened this issue Apr 28, 2020 · 6 comments

Comments

@jods4
Copy link

jods4 commented Apr 28, 2020

I made something that I think is worth discussing here.
I made this work with Vue 3.

For some typical components, it leads to very simple code, check this:

const App = defineStatefulComponent({
  setup() {
    return {      
      items: ["Hello", "World"],
      
      newItem: "",
      
      add: () => {
        this.items.push(this.newItem)
        this.newItem = ""
      },
      
      remove(item) {
        this.items.splice(this.items.indexOf(item), 1)
      },
      
      count: computed(() => this.items.length),
    }
  }
})

Magic?
You can test it and see how the trick is done here:
https://codesandbox.io/s/nice-engelbart-259gq

@jods4
Copy link
Author

jods4 commented Apr 28, 2020

It also fixes those nasty circular typing issues, granted you:

  • use the add() {...} syntax instead of add: () => ...
  • put your computed into a getter: get count() { return computed... }

Demo

Edit: Scrap that, the getter is returning a different computed everytime. It's not what we want. It does not really fix the TS issues I guess.

@KorHsien
Copy link

This is cool. 👍

Is it a bug that the handler defined in the setup function don't get bound to the context? If we define the handler in methods option, or use invocation in the template instead of method name (<button @click="add()">Add</button>), the add() {...} syntax will work.

In fact, we could use getter function directly without computed at all: get count() { return this.items.length }

So this will work:

const App = defineComponent(() => ({
  items: ["Hello", "World"],

  newItem: "",

  add() {
    this.items.push(this.newItem);
    this.newItem = "";
  },

  remove(item) {
    this.items.splice(this.items.indexOf(item), 1);
  },

  get count() {
    return this.items.length;
  }
}));

We just blend the data, methods and computed together, this looks really nice.

@jods4
Copy link
Author

jods4 commented Apr 29, 2020

Is it a bug that the handler defined in the setup function don't get bound to the context?

Not a bug, by design. AFAIK the message for Vue 3 is: never use this.
Which is understandable, because that way you don't have to explain what works and what doesn't.

Regarding event handlers, if you use this in your template:

<button @click="do()" />

Then it gets compiled to () => _ctx.do() and this is what you expect (with a twist: if setup returns a non proxy, it's automatically wrapped in a reactive by Vue, which means this is gonna be that wrapper, not the object you returned).

On the other hand, if you use:

<button @click="do" />

Vue will store that function and later invoke it with an undefined this.

In fact, we could use getter function directly without computed at all

Of course! It doesn't behave exactly the same, though.
computed caches its value and provides a single tracking point for consumers.
This counts if your computed is very expensive to compute or if you have many consumers of that value.
For trivial expressions used once in the template, plain getters are the way to go. I see people over-using computed, thinking they have to use it to be able to create reactive expression but you don't.
(In fact my demo is a bad example but it was to illustrate that computed worked.)


Because of all this, as long as:

  • you bind events with @click="do()"
  • declare your methods on the object literal with do() { ... } or as non-bound functions do: function() { ... }
  • stick to state + methods + getters, i.e. no computed.

Then you can just return a plain object and everything works, which is nice for simple components.

The demo I made above use a helper defineStatefulComponent that enables two more use cases:

  • You can use lambdas do: () => abc.
  • Which means of course you can use computed.

And that covers quite a bit of ground for simple components.


Just to be clear: this is not an attempt at replacing the Vue 3 model, which gives you lots of power and flexibility and is required for other stuff such as watchers, hooks, etc.

It's an attempt at removing ceremony and simplifying usage in simple components that are just state + methods. Sounds silly but quite a few component fall into that category in practice and I find myself doing this pattern a lot:

setup() {
  const state = reactive({
    ...
  });
  return state;

this is also more TS-friendly so I hope I can find an elegant fix for the circular type inference problem; I need more time to think about it.

@KorHsien
Copy link

AFAIK the message for Vue 3 is: never use this.

Totally understandable. But as for event handlers, the distinction between merely the presence or absence of parentheses, to me, seems unreasonable.

(with a twist: if setup returns a non proxy, it's automatically wrapped in a reactive by Vue, which means this is gonna be that wrapper, not the object you returned).

Am I missing something, doesn't this always refer to the _ctx?

Of course! It doesn't behave exactly the same, though.

Wait, so the getters in reactive don't work like computed? Wow, when I saw getters in reactive, I immediately assumed it'll be treated as computed. Sadly that's not the case. But anyway, would it be a good idea?

Just to be clear: this is not an attempt at replacing the Vue 3 model

Definitely! I was just tried to demonstrate that how clean it could be for simple components like this.

@CyberAP
Copy link
Contributor

CyberAP commented May 10, 2020

You can actually do exactly the same thing with data.

export default {
  data() {
    return {
      value: 1,
      get counter() { return 'counter: ' + this.counter },
      increment: () => { this.value++ }
    }
  }
}

@jods4
Copy link
Author

jods4 commented Jul 27, 2022

This issue was part of some experiments I did during the Vue 3 betas to check how the new composition API could, with small tweaks, lend itself to define components with classes.

2 years later Vue 3 shipped and the UX improvements went in different directions like <script setup>, so I don't think there's much to say or act upon in this issue -> I'm closing it.

@jods4 jods4 closed this as completed Jul 27, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants