|
| 1 | +- Start Date: 2019-04-09 |
| 2 | +- Target Major Version: 3.x |
| 3 | +- Reference Issues: N/A |
| 4 | +- Implementation PR: N/A |
| 5 | + |
| 6 | +# Summary |
| 7 | + |
| 8 | +- Re-design custom directive API so that it better aligns with component lifecycle |
| 9 | + |
| 10 | +- Custom directives usage on components will follow the same rules as discussed in the [Attribute Fallthrough Behavior RFC](https://github.com/vuejs/rfcs/pull/26). It will be controlled by the child component via `v-bind="$attrs"`. |
| 11 | + |
| 12 | +# Basic example |
| 13 | + |
| 14 | +## Before |
| 15 | + |
| 16 | +``` js |
| 17 | +const MyDirective = { |
| 18 | + bind(el, binding, vnode, prevVnode) {}, |
| 19 | + inserted() {}, |
| 20 | + update() {}, |
| 21 | + componentUpdated() {}, |
| 22 | + unbind() {} |
| 23 | +} |
| 24 | +``` |
| 25 | + |
| 26 | +## After |
| 27 | + |
| 28 | +``` js |
| 29 | +const MyDirective = { |
| 30 | + beforeMount(el, binding, vnode, prevVnode) {}, |
| 31 | + mounted() {}, |
| 32 | + beforeUpdate() {}, |
| 33 | + updated() {}, |
| 34 | + beforeUnmount() {}, // new |
| 35 | + unmounted() {} |
| 36 | +} |
| 37 | +``` |
| 38 | + |
| 39 | +# Motivation |
| 40 | + |
| 41 | +Make custom directive hook names more consistent with the component lifecycle. |
| 42 | + |
| 43 | +# Detailed design |
| 44 | + |
| 45 | +## Hooks Renaming |
| 46 | + |
| 47 | +Existing hooks are renamed to map better to the component lifecycle, with some timing adjustments. Arguments passed to the hooks remain unchanged. |
| 48 | + |
| 49 | +- `bind` -> `beforeMount` |
| 50 | +- `inserted` -> `mounted` |
| 51 | +- `beforeUpdate` *new, called before the element itself is updated* |
| 52 | +- ~~`update`~~ *removed, use `updated` instead* |
| 53 | +- `componentUpdated` -> `updated` |
| 54 | +- `beforeUnmount` *new* |
| 55 | +- `unbind` -> `unmounted` |
| 56 | + |
| 57 | +## Usage on Components |
| 58 | + |
| 59 | +In 3.0, with fragments support, components can potentially have more than one root nodes. This creates an issue when a custom directive is used on a component with multiple root nodes. |
| 60 | + |
| 61 | +To explain the details of how custom directives will work on components in 3.0, we need to first understand how custom directives are compiled in 3.0. For a directive like this: |
| 62 | + |
| 63 | +``` html |
| 64 | +<div v-foo="bar"></div> |
| 65 | +``` |
| 66 | + |
| 67 | +Will roughly compile into this: |
| 68 | + |
| 69 | +``` js |
| 70 | +const vFoo = resolveDirective('foo') |
| 71 | + |
| 72 | +return withDirectives(h('div'), [ |
| 73 | + [vFoo, bar] |
| 74 | +]) |
| 75 | +``` |
| 76 | + |
| 77 | +Where `vFoo` will be the directive object written by the user, which contains hooks like `mounted` and `updated`. |
| 78 | + |
| 79 | +`withDirectives` returns a cloned VNode with the user hooks wrapped and injected as vnode lifecycle hooks (see [Render Function API Changes](https://github.com/vuejs/rfcs/blob/render-fn-api-change/active-rfcs/0000-render-function-api-change.md#special-reserved-props) for more details): |
| 80 | + |
| 81 | +``` js |
| 82 | +{ |
| 83 | + onVnodeMounted(vnode) { |
| 84 | + // call vFoo.mounted(...) |
| 85 | + } |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +**As a result, custom directives are fully included as part of a VNode's data. When a custom directive is used on a component, these `onVnodeXXX` hooks are passed down to the component as extraneous props and end up in `this.$attrs`.** |
| 90 | + |
| 91 | +This also means it's possible to directly hook into an element's lifecycle like this in the template, which can be handy when a custom directive is too involved: |
| 92 | + |
| 93 | +``` html |
| 94 | +<div @vnodeMounted="myHook" /> |
| 95 | +``` |
| 96 | + |
| 97 | +This is consistent with the attribute fallthrough behavior discussed in [vuejs/rfcs#26](https://github.com/vuejs/rfcs/pull/26). So, the rule for custom directives on a component will be the same as other extraneous attributes: it is up to the child component to decide where and whether to apply it. When the child component uses `v-bind="$attrs"` on an inner element, it will apply any custom directives used on it as well. |
| 98 | + |
| 99 | +# Drawbacks |
| 100 | + |
| 101 | +N/A |
| 102 | + |
| 103 | +# Alternatives |
| 104 | + |
| 105 | +N/A |
| 106 | + |
| 107 | +# Adoption strategy |
| 108 | + |
| 109 | +- The renaming should be easy to support in the compat build |
| 110 | +- Codemod should also be straightforward |
| 111 | +- For directives used on components, the warning on unused `$attrs` as discussed in [Attribute Fallthrough Behavior](https://github.com/vuejs/rfcs/pull/26) should apply as well. |
| 112 | + |
| 113 | +# Unresolved questions |
| 114 | + |
| 115 | +N/A |
0 commit comments