Vue 3.x $attrs 用法的改变

发布于 2024-09-27 02:55:24 字数 7420 浏览 5 评论 0

英文官方文档

Summary

  • Make the attrs fallthrough behavior more consistent;
  • Make it easier to pass all extraneous attrs to child elements / components.

Motivation

In Vue 2.x, components have an implicit attrs fallthrough behavior. Any prop passed to a component, but is not declared as a prop by the component, is considered an "extraneous attribute". In 2.x, these extraneous attributes are exposed in this.$attrs and implicitly applied to the component's root node. This behavior can be disabled with inheritAttrs: false , where the user expects to explicit control where the attrs should be applied.

There are a number of inconsistencies and issues in the 2.x behavior:

  • inheritAttrs: false does not affect class and style .
  • class , style , v-on listeners and custom directives are not included in $attrs , making it cumbersome for a higher-order component (HOC) to properly pass everything down to a nested child component.
  • Functional components have no implicit attrs fallthrough at all.

In 3.x, the need for "spreading extraneous attrs" also becomes more prominent due to the ability for components to render multiple root nodes (fragments). This RFC seeks to address these problems.

Detailed design

this.$attrs now contains everything passed to the component except those that are declared as props. This includes class , style , v-on listeners (as onXXX props), and custom directives (as onVnodeXXX props) . (This is based on flat props structure as proposed in Render Function API Change ). As a result of this:

  • .native modifier for v-on will be removed.
  • this.$listeners will be removed.

When the component returns a single root node, this.$attrs will be implicitly merged into the root node's props. This is the same as 2.x, except it will now include all the props that were not previously in this.$attrs , as discussed above.

If the component receives extraneous attrs, but returns multiple root nodes (a fragment), an automatic merge cannot be performed. If the user did not perform an explicit spread (checked by access to this.$attrs during render), a runtime warning will be emitted. The component should either pick an element to apply the attrs to (via v-bind="$attrs" ), or explicitly suppress the warning with inheritAttrs: false .

inheritAttrs: false

With inheritAttrs: false , the component can either choose to intentionally ignore all extraneous attrs, or explicitly control where the attrs should be applied via v-bind="$attrs" :

<div class="wrapper">
  <!-- apply attrs to an inner element instead of root -->
  <input v-bind="$attrs">
</div>

In 2.x, this option does not affect class and style - they will be implicitly merged on root in all cases for stateful components - but in 3.0 this special case is removed: class and style will be part of $attrs just like everything else.

Merging Attrs in Render Functions(mergeProps)

In manual render functions, it may seem convenient to just use a spread:

export default {
  props: { /* ... */ },
  inheritAttrs: false,
  render() {
    return h('div', { class: 'foo', ...this.$attrs })
  }
}

However, this will cause attrs to overwrite whatever existing props of the same name. For example, there the local class may be overwritten when we probably want to merge the classes instead. Vue provides a mergeProps helper that handles the merging of class , style and onXXX listeners:

import { mergeProps } from 'vue'
export default {
  props: { /* ... */ },
  inheritAttrs: false,
  render() {
    return h('div', mergeProps({ class: 'foo' }, this.$attrs))
  }
}

This is also what v-bind uses internally.

Consistency between Functional and Stateful Components

Functional components will now share the exact same behavior with Stateful components. The extraneous attrs is passed via the second context argument (as specified in Render Function API Change ):

const Func = (props, { attrs }) => {
  return h('div', mergeProps({ id: 'x' }, attrs), props.msg)
}
Func.props = { /* ... */ }

Components with no Props Declaration

Note that for components without props declaration (see Optional Props Declaration ), there will be no implicit attrs handling of any kind, because everything passed in is considered a prop and there will be no "extraneous" attrs. A component without props declaration (mostly functional components) is responsible for explicitly passing down necessary props. This can be easily done with object rest spread:

const Func = ({ msg, ...rest }) => {
  return h('div', mergeProps({ id: 'x' }, rest), [
    h('span', msg)
  ])
}

Unresolved questions

Removing Unwanted Listeners

With flat VNode data and the removal of .native modifier, all listeners are passed down to the child component as onXXX functions:

<foo @click="foo" @custom="bar" />

compiles to:

h(foo, {
  onClick: foo,
  onCustom: bar
})

When spreading $attrs with v-bind , all parent listeners are applied to the target element as native DOM listeners. The problem is that these same listeners can also be triggered by custom events - in the above example, both a native click event and a custom one emitted by this.$emit('click') in the child will trigger the parent's foo handler. This may lead to unwanted behavior.

Props do not suffer from this problem because declared props are removed from $attrs . Therefore we should have a similar way to "declare" emitted events from a component. There is currently an open RFC for it by @niko278.

Event listeners for explicitly declared events will be removed from $attrs and can only be triggered by custom events emitted by the component via this.$emit .

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

白龙吟

暂无简介

0 文章
0 评论
611 人气
更多

推荐作者

謌踐踏愛綪

文章 0 评论 0

开始看清了

文章 0 评论 0

高速公鹿

文章 0 评论 0

alipaysp_PLnULTzf66

文章 0 评论 0

热情消退

文章 0 评论 0

白色月光

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文