Skip to content

Make binding syntax more consistent #1173

@yyx990803

Description

@yyx990803

Quick Reference of Latest Syntax

Last updated: Sep. 11th - 1.0.0-alpha.4

<!-- flow control -->
<div v-if="ok">
<div v-for="item in items">
<div v-show="hi">

<!-- two-way form binding -->
<input v-model="abc">

<!-- literal directive: add hash before equal -->
<a v-link#="/abc/123"></a>

<!-- event handlers -->
 <input
    on-change="handleChange"
    on-focus="handleFocus"
    on-blur="handleBlur">

<!-- key filter for keypress events -->
<input on-keyup-esc="handleEsc">

<!-- normal attribute bindings, make it reactive by adding "bind-" -->
<img bind-src="baseURL + '/avatar/' + username + '.png'">
<a bind-href="'/profile/' + username"></a>

<!-- class & style are enhanced to accept objects and arrays -->

<!-- toggle classes -->
<div bind-class="{ 'class-a': true, 'class-b': false }"></div>
<!-- apply a list of classes -->
<div bind-class="[ dynamicClass, 'literal-class' ]"></div>

<!-- apply style object (camelCase accepted) -->
<div bind-style="{ fontSize: '14px', color: 'red' }"></div>
<!-- apply multiple style objects -->
<div bind-style="[ styleObjectA, styleObjectB ]"></div>

<!-- shorthand for "bind-", just add colon -->
<img :src="...">
<a :href="..."></a>

<!-- component props, also use "bind-" or colon shorthand -->
<!-- without "bind-" or colon it's passed as a literal string -->
<component
  literal="hello"
  bind-dynamic="parentMsg"
  :dynamic="something"
  :two-way@="something"
  :one-time*="something">
</component>

<!-- v-el and v-ref now use dedicated syntax -->

<!-- registers vm.$.child -->
<comp $.child></comp>

<!-- registers vm.$$.node -->
<div $$.node></div>

<!-- caveat: must use dash-case instead of camelCase, similar to props -->
<!-- registers vm.$.someComp -->
<comp $.some-comp></comp>

Context

Currently we have several types of bindings in the template:

  1. Reactive directives, e.g. v-style, v-on & v-repeat. Their attribute values are directly evaluated as expressions in the current component scope, and cannot contain mustache tags.
  2. (Dynamic) Literal directives, e.g. v-transition, v-ref & v-el. Their attribute values are treated as plain strings and can contain mustache tags - but it's not always reactive: only v-transition is reactive when containing mustache tags; the other two evaluate them only once.
  3. Normal HTML attributes with mustache tags. These are converted into v-attr internally.
  4. Prop bindings, e.g. my-prop="{{abc}}". Props' attribute values are treated as plain strings and can contain mustache tags; the prop binding is only dynamic if it contains mustache tags.
  5. Directive param attributes, e.g. transition-mode, track-by, number & debounce. These are treated almost as normal attributes, but evaluated only once.

Problem

Well, as you probably have noticed, it's confusing! There are many types of attributes and there's no clear rule on where expressions are expected and where mustache interpolations are allowed.

Specifically, the prop syntax could use some improvement. The original intention of making props require mustaches tags to be reactive is so that a dynamic prop can look different from normal HTML attributes. But given that normal attributes can contain mustache tags as well, it's still not explicit enough. It's also much more common to use dynamic props than literal strings, and using mustache tags to indicate reactivity is simply not intuitive.

Another problem is it becomes awkward when you want to pass a literal number/boolean prop, because without mustache tags, the attribute value is just a string. You'd have to write prop="{{123}}" or prop="{{true}}" to pass a real number or boolean. Currently, Vue auto-casts literal props into numbers/booleans if possible, which this may not always be what we want - what if we want to pass in a string of numbers?

Proposal

Last Updated: Sep.11th (1.0.0-alpha.4)

Here's some pretty radical changes (or maybe not), but imo conceptually much cleaner. The goal here is to 1) eliminate {{ }} inside attribute values; and 2) categorize the syntax by their purpose.

  1. Text and HTML interpolations. Handled with {{ }} and {{{ }}}. And this will also be the only places where mustaches are used.

  2. Vue directives. These preserve the v- prefix because they do something special. Binding values are always parsed as expressions. No more arguments or multiple clauses, just one expression followed by one or more filters. Only v-for (previously v-repeat) preserves the item in items special syntax.

    <!-- view logic -->
    <p v-show="ok"></p>
    <p v-if="!ok"></p>
    <p v-for="item in items"></p>
    
    <!-- two-way binding -->
    <input v-model="val">
    
    <!-- empty directives -->
    <div v-cloak></div>
    <div v-pre></div>

    And that's it. Only 6 core directives. (v-text and v-html are also preserved, but they are replaceable by interpolations)

    Literal Syntax

    In 1.0, all core directives are either reactive or empty. But sometimes we may want to pass in a literal string to a custom directive instead of a dynamic expression, similar to 0.12 literal directives. But in 1.0 we want to make this explicit, so we can clearly know whether the attribute value is actually a string or an expression. So, in 1.0 there will no longer be the concept of "literal directives", we use the dot-equal syntax to indicate we are passing a literal value to the directive. The directive's update function will be called once, with the literal string as the argument:

    <a v-link#="/a/b/c">
  3. Event handlers. Prefixed with on-. Value always parsed as expressions, can either be the method name or a statement (e.g. a = !a). I've raised this once before, but people seemed to really like the fact that v-on starts with v-. IMO event handlers deserve something different and more succinct.

    <!-- easier to type, and reads better -->
    <form on-submit="handleSubmit"></form>
    
    <!-- multiple listeners also much cleaner -->
    <input
      on-change="handleChange"
      on-focus="handleFocus"
      on-blur="handleBlur">
    
    <!-- in addition: key filter can be replaced with: -->
    <input on-keyup-enter="doThis" on-keyup-esc="doThat">
  4. Normal attribute bindings. Currently these are done via putting {{ }} inside attribute values. This often leads to people thinking in a string-template fashion and get confused about where {{ }} are allowed and where not. The proposal is to prefix dynamic attribute bindings with the bind- prefix:

    <!-- plain string -->
    <img src="/avatars/123.png">
    
    <!-- bind to expression -->
    <img bind-src="'/avatars/' + userId + '.png'">
    
    <!-- style/class are enhanced to accept object/array values -->
    <div bind-class="classes"></div>
    <div bind-class="[classA, classB]"></div>
    <div bind-class="{ classA: true, classB: false }"></div>
    
    <div bind-style="cssString"></div>
    <div bind-style="{ fontSize: fontSize, color: currentColor }"></div>
    <div bind-style="[styleObjectA, styleObjectB]"></div>

    In addition, the bind- prefix can be shortened as a colon, which is totally optional:

    <img :src="'/avatars/' + userId + '.png'">
  5. Props. Similar to normal attribute bindings, non-prefixed props are always passed down as literal strings; To pass a dynamic prop, add bind- or colon prefix.

    Also, binding type indicators are now moved from the attribute value into the attribute name, right before the equal sign.

    <example
      literal="Mike"
      bind-dynamic="someThing"
      bind-onetime*="onlyOnce"
      bind-twoway@="syncsBackUp"
      :shorthand="sameAsBind">
    </example>
  6. Special attributes (including both directive params & literal directives). These will only appear together with either a directive or a component. You can also use bind- or colon prefixes for these, but only is, ref, el and transition will have reactive behavior, other directive params are evaluated only once (Vue will tell you in dev mode).

    <component
      :is="view"
      transition="slide"
      transition-mode="out-in">
    </component>
  7. Child component and element refs (previously v-ref and v-el) are no longer directives. They now have their own dedicated syntaxes. (see v-el and v-ref usage change after binding syntax update #1292)

The benefits:

  1. Explicit. The code says what it does.
  2. Simpler. Smaller set of core directives. No more "args" for directives; no more multiple clauses; It's just expressions + filters.
  3. No more confusion about mustache inside attributes. If you see a v-, on- or bind- prefix, it always means the value is an expression. If there's no prefix, then it's a always a plain string!
  4. Eliminates edge cases like src="{{abc}}" causing 404 requests and style="{{something}}" gets thrown away in IE. Also eliminates the need for v-attr.

Note

Please don't dislike this proposal just for the sake of "why so many changes"; all the changes proposed here can be implemented in a backwards compatible way in 1.0.0-alpha (with deprecation warnings) and migration should not be unacceptably painful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions