Vue3里的is到底有几种用法?搞懂能省好多动态组件和自定义元素的坑
刚开始接触Vue3的动态组件或者Web Components时,很多人都会被“is”这个属性搞懵:一会儿写在组件标签上,一会儿写在template标签上,一会儿好像还不能随便写?甚至搜旧教程还能看到v-is这种写法,完全不知道该信谁,别急,今天咱们就把Vue3里is的所有用法、区别、适用场景全捋一遍,还会踩几个新手常犯的坑,保证看完你再也不会因为is报错挠头。
首先明确:Vue3里is的身份有三个,别搞混
很多新手觉得Vue3的is只有动态组件这一种功能,其实不然——它现在是个“多面手”,有完全独立的三种用法:内置组件里的动态占位符标识、自定义Web Components的挂载标识、以及原生标签绑定自定义元素的“补丁标识”,每种用法的写法规则、生效场景都不一样,必须分开记。
动态占位符的“灵魂钥匙”——专门给内置的Transition/TransitionGroup用
先从大家最熟悉但也最容易忽略细节的场景说起:Vue3里的动态组件,哦不对,动态组件本身可以不用is,但如果把动态组件放在Transition或者TransitionGroup这种Vue自带的内置组件里,is就成了“必需品”。
为什么Transition/TransitionGroup需要单独用is绑定template?
这个问题得从Vue2转Vue3的变化讲起,在Vue2里,我们用动态组件都是直接写<component :is="currentComp"></component>,不管有没有被包裹在Transition里,都能正常切换和触发过渡动画,但到了Vue3,编译器的机制变了:它会提前把<component>解析成一个抽象的“动态组件容器”,如果这个容器直接被TransitionGroup(注意是TransitionGroup,普通Transition还好一点点,但官方推荐统一写法)包裹,会因为Vue3对“可复用元素的根节点数量”有新要求而失效,或者动画效果出问题。
举个反例你就懂了:假设你现在要做一个标签页切换,三个Tab组件分别是TabA、TabB、TabC,用TransitionGroup包裹着,想切换的时候有个平滑的淡入淡出效果,如果还像Vue2那样写:
<transition-group name="fade"> <component :is="currentTab"></component> </transition-group>
浏览器控制台大概率会给你报一个警告:<transition-group> expects each child to have a unique "key" prop.哪怕你给<component>加了key也没用?不对加key能消警告,但切换的时候可能没有过渡动画,或者只有进入没有离开——这是因为TransitionGroup内部需要“追踪每个具体DOM元素的变化”,而抽象的<component>容器在Vue3里默认不会把内部组件的根节点直接暴露给TransitionGroup,导致追踪失效。
那该怎么写?这时候就轮到第一种身份的is出场了:在Transition/TransitionGroup下面直接加一个<template>标签,把is写在这个<template>上,同时别忘了加唯一的key,正确的写法是:
<transition-group name="fade"> <template :is="currentTab" :key="currentTab"></template> </transition-group>
对,就是这么简单!这时候的is不再是给<component>用的,而是告诉内置的过渡组件:“这个template不是普通的占位符,它要被换成currentTab对应的组件,你直接追踪这个即将被替换的组件的根节点就行啦”,而且这种写法在普通Transition里也能用,虽然普通Transition包裹<component :is>也能生效,但官方从Vue3.0 Beta版开始就建议统一用template加is的方式,避免未来版本的潜在兼容性问题。
新手踩坑点:template上的is必须是动态绑定吗?普通字符串行不行?
很多人第一次用这种写法时会问:如果我不想切换标签,只想让TransitionGroup包裹一个固定的组件,能不能直接写成<template is="TabA"></template>?答案是:不行,哦等下,准确说要看你有没有用单文件组件(SFC),如果你在HTML文件里直接用CDN引入Vue3,并且用app.component()注册了全局组件,那普通字符串是可以的;但如果你用的是现代前端开发常用的SFC(.vue文件),不管是全局注册还是局部注册,template上的is都必须用v-bind(也就是冒号)动态绑定,哪怕绑定的是一个固定的字符串变量,比如currentTab = ref('TabA')。
为什么会有这个区别?因为Vue3的SFC编译器在处理静态属性时,会优先把is="xxx"当成第二种身份(也就是Web Components的挂载标识),而不是动态占位符,这一点非常重要,很多新手就是因为在SFC里用了静态is写在template上,结果要么组件没渲染出来,要么渲染成了一个陌生的HTML标签(Web Components的名字一般带连字符,普通组件名驼峰的话会被转成小写加连字符,比如TabA变成tab-a,而这个tab-a如果不是你注册的Web Components,浏览器就会当成无效标签忽略)。
自定义Web Components的“通行证”——告诉Vue“这是外部组件,别管我”
说完了第一种专门给过渡组件用的is,咱们来说第二种:绑定在普通组件标签上的静态is,用来挂载自定义Web Components。
为什么要用这种is?直接用Web Components的标签名不行吗?
这里有两个核心原因:第一个是Vue3的组件名解析规则,第二个是性能优化的需求。
先讲组件名解析规则:在Vue3的SFC里,如果你想直接用一个自定义Web Components,必须满足两个条件之一:要么这个Web Components的标签名包含至少一个连字符(比如<my-custom-button>),要么你在Vue的编译器选项里把这个标签名加到isCustomElement白名单里。
很多第三方Web Components库的标签名可能不符合你的命名习惯,或者你暂时不想改编译器选项(比如用Vite创建的项目,虽然默认支持连字符的Web Components,但如果想强制解析某个驼峰标签为Web Components,还是得加配置),这时候就可以用第二种身份的is:把普通的Vue组件标签写成<component is="my-custom-button"></component>,或者更简单的,直接写一个无效的Vue组件标签,比如<div is="my-custom-button"></div>——哦不对等下,div那种是第三种身份,别搞混,第二种身份的is是绑定在不带连字符、但Vue解析不了的普通标签名上,或者绑定在<component>上,用来强制把标签当成外部Web Components处理,不会触发Vue的组件解析逻辑,也不会走Vue的虚拟DOM diff(除了属性和事件的绑定),性能会更好。
举个例子,假设你现在引入了一个第三方UI库的Web Components,标签名是<ui-lib-primary-btn>(带连字符,默认能解析),但你不想每次都写这么长的名字,能不能简化成<PrimaryBtn>?这时候就可以用第二种身份的is:
<PrimaryBtn is="ui-lib-primary-btn" type="submit">提交</PrimaryBtn>
或者用<component>的方式:
<component is="ui-lib-primary-btn" type="submit">提交</component>
这两种写法的效果是一样的,都是告诉Vue:“不管标签名叫什么,我都要渲染成ui-lib-primary-btn这个Web Components,你只需要帮我把属性、事件、插槽传过去就行,其他的交给浏览器原生处理”。
新手踩坑点:第二种身份的is不能加冒号!不能加冒号!不能加冒号!
重要的事情说三遍!第二种身份的is必须是静态字符串,不能用v-bind动态绑定,如果你加了冒号,Vue就会把它当成第一种身份的动态占位符,去你的全局/局部组件库里找对应的组件,找不到就会报错,而不会去渲染Web Components。
还有一个小细节:如果用第二种身份的is绑定在不带连字符的标签上(比如刚才的<PrimaryBtn>),这个标签名必须不能和你注册的Vue组件重名,否则Vue会优先解析成你自己的组件,而忽略is。
原生标签的“伪装者”——用自定义Web Components覆盖原生标签的功能
第三种身份的is是最冷门但也最有用的,专门给原生HTML标签用,比如<button>、<input>、<div>,用来把原生标签替换成你自定义的Web Components,但保留原生标签的语义和默认属性。
为什么要用这种is?直接用Web Components的语义化标签不香吗?
这个问题的答案主要和无障碍访问(a11y)以及旧浏览器/旧框架的兼容性有关,你现在要做一个自定义的搜索按钮,如果你直接写成<my-search-button>,虽然功能能实现,但屏幕阅读器可能会把它当成普通的div处理,不会识别成“按钮”,导致无障碍访问有问题;但如果你写成<button is="my-search-button">搜索</button>,屏幕阅读器会先识别出这是一个原生的按钮,保留按钮的所有默认无障碍属性(比如tabindex、role、可点击的键盘事件),然后再把它的DOM结构和功能替换成你的Web Components,一举两得。
还有一个场景是旧浏览器的兼容性:虽然现在主流浏览器都支持Web Components的is属性(这种用法其实是Web Components标准里的“Customized Built-in Elements”,也就是自定义内置元素,和前面的“Autonomous Custom Elements”(自主自定义元素)是两种不同的Web Components类型),但如果你需要兼容IE11,或者旧版本的Vue2转过来的项目,直接用自主自定义元素可能需要额外的polyfill,而且修改起来比较麻烦;但用原生标签加is的方式,polyfill的兼容性会更好,旧代码的修改成本也更低——只需要给原来的原生按钮加一个is属性就行,其他的属性、事件、插槽甚至样式都可以保留。
新手踩坑点:Customized Built-in Elements的注册方式不一样!
很多人写完<button is="my-search-button">搜索</button>之后发现没效果,是因为自定义内置元素的注册方式和自主自定义元素不一样!自主自定义元素是用customElements.define('my-search-button', MySearchButton)注册的,而自定义内置元素必须加第三个参数extends,告诉浏览器你要覆盖的是哪个原生标签:
// 错误的注册方式(自主自定义元素)
customElements.define('my-search-button', MySearchButton);
// 正确的注册方式(自定义内置元素)
customElements.define('my-search-button', MySearchButton, { extends: 'button' });
这种用法的is也必须是静态字符串,不能动态绑定——这是Web Components标准的规定,不是Vue的限制,还有一点需要注意:不是所有的原生标签都支持自定义内置元素,比如<img>、<script>、<style>这些特殊标签就不行,具体可以查Web Components的官方标准。
再总结一遍:三种is的区别和适用场景,一张表格就能记住
怕记混?没关系,我给大家整理了一张清晰的对比表,以后忘了就翻出来看看:
| is的身份 | 绑定的标签类型 | 是否需要v-bind动态绑定 | 注册/使用前提 | 核心作用 |
|---|---|---|---|---|
| 动态占位符的灵魂钥匙 | Transition/Group下的template | 是(SFC必须是,CDN全局可以不是) | Transition/Group包裹,template必须加唯一key | 让过渡组件能正确追踪动态组件的根节点,触发正常的过渡动画 |
| 自定义Web Components通行证 | 普通Vue标签/component标签 | 否 | Web Components是自主自定义元素(带连字符或在isCustomElement白名单),Vue不能解析对应标签 | 简化Web Components的标签名,或强制Vue跳过组件解析,交给浏览器原生处理 |
| 原生标签的伪装者 | 原生HTML标签(button/input/div等) | 否 | Web Components是自定义内置元素,注册时必须加extends参数,原生标签支持自定义 | 保留原生标签的语义和无障碍属性,同时替换成自定义的Web Components功能 |
v-is是什么?为什么搜旧教程会看到?
很多从Vue2转Vue3的新手可能会在旧教程里看到<template v-is="currentTab"></template>这种写法,别担心,这只是Vue3的一个过渡属性,在Vue3.1版本就已经被正式弃用了,在Vue3.2及以后的版本里会直接报错或者失效,不管你现在用的是哪个版本的Vue3,都不要再用v-is了,统一用刚才讲的三种身份的is就行。
哦对了,还有一个新手常问的问题:能不能在同一个标签上同时用三种身份的is?答案当然是不能——每个标签上的is只能有一种身份,Vue会根据标签的类型、是否加v-bind、是否在特定的内置组件里自动判断,但如果你强行写冲突的配置(比如在TransitionGroup下的template上写静态is),Vue会优先按第二种身份处理,导致动态组件和过渡动画失效。
好啦,今天关于Vue3里is的所有内容就讲到这里,从三种身份的用法、区别、适用场景,到新手常踩的五个坑(SFC里template的is必须动态绑定、第二种身份的is不能加冒号、第三种身份的注册方式不一样、v-is已经弃用、不能同时用三种身份),应该都讲清楚了,如果还有什么不懂的,欢迎在评论区留言,咱们一起讨论!
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


