days since this article was written, please be aware of its timeliness
Preface
This article begins with the “Instance” section from the Vue 2.0 Official Documentation], exploring some usage methods of the Vue API and the principles behind how Vue implements certain functionalities. Additionally, it includes personal usage experiences and, from my limited perspective, an analysis of why Vue is designed this way—though this may seem presumptuous. As I am relatively new to Vue, any inaccuracies are kindly requested to be pointed out, and I appreciate your understanding in advance.
Note: Basic knowledge is skipped directly; I will only mention points I deem necessary.
Vue Instance
Every Vue.js application is launched by creating a root Vue instance via a constructor. This means that all data on a page should be maintained solely by this single instance, with the original data sources being emitted and received exclusively by the root instance for unified management. The root instance then distributes data via props or listens for data through events. Child components only need to watch/computed data changes and update promptly.
The documentation states: All Vue.js components are essentially extended Vue instances. The correct interpretation of this is that you can use the same methods and lifecycle hooks on components as you would on instances, except for data.
In components, data must be a function because components are reusable, so each invocation of a component must generate its own data.
Data proxy refers to the fact that a Vue instance proxies all properties in its data object, while the instance property $data represents the data attribute itself, distinguishing it from the proxied data.
In other words, if a vm’s data property is {a: 'xheldon'}, then vm.a equals 'xheldon', while vm.$data is {a: 'xheldon'}.
A component is essentially an (extended) Vue instance. Here’s a simple verification:
A list.vue component (template and style omitted):
1 | |
props vs data
When initializing a component, properties on props, data, and methods in computed are all bound to the Vue instance. However, properties on props have higher priority than those with the same name on data. Here’s the verification:
1 | |
The result shows a warning (not an error, rendering is unaffected):
1 | |

Meanwhile, the function names returned by computed can duplicate those of properties on data without any warnings. However, once duplicated, during initialization, the console shows that _data sets the getter and setter for the duplicated property after _computedWatcher (whether this is the reason remains to be confirmed after further research, so I’ll leave it here for now and update this article later). This leads to the property being overwritten. For related principles, see this introduction].
They are all bound to the instance properties during initialization. Duplicate computed properties are overwritten, but Vue devtool still displays them correctly.)
1 | |

Vue devtool displays them correctly:

If the method names in computed do not duplicate the property names in data:
1 | |

Additionally, the difference between methods and computed methods, aside from the latter having caching while the former does not (i.e., the latter won’t recalculate unless its dependent reactive data changes), is that if both are intended to return interpolated values, methods in methods are invoked as functionName(), whereas methods in computed are referenced as functionName—meaning the former requires executing the function, while the latter does not.
The reason for this is that the methods we write in computed properties are treated as properties mounted on the Vue instance, and the functions we provide serve as the getter for these properties.
In contrast, methods are simply functions, so whether they are referenced in interpolations or as event handlers, they must be called using ().
Thus, when an actual function is needed—here, if a function call is required, computed must return a function, not just a value, because parameters need to be passed—methods take precedence.
Below is an alternative version of a todo list different from the official documentation:
Template:
1 | |
Logic:
1 | |
In this example, if this template is used, a parameter must be passed to delete the current li when the button is clicked. Therefore, methods must be used in this case.
When using computed, parameters are not accepted (because it is a getter), even if it returns a function:
1 | |
Note: If there are functions with the same name in both, the function in computed takes precedence over the one in methods (due to the order in which getter and setter are processed; refer to the source code for details). This applies to both interpolations and event bindings. Here’s the verification:
Interpolation reference test:
Template:
1 | |
1 | |
The output function is im from computed. If computed does not return a function, the interpolation reference using only {{a}} will obviously output the value from computed (verification omitted here).
For event binding:
When calling using inline handler methods:
1 | |
Logic:
1 | |
Outputs computed.
If using method event handlers, the result is the same:
1 | |
Logic:
1 | |
Outputs computed.
Note that for event binding, computed must always return a function.
Thus, the order of precedence is:
props > data > computed > methods
I suspect the reference priority for this.xxx in computed and methods follows the same order. Those curious can verify this.
Moreover, it can be observed that in both cases, methods do not need to return a function, whereas computed must return a function and does not accept parameters (since it is a getter). In summary, event handling is best done with methods, while data binding/interpolation is best handled with computed (due to caching).
Additionally, when binding events with methods, using () or omitting it has the same effect—both will execute the function. The differences are as follows:
-
Statements with () are called inline statements, which fall into two cases: native events and custom events. Both cases can pass parameters. If the parameter list is empty, the default parameterargumentsis also empty, meaning there is no default parameter. If triggered by a native event likeinput/click, a special$eventparameter can be passed as the nativeeventhandler. If triggered by a custom event, the parameters of the event handler still depend on the values actually passed to it, and there is no special$eventobject available for custom functions. Parameters passed when triggering a custom event via$emitwill be ignored. -
Statements without () are called method events, which also fall into two cases: for native events, the nativeeventobject is passed as the sole default parameter; for custom events, the parameters passed are any number of arguments from the second to the last (excluding the event name) when the event is emitted via$emit.
Talk is cheap, show me the code.
The above describes four scenarios:
- Native event with ()
1 | |
- Custom event with ()
1 | |
- Native event without ()
1 | |
- Custom event without ()
1 | |
Directives and Parameters (Attributes)
Basic usage:
1 | |
Here, directive is called the directive, and propName is called the “parameter” of the directive. In practice, the parameter manifests as an attribute in html (Vue includes some built-in parameters/attributes, such as click in v-bind:click="method" or href in v-bind:href="/img/in-post/x.png". The former does not appear inline, while the latter, being required, appears as an inline attribute. Custom-defined parameters/attributes will always appear as inline attributes). The value is a variable (though enclosed in quotes) and in most cases comes from the parent template.
propName can include a modifier for quick operations like preventing default events (e.g., .prevent).
Some directives can be used directly, such as v-if, while others require parameters: v-bind:href or v-on:click.
Note that whether value is enclosed in quotes or not yields the same result—i.e., :propName="value" and :propName=value are equivalent. Unless otherwise specified, this applies to all cases below.
If value evaluates to false when converted to a boolean, propName is removed; if true, propName appears. Specifically, it follows these rules (only for custom attributes):
- For literals
null,undefined, orfalse, thepropNameattribute is removed. - If
valueis an undefined variable, such as:propName="wxd",propNameis removed, and a warning is issued:
1 | |
-
If
valueis an array (since arrays are objects),propNamewill always exist except in the second case below. Thevaluebehaves as follows::propName =[],:propName ="[]",:propName ="['']":valueis removed, leaving only the attribute with no value.:propName =[""]: The structure breaks.:propName =["",""]or:propName ='["",""]': Thevaluebecomes",".
-
If
valueis a nested array, after flattening its values, ifvalueor its recursive child elements contain a literalundefinedornull(not written as a string), the value at that position is left empty; ifvalueor its recursive child elements contain anObject, the value at that position becomes[object Object]. -
If
valueis an object,propNameis preserved, and the value becomes[object Object]. -
If
valueis a number or string, such as:propName = "'fff'",propNameis preserved, andvaluetakes the string or numeric value.
After reviewing the source code, this is indeed the logic:
1 | |
These rules only apply to custom properties. For built-in properties, the behavior differs. For example, when binding the class attribute:
1 | |
This means if the value of isActive is false or any other value that can be converted to a boolean false, the active class name will not be applied. Otherwise, it will be applied (consistent with the truthiness evaluation of an if statement).
Filters
When filters are chained, the first parameter of the first filter is the initial value, while the first parameter of subsequent filters is the return value of the previous filter (or undefined if there is no return value).
Template:
1 | |
Logic:
1 | |
Except for the first filter, subsequent filters cannot access the initial value. Of course, if you want to pass parameters, there are plenty of workarounds, such as having the first filter return an array, etc.
List Rendering
In v-for, if two parameters are provided, they align with the parameters of the native js forEach method: value, key. Using the of operator has the exact same effect as using the in operator—even though this isn’t the case in native js.
Another point to note is that when using v-for with components, the parent cannot automatically pass data to the component because the component has its own isolated scope. Therefore, to pass data to child components, you need to use the props property, which is slightly more verbose:
1 | |
Additionally, when v-for is used on an object, the iteration yields the object’s values, not its keys. This differs from native js, where a for in loop requires manually accessing obj['i'] to output values, and a second parameter (value, key) is needed to output keys:
1 | |
Note that in native js, unless you manually implement a Symbol.iterator, you cannot use a for of loop. However, Vue allows it—though the effect is identical to for in.
There’s also a small tip in list rendering called the “in-place reuse” principle. What does this mean? Take the tololist example mentioned earlier. If no unique key is assigned to each element, like this:
1 | |
Then every time you click the “×” to delete the current li, Vue will reuse the existing elements in place, simply moving the data to the correct position instead of remove-ing the deleted dom element to avoid reflow. Below is the page render behavior when clicking “×” to delete an element, as shown by Chrome’s devtool:

You can see that the reflow portion is only the very bottom part.
When key is added:
1 | |
Let’s look at the browser’s render behavior after clicking the close button:

Some might wonder: Why do we need to manually implement a value.key on value here instead of using the key provided by Vue in (value, key)?:
1 | |
The answer is that while the key provided by Vue appears to be a key, it remains unrelated to the current data. Thus, when deleting an li, the key is merely recalculated without being removed or shifted along with the deleted or affected elements. If the above approach is used, the effect is the same as the first method without a key—it still employs the in-place reuse strategy, where the data changes while the dom structure remains unchanged. Therefore, you need to manually implement a key, roughly like this:
1 | |
Event Handlers
Event handlers can be chained, but some elements inherently don’t support certain events, making binding meaningless—for example, binding a keyup event on a div:
1 | |
Thus, it’s common to handle events via bubbling on a div and then bind an alt+ctrl event on an input:
1 | |
I have a concern here: If an input uses @keyup.space for listening, but in Chinese input methods, the spacebar is typically used to select words, will the actual output be the unselected pinyin letters or the first candidate word after pressing the spacebar? (This might not be a Vue issue, but I’ll mention it here.)
The answer is that in most cases, the result is the first candidate word after pressing the spacebar. However, if a sentence is very long and requires pressing the spacebar twice to output the entire phrase, the first press will output nothing (blank), and the second press will output the full phrase. I tested this edge case, so it’s safe to assume the output will be the first candidate word after pressing the spacebar, not blank or pinyin letters. I used Sogou mac input method’s single-line candidate mode for testing (details omitted).
Note: The official documentation mentions IME when discussing v-model, referring to this issue. If you want v-model to respond immediately during IME composition, you can bind the input event.
Form Controls
v-model is typically used on input elements, and the value attribute in the template will be ignored—v-model only recognizes the initial value in js and binds to it. So if you write both v-model and a value attribute, the latter will appear in the dom structure but will be ignored when js retrieves the value, as v-model takes precedence:
1 | |
1 | |
The difference between the two is similar to that between .data and .attr in jQuery—what’s written inline is the value of attr('data','xxx'), and inspecting the dom structure also shows the value xxx. However, the actual value obtained via js is the one bound through .data('yyy')—unless you use attr to read the dom structure. (Note: If you modify the attr value after instantiation, js will retrieve the attr value. The initial value is ignored here—only the initial value. For example, after initialization, if v-model binds to value and you manually modify the value in the dom structure, v-model will then use the modified attr value instead of the value on data.)
If the requirement is unconventional—say, you don’t want to use v-model to get and update the input value in real-time, or you need to process the input value before updating it—you can try $ref (e.target.value also works):
Template:
1 | |
Logic:
1 | |
The official documentation also states that v-model is just syntactic sugar for two-way data binding:
1 | |
However, listening for the input event has a drawback: when using an input method, the event triggers even before pressing space to select a word. So, unless this behavior is needed, it’s better to stick with v-model.
For binding multiple elements to the same value and outputting them—a common requirement for a group of checkboxes—you need to use an array:
1 | |
1 | |
(As far as I’ve discovered,) this concise array usage only works for multiple checkbox-type inputs—i.e., several elements bound to the same v-model, where their states aren’t synchronized but their corresponding values are collected into an array. Of course, if you insist on using methods to achieve similar effects for arbitrary input types—well, never mind.
For a single checkbox, v-model binds to the value, which is either true or false. You can customize the selected and unselected values using :true-value and :false-value.
For multiple radio buttons, v-model serves a role similar to name—i.e., grouping them. Thus, radio-type inputs using v-model don’t need the name attribute.
For select types, if no value attribute is given for each option, the bound value is the content of the option; otherwise, it’s the value attribute. For multi-select selects, the data bound to v-model must be an array; otherwise, a warning is issued (though it won’t error—Vue auto-converts it, and it still runs):
1 | |
Note: For all the above types, when v-model binds to value and the value attribute is dynamically bound (:value="xxx") to another property (xxx) on data, the properties corresponding to v-model and :value are the same (strictly equal).
Components
First, we need to distinguish between what is a DOM template and what is a string template.
An HTML template refers to ordinary html elements that are bound through the el option of a Vue instance:
String template section:
1 | |
A string template refers to:
- Templates registered via
templateinjs, such as:
1 | |
Or:
1 | |
-
Templates registered via
<script type="text/x-tempalge"></script>(similar toHandlebar). -
The content inside the
<template>tag in.vuecomponents.
Because Vue parses DOM templates only after the browser has finished parsing, DOM templates cannot use components in certain tags that require specific child elements. For example, the child elements of a select tag must be option, so a custom tag like com-option won’t be recognized. To address this, you can add an is="component-name" attribute to indicate the template name used by the tag.
Literal Syntax vs. Dynamic Syntax
Here’s an important note: In native js, object properties can be numbers, though they are treated as strings (limited to ES5; in ES6, object properties can be any value, but this doesn’t conflict with what follows). However, in Vue, the data property cannot use numbers as keys. If literal syntax is used, passing a number will first be converted to a string via toString. If dynamic syntax is used, it will be treated directly as a number and won’t look for the corresponding property value bound in data:
Child component template:
1 | |
Child component logic:
1 | |
Parent component—literal syntax:
1 | |
Parent component—dynamic syntax:
1 | |
In both cases, the parent component logic is:
1 | |
The result is: When using the parent component’s literal syntax, clicking the button passes 1 to the child component, and as the documentation states, it’s the string "1", so alert shows string. When using the parent component’s dynamic syntax with v-bind bound to the 1 property in the parent’s data, the child component doesn’t receive the value corresponding to the 1 property (i.e., "属性-动态语法"), but rather the number 1 itself. Thus, clicking the button triggers an alert showing number.
Conclusion: It’s best not to use numbers as properties in data objects.
Note: If the property passed to the child component is an array or object, modifying this property in the child component will reflect in the parent component—this is usually undesirable. As the saying goes: props down, events up (example omitted). The best practice is to use a deep copy of reference types passed from the parent—unless you explicitly need the child component to affect the parent’s state, in which case, good luck.
When events up, if the child component passes additional parameters besides the event name in $emit, these parameters will be passed to the parent component’s event listener function:
Child component template:
1 | |
Child component logic:
1 | |
Parent template:
1 | |
Parent logic:
1 | |
Asynchronous Update Queue
With Vue, jQuery is no longer necessary. The greatest advantage of frameworks is avoiding repetitive code for the same operations. Two-way data binding can solve many DOM manipulation issues, but in some cases, jQuery still has its advantages. For example, when manipulating the DOM, jQuery accepts a function as a callback, which triggers after the animation completes. With our two-way data binding, after setting the data, how do we know the DOM has been updated? The answer is the same as with jQuery: the asynchronous update queue.
1 | |
I personally don’t recommend writing it this way because I believe the best logic should be placed within the instance’s properties/methods rather than outside it. Fortunately, Vue provides a way to achieve this:
1 | |
Animation
There’s not much to say about animations, but two hooks in the JavaScript hook functions need mentioning: enterCanceled and leaveCancelled. enterCancelled is used with v-if and v-show and can be triggered in both cases. The timing of its trigger is after the enter event is fired but before the animation completes, when another animation needs to be executed. On the other hand, leaveCancelled is only used with v-show and is ineffective with v-if—it will never be triggered. Its trigger timing is when the leave animation (i.e., the xxx-leave-active animation) is interrupted by another animation before completion.
Test code:
1 | |
Logic:
1 | |
Styles:
1 | |
Apart from the enter and leave hooks, which take the el element itself (a native Element type) and the done callback function as parameters, all other hooks only take the el element as their parameter.
For element transitions, it’s best to add a key to each element. Due to the in-place reuse strategy mentioned earlier, data might be replaced directly during transitions without any animation effects.
One point not mentioned at all in the official documentation is that the order of CSS class names for Vue transition animations is restricted. v-enter and v-leave must be written after v-enter-active and v-leave-active; otherwise, they won’t work. For example, if I want to create a fade-in/fade-out effect for a button click—where clicking the button triggers a new button fading in from left to right while the currently clicked button fades out from left to right—the logic would be:
1 | |
Structure:
1 | |
If your styles are written like this:
1 | |
You’ll find the animation effect unsatisfactory:

But if you place enter after enter-active:
1 | |
It works perfectly:

After comparing all possible orders of these four CSS class names, I found that as long as v-enter is placed after v-enter-active, the effect works. The order of the other class names doesn’t matter.
The transition tag should only contain the elements to be animated and no other elements. If the structure in the example above is written like this:
1 | |
No animation effect will occur. Additionally, if some CSS properties defining the element’s style are set in normal CSS class names, and the same properties are used in animation class names like v-enter, they won’t take effect. The documentation claims “their priority is higher than normal class names,” but this isn’t actually the case (or perhaps I misunderstood—corrections are welcome). Using the same example, if you set the normal CSS properties for button in the styles:
1 | |
The result is:

As you can see, only opacity produces an animation, while transform does not! (Students can test whether the animation effect still occurs when using animate.css by pre-setting elements with the same properties as in Animate.css. Feel free to submit an issue if you encounter any problems.)
transition-group is slightly different from transition. Visually, transition itself is just a wrapper container and does not participate in the page structure, whereas transition-group is replaced by Vue with a tag—defaulting to a span tag, though the tag name can be customized.
The render Function
The render function can replace the role of writing templates. Its parameter, createElement, is typically abbreviated as h. Various bindings/attributes within components or tags can all be expressed using corresponding JavaScript syntax in createElement. If something isn’t found, it means you can use native methods, such as .stop or .prevent, which can be directly replaced with event.stopPropagation() and event.preventDefault().
Other Topics
Mixin refers to modifying or adding extra functionality during the normal component writing process (i.e., within the component’s lifecycle).
Some plugins are built based on the above mixin concept. Beyond that, plugins may also add methods to Vue.prototype or introduce global methods or properties via config.
Routing can be simply implemented using the is attribute of a component, or by writing a render function to render different templates based on different paths. For more complex scenarios, third-party libraries are needed.
State Management: The example suggests adding a wrap to record each state change process. The official best practice recommends modifying state through functions rather than directly assigning values to instance properties, as this makes state changes traceable.
Unit Testing is standard unit testing—nothing special to mention.
Server-Side Rendering (Server Side Render)
The basic idea is straightforward: First, exports a Vue instance in app.js. Then, create a page template file index.html (which includes Vue.js and the $mount method for mounting the Vue instance—whether app.js also needs to be included, as shown in the official example, requires further verification). The template should contain a mount point (a non-empty element with an id attribute). In the server-side server.js, import these files and use vue-server-renderer to render the Vue instance exported from app.js. Finally, replace the mount point (a non-empty element with an id attribute, since the template from app.js already exists) when returning the content to the client.
The result of server-side rendering is that the mount point (a non-empty element with an id attribute) will have a server-rendered="true" attribute added (visible by right-clicking to view the page source, confirming it is not dynamically added by js).
The server also supports streaming rendering. First, the html needs to be split at the mount point (a non-empty element with an id attribute, such as <div id="app"></div>), dividing it into two parts, a and b. Previously, vue-server-renderer used renderToString for app.js, but to support streaming rendering, we need to switch to a method called renderToStream. Then, listen for the data event and append it after part a of the html. After the end event, concatenate part b of the html, and finally send it all out via res.send.
Postscript
Located in the capital, ping cn.vuejs.org:

ping my company’s FQ VPS:

dig it:

You can see it uses cloudflare services. International websites are tough to maintain, huh.
-
Previous
Three Ways to Implement Routing in Vue -
Next
Several Notes on Domain Optimization Configuration for This Blog
I often wish that when facing some key decisions in life, someone could tell me the best course of action so that I would not waste my precious time. Putting myself in others' shoes, I therefore write blogs often, hoping to record in this tiny corner of the vast Internet the once-in-a-lifetime experiences that matter to me, and to help those who seek help.