Skip to content

Instantly share code, notes, and snippets.

@yyx990803
Last active July 20, 2021 18:15
Show Gist options
  • Save yyx990803/9bdff05e5468a60ced06c29c39114c6b to your computer and use it in GitHub Desktop.
Save yyx990803/9bdff05e5468a60ced06c29c39114c6b to your computer and use it in GitHub Desktop.
Vue 2.5 release details

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

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>

Propagation Behavior

  • 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 global errorHandler.

  • An errorCaptured hook can return false to prevent the error from propagating further. This is essentially syaing "this error has been handled and should be ignored." It will prevent any additional errorCaptured hooks or the global errorHandler from being invoked for this error.

Functional Component Support in SFCs

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>

Environment Agnostic SSR

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)
})

v-on Automatic Key Modifiers

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.

v-on .exact Modifier

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>

Simplified Scoped Slots Usage

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.

Inject with Default Values

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]
    }
  }
}

Internals Change for nextTick

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.

@danpottshimself
Copy link

thank god! vue defo needed some more typescript support! good job ❤️

@ojaspar
Copy link

ojaspar commented Oct 24, 2017

nice work vue team

@cloudfroster
Copy link

love ErrorBoundary great !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment