You’re browsing the documentation for v2.x and earlier. For v3.x, click here.
Migration from Vue 1.x
FAQ
Woah - this is a super long page! Does that mean 2.0 is completely different, I’ll have to learn the basics all over again, and migrating will be practically impossible?
I’m glad you asked! The answer is no. About 90% of the API is the same and the core concepts haven’t changed. It’s long because we like to offer very detailed explanations and include a lot of examples. Rest assured, this is not something you have to read from top to bottom!
Where should I start in a migration?
-
Start by running the migration helper on a current project. We’ve carefully minified and compressed a senior Vue dev into a simple command line interface. Whenever they recognize an obsolete feature, they’ll let you know, offer suggestions, and provide links to more info.
-
After that, browse through the table of contents for this page in the sidebar. If you see a topic you may be affected by, but the migration helper didn’t catch, check it out.
-
If you have any tests, run them and see what still fails. If you don’t have tests, just open the app in your browser and keep an eye out for warnings or errors as you navigate around.
-
By now, your app should be fully migrated. If you’re still hungry for more though, you can read the rest of this page - or dive in to the new and improved guide from the beginning. Many parts will be skimmable, since you’re already familiar with the core concepts.
How long will it take to migrate a Vue 1.x app to 2.0?
It depends on a few factors:
-
The size of your app (small to medium-sized apps will probably be less than a day)
-
How many times you get distracted and start playing with a cool new feature. ???? Not judging, it also happened to us while building 2.0!
-
Which obsolete features you’re using. Most can be upgraded with find-and-replace, but others might take a few minutes. If you’re not currently following best practices, Vue 2.0 will also try harder to force you to. This is a good thing in the long run, but could also mean a significant (though possibly overdue) refactor.
If I upgrade to Vue 2, will I also have to upgrade Vuex and Vue Router?
Only Vue Router 2 is compatible with Vue 2, so yes, you’ll have to follow the migration path for Vue Router as well. Fortunately, most applications don’t have a lot of router code, so this likely won’t take more than an hour.
As for Vuex, even version 0.8 is compatible with Vue 2, so you’re not forced to upgrade. The only reason you may want to upgrade immediately is to take advantage of the new features in Vuex 2, such as modules and reduced boilerplate.
Templates
Fragment Instances removed
Every component must have exactly one root element. Fragment instances are no longer allowed. If you have a template like this:
<p>foo</p> <p>bar</p>
It’s recommended to wrap the entire contents in a new element, like this:
<div> <p>foo</p> <p>bar</p> </div>
Upgrade Path
Run your end-to-end test suite or app after upgrading and look for console warnings about multiple root elements in a template.
Lifecycle Hooks
beforeCompile
removed
Use the created
hook instead.
Upgrade Path
Run the migration helper on your codebase to find all examples of this hook.
compiled
replaced
Use the new mounted
hook instead.
Upgrade Path
Run the migration helper on your codebase to find all examples of this hook.
attached
removed
Use a custom in-DOM check in other hooks. For example, to replace:
attached: function () { doSomething() }
You could use:
mounted: function () { this.$nextTick(function () { doSomething() }) }
Upgrade Path
Run the migration helper on your codebase to find all examples of this hook.
detached
removed
Use a custom in-DOM check in other hooks. For example, to replace:
detached: function () { doSomething() }
You could use:
destroyed: function () { this.$nextTick(function () { doSomething() }) }
Upgrade Path
Run the migration helper on your codebase to find all examples of this hook.
init
renamed
Use the new beforeCreate
hook instead, which is essentially the same thing. It was renamed for consistency with other lifecycle methods.
Upgrade Path
Run the migration helper on your codebase to find all examples of this hook.
ready
replaced
Use the new mounted
hook instead. It should be noted though that with mounted
, there’s no guarantee to be in-document. For that, also include Vue.nextTick
/vm.$nextTick
. For example:
mounted: function () { this.$nextTick(function () { // code that assumes this.$el is in-document }) }
Upgrade Path
Run the migration helper on your codebase to find all examples of this hook.
v-for
v-for
Argument Order for Arrays changed
When including an index
, the argument order for arrays used to be (index, value)
. It is now (value, index)
to be more consistent with JavaScript’s native array methods such as forEach
and map
.
Upgrade Path
Run the migration helper on your codebase to find examples of the obsolete argument order. Note that if you name your index arguments something unusual like position
or num
, the helper will not flag them.
v-for
Argument Order for Objects changed
When including a property name/key, the argument order for objects used to be (name, value)
. It is now (value, name)
to be more consistent with common object iterators such as lodash’s.
Upgrade Path
Run the migration helper on your codebase to find examples of the obsolete argument order. Note that if you name your key arguments something like name
or property
, the helper will not flag them.
$index
and $key
removed
The implicitly assigned $index
and $key
variables have been removed in favor of explicitly defining them in v-for
. This makes the code easier to read for developers less experienced with Vue and also results in much clearer behavior when dealing with nested loops.
Upgrade Path
Run the migration helper on your codebase to find examples of these removed variables. If you miss any, you should also see console errors such as: Uncaught ReferenceError: $index is not defined
track-by
replaced
track-by
has been replaced with key
, which works like any other attribute: without the v-bind:
or :
prefix, it is treated as a literal string. In most cases, you’d want to use a dynamic binding which expects a full expression instead of a key. For example, in place of:
<div v-for="item in items" track-by="id">
You would now write:
<div v-for="item in items" v-bind:key="item.id">
Upgrade Path
Run the migration helper on your codebase to find examples of track-by
.
v-for
Range Values changed
Previously, v-for="number in 10"
would have number
starting at 0 and ending at 9. Now it starts at 1 and ends at 10.
Upgrade Path
Search your codebase for the regex /\w+ in \d+/
. Wherever it appears in a v-for
, check to see if you may be affected.
Props
coerce
Prop Option removed
If you want to coerce a prop, setup a local computed value based on it instead. For example, instead of:
props: { username: { type: String, coerce: function (value) { return value .toLowerCase() .replace(/\s+/, '-') } } }
You could write:
props: { username: String, }, computed: { normalizedUsername: function () { return this.username .toLowerCase() .replace(/\s+/, '-') } }
There are a few advantages:
- You still have access to the original value of the prop.
- You are forced to be more explicit, by giving your coerced value a name that differentiates it from the value passed in the prop.
Upgrade Path
Run the migration helper on your codebase to find examples of the coerce
option.
twoWay
Prop Option removed
Props are now always one-way down. To produce side effects in the parent scope, a component needs to explicitly emit an event instead of relying on implicit binding. For more information, see:
- Custom component events
- Custom input components (using component events)
- Global state management
Upgrade Path
Run the migration helper on your codebase to find examples of the twoWay
option.
.once
and .sync
Modifiers on v-bind
removed
Props are now always one-way down. To produce side effects in the parent scope, a component needs to explicitly emit an event instead of relying on implicit binding. For more information, see:
- Custom component events
- Custom input components (using component events)
- Global state management
Upgrade Path
Run the migration helper on your codebase to find examples of the .once
and .sync
modifiers.
Prop Mutation deprecated
Mutating a prop locally is now considered an anti-pattern, e.g. declaring a prop and then setting this.myProp = 'someOtherValue'
in the component. Due to the new rendering mechanism, whenever the parent component re-renders, the child component’s local changes will be overwritten.
Most use cases of mutating a prop can be replaced by one of these options:
- a data property, with the prop used to set its default value
- a computed property
Upgrade Path
Run your end-to-end test suite or app after upgrading and look for console warnings about prop mutations.
Props on a Root Instance replaced
On root Vue instances (i.e. instances created with new Vue({ ... })
), you must use propsData
instead of props
.
Upgrade Path
Run your end-to-end test suite, if you have one. The failed tests should alert to you to the fact that props passed to root instances are no longer working.
Computed properties
cache: false
deprecated
Caching invalidation of computed properties will be removed in future major versions of Vue. Replace any uncached computed properties with methods, which will have the same result.
For example:
template: '<p>message: {{ timeMessage }}</p>', computed: { timeMessage: { cache: false, get: function () { return Date.now() + this.message } } }
Or with component methods:
template: '<p>message: {{ getTimeMessage() }}</p>', methods: { getTimeMessage: function () { return Date.now() + this.message } }
Upgrade Path
Run the migration helper on your codebase to find examples of the cache: false
option.
Built-In Directives
Truthiness/Falsiness with v-bind
changed
When used with v-bind
, the only falsy values are now: null
, undefined
, and false
. This means 0
and empty strings will render as truthy. So for example, v-bind:draggable="''"
will render as draggable="true"
.
For enumerated attributes, in addition to the falsy values above, the string "false"
will also render as attr="false"
.
Note that for other directives (e.g. v-if
and v-show
), JavaScript’s normal truthiness still applies.
Upgrade Path
Run your end-to-end test suite, if you have one. The failed tests should alert to you to any parts of your app that may be affected by this change.
Listening for Native Events on Components with v-on
changed
When used on a component, v-on
now only listens to custom events $emit
ted by that component. To listen for a native DOM event on the root element, you can use the .native
modifier. For example:
<my-component v-on:click.native="doSomething"></my-component>
Upgrade Path
Run your end-to-end test suite, if you have one. The failed tests should alert to you to any parts of your app that may be affected by this change.
debounce
Param Attribute for v-model
removed
Debouncing is used to limit how often we execute Ajax requests and other expensive operations. Vue’s debounce
attribute parameter for v-model
made this easy for very simple cases, but it actually debounced state updates rather than the expensive operations themselves. It’s a subtle difference, but it comes with limitations as an application grows.
These limitations become apparent when designing a search indicator, like this one for example:
Using the debounce
attribute, there’d be no way to detect the “Typing” state, because we lose access to the input’s real-time state. By decoupling the debounce function from Vue however, we’re able to debounce only the operation we want to limit, removing the limits on features we can develop:
<!-- By using the debounce function from lodash or another dedicated utility library, we know the specific debounce implementation we use will be best-in-class - and we can use it ANYWHERE. Not only in our template. --> <script src="https://cdn.jsdelivr.net/lodash/4.13.1/lodash.js"></script> <div id="debounce-search-demo"> <input v-model="searchQuery" placeholder="Type something"> <strong>{{ searchIndicator }}</strong> </div>
new Vue({ el: '#debounce-search-demo', data: { searchQuery: '', searchQueryIsDirty: false, isCalculating: false }, computed: { searchIndicator: function () { if (this.isCalculating) { return '⟳ Fetching new results' } else if (this.searchQueryIsDirty) { return '... Typing' } else { return '✓ Done' } } }, watch: { searchQuery: function () { this.searchQueryIsDirty = true this.expensiveOperation() } }, methods: { // This is where the debounce actually belongs. expensiveOperation: _.debounce(function () { this.isCalculating = true setTimeout(function () { this.isCalculating = false this.searchQueryIsDirty = false }.bind(this), 1000) }, 500) } })
Another advantage of this approach is there will be times when debouncing isn’t quite the right wrapper function. For example, when hitting an API for search suggestions, waiting to offer suggestions until after the user has stopped typing for a period of time isn’t an ideal experience. What you probably want instead is a throttling function. Now since you’re already using a utility library like lodash, refactoring to use its throttle
function instead takes only a few seconds.
Upgrade Path
Run the migration helper on your codebase to find examples of the debounce
attribute.
lazy
or number
Param Attributes for v-model
replaced
The lazy
and number
param attributes are now modifiers, to make it more clear what That means instead of:
<input v-model="name" lazy> <input v-model="age" type="number" number>
You would use:
<input v-model.lazy="name"> <input v-model.number="age" type="number">
Upgrade Path
Run the migration helper on your codebase to find examples of the these param attributes.
value
Attribute with v-model
removed
v-model
no longer cares about the initial value of an inline value
attribute. For predictability, it will instead always treat the Vue instance data as the source of truth.
That means this element:
<input v-model="text" value="foo">
backed by this data:
data: { text: 'bar' }
will render with a value of “bar” instead of “foo”. The same goes for a <textarea>
with existing content. Instead of:
<textarea v-model="text"> hello world </textarea>
You should ensure your initial value for text
is “hello world”.
Upgrade Path
Run your end-to-end test suite or app after upgrading and look for console warnings about inline value attributes with v-model
.
v-model
with v-for
Iterated Primitive Values removed
Cases like this no longer work:
<input v-for="str in strings" v-model="str">
The reason is this is the equivalent JavaScript that the <input>
would compile to:
strings.map(function (str) { return createElement('input', ...) })
As you can see, v-model
‘s two-way binding doesn’t make sense here. Setting str
to another value in the iterator function will do nothing because it’s only a local variable in the function scope.
Instead, you should use an array of objects so that v-model
can update the field on the object. For example:
<input v-for="obj in objects" v-model="obj.str">
Upgrade Path
Run your test suite, if you have one. The failed tests should alert to you to any parts of your app that may be affected by this change.
v-bind:style
with Object Syntax and !important
removed
This will no longer work:
<p v-bind:style="{ color: myColor + ' !important' }">hello</p>
If you really need to override another !important
, you must use the string syntax:
<p v-bind:style="'color: ' + myColor + ' !important'">hello</p>
Upgrade Path
Run the migration helper on your codebase to find examples of style bindings with !important
in objects.
v-el
and v-ref
replaced
For simplicity, v-el
and v-ref
have been merged into the ref
attribute, accessible on a component instance via $refs
. That means v-el:my-element
would become ref="myElement"
and v-ref:my-component
would become ref="myComponent"
. When used on a normal element, the ref
will be the DOM element, and when used on a component, the ref
will be the component instance.
Since v-ref
is no longer a directive, but a special attribute, it can also be dynamically defined. This is especially useful in combination with v-for
. For example:
<p v-for="item in items" v-bind:ref="'item' + item.id"></p>
Previously, v-el
/v-ref
combined with v-for
would produce an array of elements/components, because there was no way to give each item a unique name. You can still achieve this behavior by giving each item the same ref
:
<p v-for="item in items" ref="items"></p>
Unlike in 1.x, these $refs
are not reactive, because they’re registered/updated during the render process itself. Making them reactive would require duplicate renders for every change.
On the other hand, $refs
are designed primarily for programmatic access in JavaScript - it is not recommended to rely on them in templates, because that would mean referring to state that does not belong to the instance itself. This would violate Vue’s data-driven view model.
Upgrade Path
Run the migration helper on your codebase to find examples of v-el
and v-ref
.
v-else
with v-show
removed
v-else
no longer works with v-show
. Use v-if
with a negation expression instead. For example, instead of:
<p v-if="foo">Foo</p> <p v-else v-show="bar">Not foo, but bar</p>
You can use:
<p v-if="foo">Foo</p> <p v-if="!foo && bar">Not foo, but bar</p>
Upgrade Path
Run the migration helper on your codebase to find examples of the v-else
with v-show
.
Custom Directives simplified
Directives have a greatly reduced scope of responsibility: they are now only used for applying low-level direct DOM manipulations. In most cases, you should prefer using components as the main code-reuse abstraction.
Some of the most notable differences include:
- Directives no longer have instances. This means there’s no more
this
inside directive hooks. Instead, they receive everything they might need as arguments. If you really must persist state across hooks, you can do so onel
. - Options such as
acceptStatement
,deep
,priority
, etc have all been removed. To replacetwoWay
directives, see this example. - Some of the current hooks have different behavior and there are also a couple new hooks.
Fortunately, since the new directives are much simpler, you can master them more easily. Read the new Custom Directives guide to learn more.
Upgrade Path
Run the migration helper on your codebase to find examples of defined directives. The helper will flag all of them, as it's likely in most cases that you'll want to refactor to a component.
Directive .literal
Modifier removed
The .literal
modifier has been removed, as the same can be easily achieved by providing a string literal as the value.
For example, you can update:
<p v-my-directive.literal="foo bar baz"></p>
to:
<p v-my-directive="'foo bar baz'"></p>
Upgrade Path
Run the migration helper on your codebase to find examples of the `.literal` modifier on a directive.
Transitions
transition
Attribute replaced
Vue’s transition system has changed quite drastically and now uses <transition>
and <transition-group>
wrapper elements, rather than the transition
attribute. It’s recommended to read the new Transitions guide to learn more.
Upgrade Path
Run the migration helper on your codebase to find examples of the transition
attribute.
Vue.transition
for Reusable Transitions replaced
With the new transition system, you can now use components for reusable transitions.
Upgrade Path
Run the migration helper on your codebase to find examples of Vue.transition
.
Transition stagger
Attribute removed
If you need to stagger list transitions, you can control timing by setting and accessing a data-index
(or similar attribute) on an element. See an example here.
Upgrade Path
Run the migration helper on your codebase to find examples of the transition
attribute. During your update, you can transition (pun very much intended) to the new staggering strategy as well.
Events
events
option removed
The events
option has been removed. Event handlers should now be registered in the created
hook instead. Check out the $dispatch
and $broadcast
migration guide for a detailed example.
Vue.directive('on').keyCodes
replaced
The new, more concise way to configure keyCodes
is through Vue.config.keyCodes
. For example:
// enable v-on:keyup.f1 Vue.config.keyCodes.f1 = 112
Upgrade Path
Run the migration helper on your codebase to find examples of the the old keyCode
configuration syntax.
$dispatch
and $broadcast
replaced
$dispatch
and $broadcast
have been removed in favor of more explicitly cross-component communication and more maintainable state management solutions, such as Vuex.
The problem is event flows that depend on a component’s tree structure can be hard to reason about and are very brittle when the tree becomes large. They don’t scale well and only set you up for pain later. $dispatch
and $broadcast
also do not solve communication between sibling components.
One of the most common uses for these methods is to communicate between a parent and its direct children. In these cases, you can actually listen to an $emit
from a child with v-on
. This allows you to keep the convenience of events with added explicitness.
However, when communicating between distant descendants/ancestors, $emit
won’t help you. Instead, the simplest possible upgrade would be to use a centralized event hub. This has the added benefit of allowing you to communicate between components no matter where they are in the component tree - even between siblings! Because Vue instances implement an event emitter interface, you can actually use an empty Vue instance for this purpose.
For example, let’s say we have a todo app structured like this:
Todos ├─ NewTodoInput └─ Todo └─ DeleteTodoButton
We could manage communication between components with this single event hub:
// This is the event hub we'll use in every // component to communicate between them. var eventHub = new Vue()
Then in our components, we can use $emit
, $on
, $off
to emit events, listen for events, and clean up event listeners, respectively:
// NewTodoInput // ... methods: { addTodo: function () { eventHub.$emit('add-todo', { text: this.newTodoText }) this.newTodoText = '' } }
// DeleteTodoButton // ... methods: { deleteTodo: function (id) { eventHub.$emit('delete-todo', id) } }
// Todos // ... created: function () { eventHub.$on('add-todo', this.addTodo) eventHub.$on('delete-todo', this.deleteTodo) }, // It's good to clean up event listeners before // a component is destroyed. beforeDestroy: function () { eventHub.$off('add-todo', this.addTodo) eventHub.$off('delete-todo', this.deleteTodo) }, methods: { addTodo: function (newTodo) { this.todos.push(newTodo) }, deleteTodo: function (todoId) { this.todos = this.todos.filter(function (todo) { return todo.id !== todoId }) } }
This pattern can serve as a replacement for $dispatch
and $broadcast
in simple scenarios, but for more complex cases, it’s recommended to use a dedicated state management layer such as Vuex.
Upgrade Path
Run the migration helper on your codebase to find examples of $dispatch
and $broadcast
.
Filters
Filters Outside Text Interpolations removed
Filters can now only be used inside text interpolations ({{ }}
tags). In the past we’ve found using filters within directives such as v-model
, v-on
, etc led to more complexity than convenience. For list filtering on v-for
, it’s also better to move that logic into JavaScript as computed properties, so that it can be reused throughout your component.
In general, whenever something can be achieved in plain JavaScript, we want to avoid introducing a special syntax like filters to take care of the same concern. Here’s how you can replace Vue’s built-in directive filters:
Replacing the debounce
Filter
Instead of:
<input v-on:keyup="doStuff | debounce 500">
methods: { doStuff: function () { // ... } }
Use lodash’s debounce
(or possibly throttle
) to directly limit calling the expensive method. You can achieve the same as above like this:
<input v-on:keyup="doStuff">
methods: { doStuff: _.debounce(function () { // ... }, 500) }
For more on the advantages of this strategy, see the example here with v-model
.
Replacing the limitBy
Filter
Instead of:
<p v-for="item in items | limitBy 10">{{ item }}</p>
Use JavaScript’s built-in .slice
method in a computed property:
<p v-for="item in filteredItems">{{ item }}</p>
computed: { filteredItems: function () { return this.items.slice(0, 10) } }
Replacing the filterBy
Filter
Instead of:
<p v-for="user in users | filterBy searchQuery in 'name'">{{ user.name }}</p>
Use JavaScript’s built-in .filter
method in a computed property:
<p v-for="user in filteredUsers">{{ user.name }}</p>
computed: { filteredUsers: function () { var self = this return self.users.filter(function (user) { return user.name.indexOf(self.searchQuery) !== -1 }) } }
JavaScript’s native .filter
can also manage much more complex filtering operations, because you have access to the full power of JavaScript within computed properties. For example, if you wanted to find all active users and case-insensitively match against both their name and email:
var self = this self.users.filter(function (user) { var searchRegex = new RegExp(self.searchQuery, 'i') return user.isActive && ( searchRegex.test(user.name) || searchRegex.test(user.email) ) })
Replacing the orderBy
Filter
Instead of:
<p v-for="user in users | orderBy 'name'">{{ user.name }}</p>
Use lodash’s orderBy
(or possibly sortBy
) in a computed property:
<p v-for="user in orderedUsers">{{ user.name }}</p>
computed: { orderedUsers: function () { return _.orderBy(this.users, 'name') } }
You can even order by multiple columns:
_.orderBy(this.users, ['name', 'last_login'], ['asc', 'desc'])
Upgrade Path
Run the migration helper on your codebase to find examples of filters being used inside directives. If you miss any, you should also see console errors.
Filter Argument Syntax changed
Filters’ syntax for arguments now better aligns with JavaScript function invocation. So instead of taking space-delimited arguments:
<p>{{ date | formatDate 'YY-MM-DD' timeZone }}</p>
We surround the arguments with parentheses and delimit the arguments with commas:
<p>{{ date | formatDate('YY-MM-DD', timeZone) }}</p>
Upgrade Path
Run the migration helper on your codebase to find examples of the old filter syntax. If you miss any, you should also see console errors.
Built-In Text Filters removed
Although filters within text interpolations are still allowed, all of the filters have been removed. Instead, it’s recommended to use more specialized libraries for solving problems in each domain (e.g. date-fns
to format dates and accounting
for currencies).
For each of Vue’s built-in text filters, we go through how you can replace them below. The example code could exist in custom helper functions, methods, or computed properties.
Replacing the json
Filter
You actually don’t need to for debugging anymore, as Vue will nicely format output for you automatically, whether it’s a string, number, array, or plain object. If you want the exact same functionality as JavaScript’s JSON.stringify
though, then you can use that in a method or computed property.
Replacing the capitalize
Filter
text[0].toUpperCase() + text.slice(1)
Replacing the uppercase
Filter
text.toUpperCase()
Replacing the lowercase
Filter
text.toLowerCase()
Replacing the pluralize
Filter
The pluralize package on NPM serves this purpose nicely, but if you only want to pluralize a specific word or want to have special output for cases like 0
, then you can also easily define your own pluralize functions. For example:
function pluralizeKnife (count) { if (count === 0) { return 'no knives' } else if (count === 1) { return '1 knife' } else { return count + 'knives' } }
Replacing the currency
Filter
For a very naive implementation, you could do something like this:
'$' + price.toFixed(2)
In many cases though, you’ll still run into strange behavior (e.g. 0.035.toFixed(2)
rounds up to 0.04
, but 0.045
rounds down to 0.04
). To work around these issues, you can use the accounting
library to more reliably format currencies.
Upgrade Path
Run the migration helper on your codebase to find examples of the obsolete text filters. If you miss any, you should also see console errors.
Two-Way Filters replaced
Some users have enjoyed using two-way filters with v-model
to create interesting inputs with very little code. While seemingly simple however, two-way filters can also hide a great deal of complexity - and even encourage poor UX by delaying state updates. Instead, components wrapping an input are recommended as a more explicit and feature-rich way of creating custom inputs.
As an example, we’ll now walk the migration of a two-way currency filter:
It mostly works well, but the delayed state updates can cause strange behavior. For example, try entering 9.999
into one of those inputs. When the input loses focus, its value will update to $10.00
. When looking at the calculated total however, you’ll see that 9.999
is what’s stored in our data. The version of reality that the user sees is out of sync!
To start transitioning towards a more robust solution using Vue 2.0, let’s first wrap this filter in a new <currency-input>
component:
This allows us add behavior that a filter alone couldn’t encapsulate, such as selecting the content of an input on focus. Now the next step will be to extract the business logic from the filter. Below, we pull everything out into an external currencyValidator
object:
This increased modularity not only makes it easier to migrate to Vue 2, but also allows currency parsing and formatting to be:
- unit tested in isolation from your Vue code
- used by other parts of your application, such as to validate the payload to an API endpoint
Having this validator extracted out, we’ve also more comfortably built it up into a more robust solution. The state quirks have been eliminated and it’s actually impossible for users to enter anything wrong, similar to what the browser’s native number input tries to do.
We’re still limited however, by filters and by Vue 1.0 in general, so let’s complete the upgrade to Vue 2.0:
You may notice that:
- Every aspect of our input is more explicit, using lifecycle hooks and DOM events in place of the hidden behavior of two-way filters.
- We can now use
v-model
directly on our custom inputs, which is not only more consistent with normal inputs, but also means our component is Vuex-friendly. - Since we’re no longer using filter options that require a value to be returned, our currency work could actually be done asynchronously. That means if we had a lot of apps that had to work with currencies, we could easily refactor this logic into a shared microservice.
Upgrade Path
Run the migration helper on your codebase to find examples of filters used in directives like v-model
. If you miss any, you should also see console errors.
Slots
Duplicate Slots removed
It is no longer supported to have <slot>
s with the same name in the same template. When a slot is rendered it is “used up” and cannot be rendered elsewhere in the same render tree. If you must render the same content in multiple places, pass that content as a prop.
Upgrade Path
Run your end-to-end test suite or app after upgrading and look for console warnings about duplicate slots v-model
.
slot
Attribute Styling removed
Content inserted via named <slot>
no longer preserves the slot
attribute. Use a wrapper element to style them, or for advanced use cases, modify the inserted content programmatically using render functions.
Upgrade Path
Run the migration helper on your codebase to find CSS selectors targeting named slots (e.g. [slot="my-slot-name"]
).
Special Attributes
keep-alive
Attribute replaced
keep-alive
is no longer a special attribute, but rather a wrapper component, similar to <transition>
. For example:
<keep-alive> <component v-bind:is="view"></component> </keep-alive>
This makes it possible to use <keep-alive>
on multiple conditional children:
<keep-alive> <todo-list v-if="todos.length > 0"></todo-list> <no-todos-gif v-else></no-todos-gif> </keep-alive>
When <keep-alive>
has multiple children, they should eventually evaluate to a single child. Any child other than the first one will be ignored.
When used together with <transition>
, make sure to nest it inside:
<transition> <keep-alive> <component v-bind:is="view"></component> </keep-alive> </transition>
Upgrade Path
Run the migration helper on your codebase to find keep-alive
attributes.
Interpolation
Interpolation within Attributes removed
Interpolation within attributes is no longer valid. For example:
<button class="btn btn-{{ size }}"></button>
Should either be updated to use an inline expression:
<button v-bind:class="'btn btn-' + size"></button>
Or a data/computed property:
<button v-bind:class="buttonClasses"></button>
computed: { buttonClasses: function () { return 'btn btn-' + size } }
Upgrade Path
Run the migration helper on your codebase to find examples of interpolation used within attributes.
HTML Interpolation removed
HTML interpolations ({{{ foo }}}
) have been removed in favor of the v-html
directive.
Upgrade Path
Run the migration helper on your codebase to find HTML interpolations.
One-Time Bindings replaced
One time bindings ({{* foo }}
) have been replaced by the new v-once
directive.
Upgrade Path
Run the migration helper on your codebase to find one-time bindings.
Reactivity
vm.$watch
changed
Watchers created via vm.$watch
are now fired before the associated component rerenders. This gives you the chance to further update state before the component rerender, thus avoiding unnecessary updates. For example, you can watch a component prop and update the component’s own data when the prop changes.
If you were previously relying on vm.$watch
to do something with the DOM after a component updates, you can instead do so in the updated
lifecycle hook.
Upgrade Path
Run your end-to-end test suite, if you have one. The failed tests should alert to you to the fact that a watcher was relying on the old behavior.
vm.$set
changed
vm.$set
is now an alias for Vue.set
.
Upgrade Path
Run the migration helper on your codebase to find examples of the obsolete usage.
vm.$delete
changed
vm.$delete
is now an alias for Vue.delete
.
Upgrade Path
Run the migration helper on your codebase to find examples of the obsolete usage.
Array.prototype.$set
removed
Use Vue.set
instead.
Upgrade Path
Run the migration helper on your codebase to find examples of .$set
on an array. If you miss any, you should see console errors from the missing method.
Array.prototype.$remove
removed
Use Array.prototype.splice
instead. For example:
methods: { removeTodo: function (todo) { var index = this.todos.indexOf(todo) this.todos.splice(index, 1) } }
Or better yet, pass removal methods an index:
methods: { removeTodo: function (index) { this.todos.splice(index, 1) } }
Upgrade Path
Run the migration helper on your codebase to find examples of .$remove
on an array. If you miss any, you should see console errors from the missing method.
Vue.set
and Vue.delete
on Vue instances removed
Vue.set
and Vue.delete
can no longer work on Vue instances. It is now mandatory to properly declare all top-level reactive properties in the data option. If you’d like to delete properties on a Vue instance or its $data
, set it to null.
Upgrade Path
Run the migration helper on your codebase to find examples of Vue.set
or Vue.delete
on a Vue instance. If you miss any, they'll trigger console warnings.
Replacing vm.$data
removed
It is now prohibited to replace a component instance’s root $data. This prevents some edge cases in the reactivity system and makes the component state more predictable (especially with type-checking systems).
Upgrade Path
Run the migration helper on your codebase to find examples of overwriting vm.$data
. If you miss any, console warnings will be emitted.
vm.$get
removed
Instead, retrieve reactive data directly.
Upgrade Path
Run the migration helper on your codebase to find examples of vm.$get
. If you miss any, you'll see console errors.
DOM-Focused Instance Methods
vm.$appendTo
removed
Use the native DOM API:
myElement.appendChild(vm.$el)
Upgrade Path
Run the migration helper on your codebase to find examples of vm.$appendTo
. If you miss any, you'll see console errors.
vm.$before
removed
Use the native DOM API:
myElement.parentNode.insertBefore(vm.$el, myElement)
Upgrade Path
Run the migration helper on your codebase to find examples of vm.$before
. If you miss any, you'll see console errors.
vm.$after
removed
Use the native DOM API:
myElement.parentNode.insertBefore(vm.$el, myElement.nextSibling)
Or if myElement
is the last child:
myElement.parentNode.appendChild(vm.$el)
Upgrade Path
Run the migration helper on your codebase to find examples of vm.$after
. If you miss any, you'll see console errors.
vm.$remove
removed
Use the native DOM API:
vm.$el.remove()
Upgrade Path
Run the migration helper on your codebase to find examples of vm.$remove
. If you miss any, you'll see console errors.
Meta Instance Methods
vm.$eval
removed
No real use. If you do happen to rely on this feature somehow and aren’t sure how to work around it, post on the forum for ideas.
Upgrade Path
Run the migration helper on your codebase to find examples of vm.$eval
. If you miss any, you'll see console errors.
vm.$interpolate
removed
No real use. If you do happen to rely on this feature somehow and aren’t sure how to work around it, post on the forum for ideas.
Upgrade Path
Run the migration helper on your codebase to find examples of vm.$interpolate
. If you miss any, you'll see console errors.
vm.$log
removed
Use the Vue Devtools for the optimal debugging experience.
Upgrade Path
Run the migration helper on your codebase to find examples of vm.$log
. If you miss any, you'll see console errors.
Instance DOM Options
replace: false
removed
Components now always replace the element they’re bound to. To simulate the behavior of replace: false
, you can wrap your root component with an element similar to the one you’re replacing. For example:
new Vue({ el: '#app', template: '<div id="app"> ... </div>' })
Or with a render function:
new Vue({ el: '#app', render: function (h) { h('div', { attrs: { id: 'app', } }, /* ... */) } })
Upgrade Path
Run the migration helper on your codebase to find examples of replace: false
.
Global Config
Vue.config.debug
removed
No longer necessary, since warnings come with stack traces by default now.
Upgrade Path
Run the migration helper on your codebase to find examples of Vue.config.debug
.
Vue.config.async
removed
Async is now required for rendering performance.
Upgrade Path
Run the migration helper on your codebase to find examples of Vue.config.async
.
Vue.config.delimiters
replaced
This has been reworked as a component-level option. This allows you to use alternative delimiters within your app without breaking 3rd-party components.
Upgrade Path
Run the migration helper on your codebase to find examples of Vue.config.delimiters
.
Vue.config.unsafeDelimiters
removed
HTML interpolation has been removed in favor of v-html
.
Upgrade Path
Run the migration helper on your codebase to find examples of Vue.config.unsafeDelimiters
. After this, the helper will also find instances of HTML interpolation so that you can replace them with `v-html`.
Global API
Vue.extend
with el
removed
The el option can no longer be used in Vue.extend
. It’s only valid as an instance creation option.
Upgrade Path
Run your end-to-end test suite or app after upgrading and look for console warnings about the el
option with Vue.extend
.
Vue.elementDirective
removed
Use components instead.
Upgrade Path
Run the migration helper on your codebase to find examples of Vue.elementDirective
.
Vue.partial
removed
Partials have been removed in favor of more explicit data flow between components, using props. Unless you’re using a partial in a performance-critical area, the recommendation is to use a normal component instead. If you were dynamically binding the name
of a partial, you can use a dynamic component.
If you happen to be using partials in a performance-critical part of your app, then you should upgrade to functional components. They must be in a plain JS/JSX file (rather than in a .vue
file) and are stateless and instanceless, like partials. This makes rendering extremely fast.
A benefit of functional components over partials is that they can be much more dynamic, because they grant you access to the full power of JavaScript. There is a cost to this power however. If you’ve never used a component framework with render functions before, they may take a bit longer to learn.
Upgrade Path
Run the migration helper on your codebase to find examples of Vue.partial
.
© 2013–present Yuxi Evan You
Licensed under the MIT License.
https://vuejs.org/v2/guide/migration.html