Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why don't I need `this` in v-on attribute values?

Tags:

vue.js

vuejs2

Is there a slightly more precise specification of the v-on attribute syntax of v-on attributes other than my current understanding? :

You can for the most part use normal JavaScript syntax except only call methods and reference props or computed props and you don't need this anywhere. Sort of...

The otherwise excellent vue docs are not very helpful here. I've gotten used to writing:

<foo-bar @someevent="method"/>

and even

<foo-bar @someevent="event => method('someArg', event)"/>

But it has always bugged me that I don't need this in there and that global functions don't work as in:

<foo-bar @someevent="console.log"/>

giving: Property or method "console" is not defined on the instance but referenced during render.

How do these v-on attributes work and what is their syntax - really? In-depth?

Concretely, I noticed that these two work:

<foo-bar @someevent="(event) => $emit('someevent', event)"/>
<foo-bar @someevent="(...args) => reEmitMethod('someevent', ...args)"/>

But not

<foo-bar @someevent="(...args) => $emit('someevent', ...args)"/>

giving the cryptic error message: [Vue warn]: Error in v-on handler: "TypeError: vm is undefined"

Why doesn't that work, when the above two do?

I'm guessing that the reason I don't understand why the last example fails is because I don't fundamentally understand how v-on attributes work, why global functions don't work and how it is possible that I don't need this anywhere.

I guess this is just a special case of template syntax but reading that still doesn't leave me with a feeling of deep understanding...

like image 800
Peter V. Mørch Avatar asked Oct 15 '22 13:10

Peter V. Mørch


1 Answers

Vue.js template compiler cheats a little bit when it comes to evaluating the expression inside the template. I don't think it's in documentation and it works good enough on an intuitive level as you have described, and maybe that is a topic that needs to be described in the documentation proper.

If you want to know how Vue.js template compiler works in general, there's a comprehensible talk by Evan You (Vue.js author) complete with an interactive tool that lets you see the result of template compilation. Notice the 'strip this' checkbox in the upper right corner of template explorer, because it is easy to miss with(this){ ... } expression in the render function.

The thing is that the template compiler (at the time of writing v2.6.10) checks the contents of v-on clause with a couple of regular expressions and writes the handler function depending on which regexp matches the content. So your expression is not processed into AST by some sort of JS compiler in runtime for the purposes of determining what kind of expression it is, it is just quickly dropped into one of four buckets. But it is processed by a transpiler called buble before that which handles spread operator, arrow functions and other ES2015 features.

Below is my understanding of the code, which you can see here

I. Expression is a method path, which means the method handler is passed directly

<div @click="method"></div>
// compiled to
return _c('div', {
  on: {
    "click": method
  }
})

II. Expression is a function expression

<div  @click="function() { console.log('why do I fail?') }"></div>
// compiled to (using with)
return _c('div', {
  on: {
    "click": function () {
      console.log('why do I fail?')
      /*
        console is no longer in your global scope replaced by rendering context
        arguments can be defined in this function definition
      */
    }
  }
})

III. Expression is a function invocation

<div @click="method(myOtherArgument)"></div>
// compiled to
return _c('div', {
  on: {
    "click": function ($event) {
      return method(myOtherArgument)
      /*
        this is why you usually want to call method with
        $event to pass event with some other argument, because $event is in lexical scope
      */
    }
  }
})

IIIa. ES2015 function invocation

<div @click="(...args) => $emit('someevent', ...args)">Why do I fail?</div>
// transpiled (not using with!)
return _c('div', {
 on: {
  "click": function () {
    var args = [],
      len = arguments.length;
    while (len--) args[len] = arguments[len];

    return _vm.$emit.apply(void 0, ['someevent'].concat(args));
    // apply gets called with undefined as its first argument, looks like a bug
  }
 }
})

IV. Expression is a 'raw expression' (basically, none of the above)

<div  @click="method && method"></div>
// compiled to
return _c('div', {
  on: {
    "click": function ($event) { // note that $event has appeared in function definition
      method && method // note no 'return' statement
    }
  }
})

To reiterate, Vue template compiler uses a couple of deliberate tricks to minimize code that needs to be written by the user of the framework, but it comes at a cost of missing some finer details when users start putting more complex expressions in the handler

like image 197
esmekhov Avatar answered Oct 20 '22 23:10

esmekhov