在很多情况下,传递给 Vue 组件的属性不应添加到根元素,而应添加到子元素。 例如,在这个 UI 组件中,必须使用数量惊人的 props 来确保将属性添加到input
元素,而不是包装器div
。
此外,通常希望将表单元素上的所有事件侦听器公开给父元素,如果元素不是根元素,目前这也需要大量样板(在这种情况下, .native
修饰符可以解决问题)。
编辑:从这里开始赶上讨论。
当前默认情况下,“公开”元素(可以添加任意属性的元素)始终是根元素。 新指令可用于定义不同的暴露元素。 指令名称的一些想法:
v-expose
(可能是我个人最喜欢的)v-expose-attrs
(可能更清晰,但更长)v-main
v-primary
如果v-expose
被添加到一个元素,它将接受传递给它的组件的属性 - 这些属性将__不再__被传递给根元素。
其他可能不错的功能:
v-expose
可以接受字符串或字符串数组(例如v-expose="class"
或v-expose="['class', 'type', 'placeholder']"
)。 在这种情况下,这些属性将添加到元素(同样,而不是添加到根元素),但所有其他属性将添加到根元素或具有无值v-expose
的元素.嗯,我不知道,但对于像这样的组件,我认为你可以使用 JSX 或createElement
来传播道具。
https://github.com/doximity/vue-genome
对我们来说,这将是一个伟大的。 我们将所有输入包装在一个标签中,用于样式和用户体验。 我同意我们可以改为使用 jsx,但模板对于每个人来说都更容易遵循。
@Austio ,不幸的是,这就是模板的回报......等等......也许我们可以想出一种方法将spread
props 转换为 vue 模板?
我个人喜欢这个功能。 但这似乎打破了 v-bind 行为的一致性,就像有时我仍然需要为根元素绑定class
属性。
那么如何使用一对指令作为 getter 和 setter,例如:
在组件内部,定义一个v-expose
锚点:
<input v-expose="foo" />
使用时:
<the-component v-define:foo="{propA: '', propB: ''}"></the-component>
<!-- or maybe use v-bind for it directly -->
<the-component :foo="{propA: '', propB: ''}"></the-component>
@jkzing ,这看起来很棒,但同样,这看起来像一个基本的点差,并且存在潜在的问题,例如您如何定义@keyup.enter.prevent="myAction"
?
你不能只是<the-component :foo="{'@keyup.enter.prevent': myAction}"></the-component>
,这意味着你必须在运行时保留所有修饰符,如enter
和prevent
(这是 vue-template-compiler 的一部分提款机)
@nickmessing
看起来像一个基本的点差
我们正在谈论的事情是为模板用户带来诸如传播之类的东西
<the-component :foo="{'@keyup.enter.prevent': myAction}"></the-component>
@
是 v-on shortland,不代表prop
(v-bind)。
@jkzing ,在描述的链接中也有很多v-on
绑定
@nickmessing嗯...至于v-on
绑定,它是另一个主题 IMO,如事件冒泡。 🤔
@jkzing ,这是v-expose
afaik 的全部概念,使所有属性“转到”组件中的某个元素
@nickmessing ,无法确定最初的提案,但我认为不应将事件侦听器视为attribute
。
@jkzing ,可能不是,但考虑到<my-awesome-text-input />
的常见示例,其中您可以拥有 >9000 个不同的道具,您只希望它们全部都能到达您的自定义组件中的<input />
的代码。
我个人使用v-bind="$props"
或者您可以过滤掉这些以排除您不想应用的道具。 通过这种方式,您可以一次在输入上应用多个道具。 确实 v-expose 可能很有用,因为对于包装器组件,例如输入,您必须指定所有这些 html props
所以这
https://github.com/almino/semantic-ui-vue2/blob/master/src/elements/Input.vue#L9
cane 减少到v-bind="$props"
或v-bind="filteredProps"
,其中filteredProps 可能是一些计算属性
@cristijora我们也在使用v-bind="someProps"
。 这个解决方案的问题是过多的属性将被添加为 HTML 属性。 如果v-bind=
可以过滤掉组件不接受的所有属性,那就太好了。 使用动态<component>
我们不知道要在计算属性中过滤掉哪些道具。 虽然可以提取options.props
并使用lodash._pick
。
这对于指令真的可行吗?
@posva ,我不认为这本身就可以作为指令工作,但它可以是 vue 模板引擎的一部分,它可以在内部传播 + 一些事件传播
@posva我不认为是用户构建的指令,所以我可能使用了错误的语言。 我的意思只是一个“特殊属性”。
@chrisvfritz您对 API 的使用方式有什么想法吗(指定要公开的内容以及如何添加到子项)
我可以看到这与提供/注入概念的使用相似。
@Austio我可能不理解这个问题,但我在原始帖子中提供了一些关于 API 的想法。
嘿 Chris,意思是关于使用类似提供注入的其他想法,您可以在其中声明在父级中可公开的内容,然后在子级中使用它。
啊,我明白了。 我不确定是否需要这样做。 信息已经可以通过 props 和 slot 传递——甚至可以使用this.$parent
访问父级的私有属性,尽管我认为最好避免这种模式。
@Austio是否有您正在考虑的特定用例?
@chrisvfritz在渲染函数中如何工作?
我想也许更好的是:
$attributes
,例如(命名 tbd)$props
:v-bind="$attributes"
这将具有在 JSX/render 函数中几乎完全相同的额外好处
@LinusBorg我喜欢你的想法。 😄 你的方法更直观。
作为旁注,我认为有了这个 API,Vue 的下一个主要版本甚至可以完全删除属性自动继承,以便跨组件通信可以在双方保持明确。
可以贬值或消除这种行为,是的。
如果这值得对库等中的许多组件进行可能需要的更改,则要决定并应与社区讨论,尤其是 UI 集合作者。
A 关于 prob posed 特性:此信息已通过context.data.attributes
在功能组件中可用,因此此功能将为实例组件提供基本相同的功能。
对,就是这样。 我想到的主要目的是让 UI 组件作者(第 3 方和内部)的工作更简单。 目前有很多情况需要这样的事情:
<input
v-bind:id="id"
v-bind:accept="accept"
v-bind:alt="alt"
v-bind:autocomplete="autocomplete"
v-bind:autofocus="autofocus"
v-bind:checked="checked"
v-bind:dirname="dirname"
v-bind:disabled="disabled"
v-bind:form="form"
v-bind:formaction="formaction"
v-bind:formenctype="formenctype"
v-bind:formmethod="formmethod"
v-bind:formnovalidate="formnovalidate"
v-bind:formtarget="formtarget"
v-bind:list="list"
v-bind:max="max"
v-bind:maxlength="maxlength"
v-bind:min="min"
v-bind:multiple="multiple"
v-bind:name="name"
v-bind:pattern="pattern"
v-bind:placeholder="placeholder"
v-bind:readonly="readonly"
v-bind:required="required"
v-bind:src="src"
v-bind:step="step"
v-bind:type="type"
v-bind:value="value"
v-on:keydown="emitKeyDown"
v-on:keypress="emitKeyPress"
v-on:keyup="emitKeyUp"
v-on:mouseenter="emitMouseEnter"
v-on:mouseover="emitMouseOver"
v-on:mousemove="emitMouseMove"
v-on:mousedown="emitMouseDown"
v-on:mouseup="emitMouseUp"
v-on:click="emitClick"
v-on:dblclick="emitDoubleClick"
v-on:wheel="emitWheel"
v-on:mouseleave="emitMouseLeave"
v-on:mouseout="emitMouseOut"
v-on:pointerlockchange="emitPointerLockChange"
v-on:pointerlockerror="emitPointerLockError"
v-on:blur="emitBlur"
v-on:change="emitChange($event.target.value)"
v-on:contextmenu="emitContextMenu"
v-on:focus="emitFocus"
v-on:input="emitInput($event.target.value)"
v-on:invalid="emitInvalid"
v-on:reset="emitReset"
v-on:search="emitSearch"
v-on:select="emitSelect"
v-on:submit="emitSubmit"
>
新的$attributes
属性可以将其缩短为:
<input
v-bind="$attributes"
v-on:keydown="emitKeyDown"
v-on:keypress="emitKeyPress"
v-on:keyup="emitKeyUp"
v-on:mouseenter="emitMouseEnter"
v-on:mouseover="emitMouseOver"
v-on:mousemove="emitMouseMove"
v-on:mousedown="emitMouseDown"
v-on:mouseup="emitMouseUp"
v-on:click="emitClick"
v-on:dblclick="emitDoubleClick"
v-on:wheel="emitWheel"
v-on:mouseleave="emitMouseLeave"
v-on:mouseout="emitMouseOut"
v-on:pointerlockchange="emitPointerLockChange"
v-on:pointerlockerror="emitPointerLockError"
v-on:blur="emitBlur"
v-on:change="emitChange($event.target.value)"
v-on:contextmenu="emitContextMenu"
v-on:focus="emitFocus"
v-on:input="emitInput($event.target.value)"
v-on:invalid="emitInvalid"
v-on:reset="emitReset"
v-on:search="emitSearch"
v-on:select="emitSelect"
v-on:submit="emitSubmit"
>
尽管如此,我认为有某种方式也可以公开事件仍然很好。 也许一个空的v-on
指令可以将父级上的所有事件侦听器转发到这个元素?
<input
v-bind="$attributes"
v-on
>
或者,如果最终确实有我们想要捆绑的多个问题,我们可能会回到v-expose
:
<input v-expose>
这已经变成了关于如何简化 UI 组件构建的更广泛的讨论,而不是特定的功能请求,所以我将重新标记这个问题。 🙂
我迟到了这个话题,但我也有一些想法。
v-bind
当前解决方案和缺点首先,我已经使用并喜欢v-bind="propObject"
功能(如此强大)。 例如, bootstrap-vue有一个内部链接组件,可以在任何地方使用(按钮、导航、下拉列表等)。 组件枢轴成为本机锚点与基于路由器链接的href
与to
以及vm.$router
,因此有相当多的属性可以有条件地传递给每个这些组件中。
我们的解决方案是将这些 props 放在一个 mixin 中,并将v-bind="linkProps"
与一个计算对象一起使用。 这很好用,但仍然需要大量开销,将 mixin 添加到_所有其他使用链接组件的组件_。
v-expose
使用v-bind
可能性我个人喜欢v-expose
的概念,也许它可以像默认插槽 + 命名插槽一样工作,然后使用修饰符访问命名属性插槽。
默认属性 _"slot"_ 将始终将属性传递给组件本身(无更改),而其他命名目标可以由组件指定。 也许是这样的:
<template>
<my-component
<!-- Nothing new here -->
v-bind="rootProps"
<!-- This binds the `linkProps` object to the named attribute slot `link` -->
v-bind.link="linkProps"
/>
</template>
在MyComponent.vue
:
<template>
<div>
<router-link v-expose="link" />
</div>
</template>
除了.native
是一个非常强大的修饰符之外,我没有太多要添加的内容。 为我解决了很多问题。 尽管如此,Vue 开发人员似乎对它几乎一无所知(我看到大量 UI 库问题可以通过向开发人员公开此功能来解决)。 我在网站上放置了一个 PR,以在网站中添加更多文档和搜索支持,并可能针对谷歌搜索进行了优化。
来自另一个问题中关于 API 表面的争论,我必须重申,我不是v-expose
想法的粉丝。 它引入了另一种“工作方式”,并且不适用于 JSX 而不在那里实现一些特殊的东西,等等。
我尊重 React 人员的一件事是他们对精简 API 的承诺,并尽可能多地使用该语言的功能。 本着这种精神,重新使用我们已经拥有的用于属性的 props 的模式似乎比引入另一个抽象要好得多。
<my-input
type="file"
mode="dropdown"
>
<template>
<div>
<input v-bind="$attributes">
<dropdown v-bind="{ ...$props, $attributes.type }"/>
</div>
</template
啊,我现在明白你在说什么了。 我喜欢它! 这是目前可用的吗? vm.$attributes
会是加法吗?
重新阅读您的评论。 我正在追踪👍
是的, $attributes
将是加法。
此外,我们需要一个选项来关闭将属性应用于根元素的当前默认行为,如下所示:
```html
如果我们决定这样做会导致重大更改,那么这可能会成为 Vue 3.0 中的默认设置。
@LinusBorg你对处理事件方面的事情有什么想法? 为了遵循相同的策略,我想我们还可以添加一个$listeners
属性,它可能如下所示:
{
input: function () { /* ... */ },
focus: function () { /* ... */ },
// ...
}
那么也许v-on
可以接受一个对象,类似于v-bind
。 所以我们有:
<input v-bind="$attributes" v-on="$listeners">
我预见的一个问题是input
/ change
事件,因为v-model
对组件的工作方式与对元素的工作方式略有不同。 我也不知道我们是否想要$listeners
和$nativeListeners
。 我想如果$listeners
可用,那么.native
修饰符可能已过时。
此外,关于applyComponentAttrsToRoot
选项,也许exposeRootEl
会是一个好名字,当设置为false
,可以禁用自动应用的属性和.native
事件转发?
能够通过Vue.config
为整个应用程序以及单个组件禁用此功能也可能很好。
我最近对$listeners
有一个类似的想法 - 它也可以通过以下方式在功能组件上使用
context.data.listeners
所以我们最终会得到$props
、 $attributes
、 $listeners
,这对我来说听起来不错。
还有 #5578 要求v-on="{...}"
对象语法,就像我用于$attributes
,它很适合。
但我不确定 .native 修饰符。 为了使组件事件和本机侦听器都能工作,API 最终会复杂得多,而且使用是有问题的,因为应用于根元素的本机事件侦听器仍然会捕获所需的事件冒泡,所以它可能不会必须将其分配给模板中的特定元素。
一般来说,我会说对于低级组件库,当模板变得难以使用时,应该首选渲染函数。 但我同意以下内容是有价值的:
禁用“自动应用在 props 中找不到的绑定作为根元素的属性”的默认行为(相关问题:这是否也会影响class
和style
绑定?)
公开一种更简单的方法来将组件上的外部绑定“继承”到不一定是根的内部元素上。 理想情况下,模板和渲染函数之间具有一致性。
ia kie like vue,简单的工具
只想说v2.4中的PR非常好! 👍
来自发行说明
结合这些,我们可以将这样的组件简化为:
<div>
<input v-bind="$attrs" v-on="$listeners">
</div>
看起来不错,但事实并非如此,因为这些类型的组件旨在与 v-model 一起使用,据我所知,包装组件上的 v-model 无法正常工作。 例如,是否有任何示例说明如何将 v-model 从包装组件转发到输入?
我发现的最简单的方法:
https://jsfiddle.net/60xdxh0h/2/
也许功能组件与模板一起工作会更直接
这些类型的组件旨在与 v-model 一起使用,据我所知,包装组件上的 v-model 无法正常工作。
你为什么那么想? v-model 只是 prop 和事件侦听器的语法糖,两者都在 $attr/$props 中,因此可以轻松传递。
我想唯一需要了解子选项的事情是如果孩子更改了model
默认值,那是真的。
但是,根据情况,可以阅读这些内容。
当然它是一种语法糖,但我的意思是它可能会令人困惑
结合这些,我们可以将这样的组件简化为:
当实际基于示例https://github.com/almino/semantic-ui-vue2/blob/master/src/elements/Input.vue 时,您不能直接传递侦听器来实现相同的控制。 (例如:你必须使用 v-on:input="emitInput($event.target.value)" )
不管怎样,这个PR很有价值,干得好!
@AlexandreBonaventure这是因为v-model
在组件上的工作方式与在元素上的工作方式略有不同。 DOM 事件为回调提供一个事件对象,而组件事件直接提供一个值。 结果是v-model
_does_ 工作,但绑定值是 DOM 的事件对象。 😕
我认为您是对的, v-model
在这里工作是可取的,但我不确定解决它的最佳地点在哪里。 一些想法:
也许可以将不可枚举的属性添加到$listeners
(例如__$listeners__: true
,以帮助v-on
检测v-on="$listeners"
。然后在$listeners
对象被传递给v-on
,每个监听器都可以被包装:
function (event) {
listener(event.target.value)
}
一个缺点是现在我们正在丢弃数据。 例如,如果有人想访问keyCode
,他们就不走运了。 但是,如果v-on
的对象语法支持修饰符,我们可以通过使.native
禁用自动包装行为来解决此问题。
@yyx990803 @LinusBorg您对可行性有何看法? 我缺少任何边缘情况?
哦,我明白了,您指的是 rral 上的 v-model。 表单元素,我是在组件上考虑的。
不管有没有这个 PR,你都不能/不应该在道具上使用它。 在高级应用程序中,使用它是相当罕见的(尽管可以实现)。
@LinusBorg只是想确保我们在同一页面上。 给定带有此模板的CustomInput
组件:
<div>
<input v-bind="$attrs" v-on="$listeners">
<div>
您不希望下面的代码起作用吗?
<CustomInput v-model="myValue" />
我希望它能够工作——但我理解 alexandre 的方式是,他指的是元素上的 v-model,而不是组件——它最终只适用于变异的本地状态。
我试图说出@chrisvfritz在他的后
@LinusBorg在最新版本中这样做的问题在于它仍然被认为是一种反模式,并会触发关于改变状态的警告。
在 value 属性不是字符串的情况下,让上述工作非常有用。 以一个组合组件为例,我试图使用从我自己的库中导入的枚举作为选择选项的值:
<template>
<select class="combo" v-model="value" v-on="$listeners">
<option v-for="(item, key) in items" :value="item">{{key}}</option>
</select>
</template>
<script>
export default {
props: {
items: {
type: Object,
required: true
},
value: {}
}
}
</script>
这是我用于父项中的列表之一的示例:
execList: {
"None": ACT_EXEC_TYPES.NONE,
"Function": ACT_EXEC_TYPES.FUNCTION,
"Code": ACT_EXEC_TYPES.CODE
}
以及我如何使用组合组件:
<combo :items="execList" v-model="selectedAction.execType"/>
我一直在努力使这项工作正常进行 2 天,但仍然感到非常沮丧。 问题是 $event.target.value 始终是一个字符串,而不是像:value
那样进行评估。
@LinusBorg @AlexandreBonaventure @RobertBColton我刚刚打开了一个问题,我们可以在此集中讨论这个问题。
最有用的评论
@chrisvfritz在渲染函数中如何工作?
我想也许更好的是:
$attributes
,例如(命名 tbd)$props
:这将具有在 JSX/render 函数中几乎完全相同的额外好处