This is side-document providing details for some highlighted changes in 2.5.0. For a full list of changes, see the full release note.
- Error Handling with
errorCaptured
Hook - Functional Component Support in SFCs
- Environment Agnostic SSR
v-on
Automatic Key Modifiersv-on
.exact
Modifier- Simplified Scoped Slots Usage
- Inject with Default Values
- Internals change for nextTick
In 2.4 and earlier versions, we typical use the global config.errorHandler
option for handling unexpected errors in our applications. We also have the renderError
component option for handling errors in render functions. However, we lack a mechanism for handling generic errors inside a specific part of the application.
In 2.5 we introduce the new errorCaptured
hook. A component with this hook captures all errors (excluding those fired in async callbacks) from its child component tree (excluding itself). If you are familiar with React, this is similar to the concept of Error Boundaries introduced in React 16. The hook receives the same arguments as the global errorHandler
, and you can leverage this hook to gracefully handle and display the error.
You can modify component state in this hook. However, it is important to have conditionals in your template or render function that short circuits other content when an error has been captured; otherwise the component will be thrown into an infinite render loop.
Here is an example of a simple ErrorBoundary
component that can be used to "wrap" another component:
Vue.component('ErrorBoundary', {
data: () => ({ error: null }),
errorCaptured (err, vm, info) {
this.error = `${err.stack}\n\nfound in ${info} of component`
return false
},
render (h) {
if (this.error) {
return h('pre', { style: { color: 'red' }}, this.error)
}
// ignoring edge cases for the sake of demonstration
return this.$slots.default[0]
}
})
<error-boundary>
<another-component/>
</error-boundary>
-
By default, all errors are still sent to the global
errorHandler
if it is defined, so that these errors can still be reported to an analytics service in a single place. -
If multiple
errorCaptured
hooks exist on a component's inheritance chain or parent chain, all of them will be invoked on the same error. -
If the
errorCaptured
hook itself throws an error, both this error and the original captured error are sent to the globalerrorHandler
. -
An
errorCaptured
hook can returnfalse
to prevent the error from propagating further. This is essentially syaing "this error has been handled and should be ignored." It will prevent any additionalerrorCaptured
hooks or the globalerrorHandler
from being invoked for this error.
With vue-loader >= 13.3.0
, functional components defined as a Single-File Component in a *.vue
file now enjoys proper template compilation, Scoped CSS and hot-reloading support.
Functional templates are denoted with a functional
attribute on the <template>
block. Expressions in the template are evaluated in the functional render context. This means props need to be accessed as props.xxx
in the template:
<template functional>
<div>{{ props.msg }}</div>
</template>
The default build of vue-server-renderer
assumes a Node.js environment, which makes it unusable in alternative JavaScript environments such as php-v8js or Nashorn. In 2.5 we have shipped a build that is largely environment-agnostic, which makes it usable in the environments mentioned above.
For both environments, it is necessary to first prepare the environment by mocking the global
and process
objects, with process.env.VUE_ENV
set to "server"
, and process.env.NODE_ENV
set to "development"
or "production"
.
In Nashorn, it may also be necessary to provide a polyfill for Promise
or setTimeout
using Java's native timers.
Example usage in php-v8js:
<?php
$vue_source = file_get_contents('/path/to/vue.js');
$renderer_source = file_get_contents('/path/to/vue-server-renderer/basic.js');
$app_source = file_get_contents('/path/to/app.js');
$v8 = new V8Js();
$v8->executeString('var process = { env: { VUE_ENV: "server", NODE_ENV: "production" }}; this.global = { process: process };');
$v8->executeString($vue_source);
$v8->executeString($renderer_source);
$v8->executeString($app_source);
?>
// app.js
var vm = new Vue({
template: `<div>{{ msg }}</div>`,
data: {
msg: 'hello'
}
})
// exposed by vue-server-renderer/basic.js
renderVueComponentToString(vm, (err, res) => {
print(res)
})
Currently, for keys without a built-in alias, we either have to use the raw keyCode as the modifier (@keyup.13="foo"
), or register an alias in config.keyCodes
.
In 2.5, you can directly use any valid key names exposed via KeyboardEvent.key
as modifiers by converting them to kebab-case:
<input @keyup.page-down="onPageDown">
In the above example, the handler will only be called if $event.key === 'PageDown'
.
Existing key modifiers are still preserved. Note that a few keys (.esc
and all arrow keys) have inconsistent key values in IE9, their built-in aliases should be preferred if you need to support IE9.
The new .exact
modifier should be used in combination with other system modifiers (.ctrl
, .alt
, .shift
and .meta
) to indicate that the exact combination of modifiers must be pressed for the handler to fire.
<!-- this will fire even if Alt or Shift is also pressed -->
<button @click.ctrl="onClick">A</button>
<!-- this will only fire when only Ctrl is pressed -->
<button @click.ctrl.exact="onCtrlClick">A</button>
Previously we must use <template>
in combination with the scope
attribute to denote a scoped slot:
<comp>
<template scope="props">
<div>{{ props.msg }}</div>
</template>
</comp>
The original reasoning was to avoid the ambiguity in the use of the scope
attribute, because it can be confused for a prop if used on a component. However, the required <template>
results in an extra level of nesting, which makes the syntax a bit verbose.
In 2.5, the scope
attribute has been deprecated (it still works, but you will get a soft warning). Instead, we now use slot-scope
to denote a scoped slot, and it can be used on a normal element/component in addition to <template>
:
<comp>
<div slot-scope="props">
{{ props.msg }}
</div>
</comp>
Note that this change means that slot-scope
is now a reserved attribute and can no longer be used as a component prop.
Injections can now be optional and can declare default values:
export default {
inject: {
foo: { default: 'foo' }
}
}
If it needs to be injected from a property with a different name, use from
to denote the source property:
export default {
inject: {
foo: {
from: 'bar',
default: 'foo'
}
}
}
Similar to prop defaults, you need to use a factory function for non primitive values:
export default {
inject: {
foo: {
from: 'bar',
default: () => [1, 2, 3]
}
}
}
We have changed the implementation of Vue.nextTick
to fix a few bugs (related to #6566, #6690). The change involves using a macro task instead of a micro task to defer DOM updates when inside a DOM event handler attached via v-on
. This means any Vue updates triggered by state changes inside v-on
handlers will be now deferred using a macro task. This may lead to changes in behavior when dealing with native DOM events.
For more details regarding micro/macro tasks, see this blog post.
For the new implementation, see source code for nextTick.
nice