Vue <component> 笔记
1. 正常用法
<component :is="tag" value="hello" />
tag = 'input' 时渲染成:
<input value="hello">
<component> 是 Vue 的内置特殊元素,编译期会变成:
_resolveDynamicComponent(is)
is 的具体值运行时才解析。
2. 坑一:is 藏进 v-bind 不生效
错误写法
<component v-bind="{ is: 'input', value: 'hello' }" />
现象
控制台报错:
[Vue warn]: Failed to resolve component: component
DOM 里出现死标签:
<component is="input" value="hello"></component>
原因
is 必须显式写在 <component> 标签上。
藏进 v-bind 对象后,编译器不会把它当动态组件的 is,而是当普通 attribute。 <component> 拿不到 is,就被当普通组件名解析,找不到就回退成未知元素。
正确写法
<component :is="'input'" v-bind="{ value: 'hello' }" />
静态也行:
<component is="input" v-bind="{ value: 'hello' }" />
编译产物对比
显式写 is:
_createBlock(_resolveDynamicComponent('input'), ...)
藏进 v-bind:
const _component_component = _resolveComponent("component")
_createVNode(_component_component, _normalizeProps(_guardReactiveProps({
is: 'input',
value: 'hello'
})), null, 16)
编译器完全没识别出动态组件,而是把 <component> 当普通组件名去解析。
3. 坑二:v-model 在 <component> 上,当 is 是原生元素时失效
例子
<component :is="'input'" v-model="username" />
能打字,但 username 不变。
为什么失效
v-model 是编译期语法糖。编译器看到它,要决定展开成什么,但它只看标签名。
看到标签是 <component>,编译器不知道运行时 is 会解析成原生元素还是组件,只能默认按“组件”展开:
_createBlock(_resolveDynamicComponent('input'), {
modelValue: username,
"onUpdate:modelValue": $event => (username = $event)
}, null, 8, ["modelValue"])
这是组件那套约定:传 modelValue,监听 update:modelValue。
运行时 is 解析成原生 input。原生 input 不认识 modelValue 这个 prop,也永远不会触发 update:modelValue 事件。结果就是:
- 能输入 —— 那是浏览器自己的行为
username不同步 —— 组件那套约定,原生元素接不住
对比:为什么 <input v-model> 就正常
直接写 <input v-model="username" />,编译器知道这是原生元素,会按原生那套展开,内部直接操作 DOM 的 value 和 input 事件。所以正常。
关键区别
| 写法 | 编译后 | 走哪条路 |
|---|---|---|
<component v-model> | 展开成 modelValue + update:modelValue | 组件路径 |
<input v-model> | 展开成原生 v-model 指令 | 原生元素路径 |
编译器只看标签名,不看 is 的值。
那想用 v-model 怎么办
三条路,按场景选:
① 让 is 指向真组件
真组件认 modelValue 那套,v-model 就通了:
<component :is="RealComponent" v-model="x" />
② 不用 v-model,手动接
<component
:is="'input'"
:value="x"
@input="e => x = e.target.value"
/>
取值按元素类型:input / select 用 e.target.value,checkbox 用 e.target.checked。
③ 干脆不用 <component>,直接 v-if / v-else
<input v-if="type === 'input'" v-model="x" />
<textarea v-else v-model="x" />
如果只是 input / textarea 二选一,这是最稳的。
4. 一句话总结
is必须显式写在<component>标签上,不能藏进v-bind对象。v-model认标签,不认is。标签是<component>,它就按组件路径展开;运行时若是原生元素,接不住。- 动态原生标签 +
v-model天生走不通,要么让is指向真组件,要么手动绑定,要么直接v-if/v-else。