Work with the DOM
同学们,今天我们来看一个非常核心的概念,这个在Lightning Web Components开发里几乎每一步都会遇到,就是,DOM,。 DOM,全称是文档对象模型,你可以把它理解成整个HTML页面的一个活地图。有了这个地图,我们就能用代码去读取、修改页面的内容、结构,甚至是每个元素的样式。LWC之所以强大,就是因为它给了我们好几种不同的方式去和这个DOM打交道,也就是它支持,多种DOM模式,。 具体是哪几种呢?我们一个一个来看,很好理解。 首先是,原生Shadow DOM,。这个“Shadow”是影子的意思,它其实是浏览器自带的一种封装机制。组件里面的DOM结构、CSS样式,全部被包在一个独立的影子里,外部的样式进不来,里面的样式也漏不出去。对于现代浏览器,LWC默认就用它,让组件之间完全不打架。 那如果用户还在用一些老旧的浏览器呢?那些浏览器根本不认识影子DOM。这个时候,LWC会启用一种叫作,合成阴影,的模式。英文是Synthetic Shadow,你可以把它看成LWC专门为老浏览器打的一个补丁,用JavaScript来模拟出影子DOM的封装效果。对我们开发者来说,写代码的方式几乎一样,这是LWC帮我们处理好的兼容性问题。 第三种就是,Light DOM,,光DOM。这个和影子DOM正好反着来,它完全没有封装。组件的内容直接就放在全局的页面DOM里,没有任何隔离。好处是你可以从外部用全局CSS直接定义组件内部的样式,用起来很简单。但缺点也很明显,就是样式容易互相干扰。如果你需要那种轻松的全局控制,或者你正在为一个第三方库做包装,那Light DOM可能会派上用场。 最后还有一个已经在测试阶段的,混合阴影模式,。它的目标是帮助大家把老旧的合成阴影组件,逐步地、安全地迁移到真正的原生影子DOM上,而且不需要一次性大改。你可以让一部分组件先用原生封装,其他的先继续用合成模式,非常灵活。 了解了这四种模式,你可能会好奇:那在不同的模式之间,我们去读写元素、做样式这些操作,到底有哪些不一样的地方呢?这正是我们这一章要深入探讨的内容。 我们后续会覆盖的范围非常全,包括:不同模式下怎么安全地拿到元素;影子DOM内部的树形结构到底长什么样;CSS的作用域在不同模式下差别有多大;还有跨模式的插槽,也就是slot,它的行为会怎么变;以及事件是如何重定向的,也就是事件冒泡出去以后,源头变成了谁。当然,我们也不会忽略可访问性,怎么去做作用域插槽和样式,以及最实用的,怎么在各模式之间平滑转换。 别担心,这些点我都会配着详细的代码示例和实用指南,手把手地带你掌握LWC中DOM操作的一招一式。好了,这一页概览就先到这里,我们来一步步往下走。
本课程共有 25 个章节
同学们,今天我们来聊聊LWC里怎么安全又高效地操作DOM元素。 首先记住一个铁律:永远别用window或document全局对象去查DOM,比如document.querySelector这种写法。在LWC里它会被直接拦截,而且这么做会打破组件边界,咱的组件是封装好的,别去偷看隔壁组件的内部元素。 那正确姿势是什么呢?LWC推荐我们用声明式的方式,也就是那些HTML指令,像 lwc:if、for:each 这些。能用指令控制显示、循环的,就别自己动手用JavaScript去增删节点,这样代码更干净,也更符合框架的设计。 如果确实需要拿到某个元素的引用,咱们有两种模式。第一种是 querySelector 这套,通过 this.template.querySelector 去用CSS选择器找元素。第二种更直接,就是 refs 模式,在模板里给元素加个 lwc:ref 属性,然后在JS里直接 this.refs.xxx 拿到引用,不用写选择器,更安全也更快。 用 querySelector 的时候还要注意影子DOM和轻型DOM的区别。如果你在组件自己的影子DOM里,就用 this.template.querySelector;如果实在需要用轻型DOM,也就是元素被投射进来那种情况,才用 this.querySelector。一般大部分场景下咱们都用前者,记住就行。 最后,要是你非得集成一个第三方库,库直接操作原生DOM怎么办?这时候给那个根元素加上 lwc:dom="manual" 属性,然后用 platformResourceLoader 加载库就行了。这样LWC就知道这块DOM交给你手动打理,不再报错。 好,关于LWC里DOM元素的访问规则,就讲清楚啦。下回见!
同学们,今天我们来聊聊在LWC组件里怎么用JavaScript选择和操作页面上的元素,也就是querySelector和querySelectorAll这两个标准的DOM API。 首先记住一个前提:如果你要查找的元素是在组件的影子DOM里,也就是那些有自己隔离样式的内部元素,那么调用的时候一定要用this.template打头。比如,this.template.querySelector('.my-class'),这就会限定查找范围只在你这个组件的影子树里面,不会跑到外面去。 那要是你的组件用的是轻量DOM呢?就直接用this,比如this.querySelector('.my-class'),它会在组件的整个模板内搜索,但要注意,轻量DOM的查询有时候可能会搜到你直接模板之外的元素,所以写选择器的时候最好精确一点,别用太宽泛的规则。 好,下面这五个注意事项,大家一定要记牢,不然会遇到很多莫名其妙的bug。 第一,元素顺序没有保证。也就是说你执行查询得到的结果列表,元素出现的顺序不一定跟它们在模板里的书写顺序一致,千万别写依赖特定顺序的逻辑。 第二,条件渲染的元素不会出现在结果里。比如你用了if:true或if:false指令,在条件不满足时,对应的元素压根就不会被创建到DOM里,自然也就查不到。 第三,千万不要用ID选择器。因为在LWC渲染时,你写的id属性值会被自动转换成全局唯一的标识符,你代码里写的那个ID在运行时早就变了,根本匹配不上。 第四,轻量DOM查询可能越界,刚才提到过,这里再强调一下:如果你的选择器不够具体,可能会选中组件外面的一些元素,导致预期之外的行为。 第五,在Lightning Buttons组件下使用querySelectorAll可能会造成内存泄漏。这是历史遗留问题,如果你还在用旧版引擎LWC,要特别小心。最好的解决方式是迁移到Lightning Web Security,也就是LWS,或者改用refs引用来获取元素,避免直接查询。 最后给大家推荐一个学习资源:官方的lwc-recipes代码库里面有个miscDomQuery组件,它用实例演示了querySelectorAll的正确用法,大家可以拉下来跑一跑,看看效果。 好了,掌握这些,你就能安全高效地在LWC里和DOM打交道了。下节课我们会详细讲refs的使用,那是一个更好、更推荐的元素访问方式。
好,今天我们来讲一讲在 Lightning Web Components 里怎么正确地拿到页面上的元素,也就是我们常说的 DOM 元素。在 LWC 里面,官方推荐的方法是使用 ref,也就是引用。 你只需要在模板里,给想要拿到的那个元素加上一个 lwc:ref 指令,然后给它起个名字,比如 “myButton”,像这样写:lwc:ref="myButton"。然后,在你的 JavaScript 代码里,就可以直接用 this.refs.myButton 来访问这个元素了,非常简单直接。 这样做的好处很多。它让我们彻底摆脱了以前那种用 CSS 选择器去查找元素的方式,不用再去纠结什么 querySelector 这类东西了。ref 提供的引用是直接的、类型安全的,而且不管你的元素藏在影子 DOM 还是轻型 DOM 里,它的行为完全一致,用起来特别省心。 不过,用 ref 也有几个必须遵守的规则,不然会遇到坑。 第一个,你必须在元素真正渲染出来之后,才能通过 ref 去访问它。一般是在 renderedCallback 这个生命周期钩子里去操作,这时候可以确保元素已经在页面上了。 第二个,如果你在模板里不小心给了多个元素同样的 ref 名字,那 this.refs.那个名字 只能拿到最后一个匹配的元素,前面的不会返回成数组,所以名字最好保持唯一。 第三个,this.refs 这个对象本身是只读的,你不能整体给它替换掉,但你可以去赋值或者修改它暴露出来的引用,只是要小心,这可能会引发组件重新渲染。另外,ref 不能用在 template 标签上,也不能指向你插入到轻型 DOM 的 slot 元素,还有在 for:each 或者 iterator 循环里面也不能使用 lwc:ref,这些情况要用其他办法。 最后还有一个细节:ref 在原型链上是可配置、可写的,也就是说如果你的组件自己定义了一个和基类同名的 ref,它会覆盖掉基类里对应的引用,所以名字不要冲突,避免意外。 总结一下,记住这几点,用 ref 去拿元素就又安全又高效,是我们写 LWC 时候的首选方法。
同学们,今天我们来聊聊多模板组件中的 refs 是如何工作的,这个话题其实非常直观。 想象一下,你的组件会根据不同条件渲染不同的模板。比如,有时候显示模板 A,有时候显示模板 B。而 this.refs 就像是一个智能的小助手,它永远只指向当前屏幕上那个真正活跃的模板里的元素。 具体来说,当你的 render() 方法返回不同的模板时,refs 对象会立刻更新,它会丢弃上一个模板里的所有引用,只保留新模板里定义的 refs。也就是说,旧的 refs 你就访问不到了。 我们来看一个典型的增量示例:假设有一个计数器,每当你调用增量函数时,它会在模板 A 和模板 B 之间切换。每次切换时,你都可以在代码里打印出 this.refs,你会发现它总是只反映当前模板里的元素,之前模板里的引用已经完全被清除了。 这种机制在搭配 lwc:if 或 lwc:else 条件渲染时特别好用。你甚至可以在两个分支中使用完全相同的 ref 名称。比如,你可以在 if 和 else 分支里都放一个按钮,并都给它们设置 ref="toggleDarkMode",这时 this.refs.toggleDarkMode 就会自动引用当前现实在页面上的那个按钮——不管它是哪个分支渲染出来的。这种写法比故意取不同的 ref 名字、然后自己判断哪个被定义了要清晰干净得多。 所以,记住一点:this.refs 是与当前活动模板动态绑定的,它让你无需手动管理模板切换带来的引用清理问题,代码更简洁也更安全。 好了,这个小点大家理解了吗?有什么问题可以随时问我。
同学们,今天我们来聊聊LWC中一个很贴心的小工具,叫做`hostElement`。 你们可能遇到过这样的情况:想拿到组件自己的那个外层HTML元素,也就是宿主元素。以前呢,如果你是影子DOM模式,得用`this.template.host`;但要是你切到了轻型DOM模式,`this.template`直接就空了,就碰不到宿主了。是不是挺头疼的?代码每次都要判断模式,很麻烦。 好消息是,从API版本62.0开始,Salesforce给我们提供了一个统一的属性,就叫`hostElement`。不管你的组件用的是影子DOM还是轻型DOM,`this.hostElement`都能稳稳地返回那个宿主元素。在影子DOM下它等于`this.template.host`;在轻型DOM下它照样工作,不会因为`this.template`为空就罢工。这样你就不用关心渲染模式,写法统一了。 有什么用呢?比如你想获取组件的标签名,用`this.hostElement.tagName`;或者在宿主上设置属性;或者和父组件传个信;再或者要跟某个需要宿主DOM节点的外部库对接,都很直接。所以,从今往后,要访问宿主元素,就记住用`this.hostElement`,它就是我们跨越DOM模式的首选方式。 简单吧?记住这一点,代码就能更干净、更健壮了。好了,这一小节就到这里,有什么问题随时问。
同学们,我们这节课来聊聊 Web 组件里的一个核心概念,叫影子 DOM。听名字可能有点神秘,其实它就是一套封装机制,就像给组件穿上了一层隐形的外壳,保护里面的东西不被外面随便打扰。 简单来说,当你创建一个 Web 组件,它的内部结构会形成一个独立的 DOM 树,叫做影子树。这个树挂载在宿主元素上,但跟外面的主 DOM 是隔离开的。你用浏览器的开发者工具就能看到,组件内部代码开头有个 #shadow-root 的标识,这就是隔离的边界线。 这个隔离到底影响了哪些方面呢?主要有四点,你记住就能理解影子 DOM 的精髓了。 第一,样式隔离。你在父组件里写的 CSS 样式,是穿透不了这个影子边界的。也就是说,外面的样式不会意外地把你组件内部的按钮或文字给改坏了。当然,你也会发现,里面也很难直接受外面控制,这保护了组件的样式独立性。 第二,事件重定向。如果组件内部发生了一个事件,比如点击按钮,这个事件会冒泡穿过影子边界,但是它把自己原来的目标对象重新包装了一下。这样,外面收到的 event.target 就不是你内部的真实元素了,而是宿主组件本身。这就隐藏了内部细节,只暴露一个干净的接口。 第三,DOM 查询无法穿透。你在组件外部使用 document.querySelector 这类方法,是搜不到影子树里面的任何元素的。那如果组件自己内部想查呢?就得用 this.template.querySelector 这个专用方法,注意,一定是 template 这个属性上调用,它指向的就是影子树里的内容。 第四,插槽元素特殊。组件里用 slot 传递进来的那些内容,它们实际上并不属于影子树。它们还归父组件管,所以你要在组件里查询这些插槽进来的元素时,不能用 this.template.querySelector,而应该用 this.querySelector,这样才能找到那些由外部注入进来的孩子。 最后,有个特别有用的信息。在 Lightning Experience 里,为了确保这些行为在所有浏览器里表现一致,LWC 用了一种叫做合成影子 polyfill 的技术。也就是说,不管浏览器本身支不支持原生影子 DOM,你的组件都能享受到刚才说的这些封装保护,行为统一可靠。 好了,现在你理解影子 DOM 的妙处了吧?它就是让组件内部世界保持干净、不受干扰,同时又保留必要的通信渠道。后续我们会慢慢体会到这种封装带来的好处。
同学们,我们这节课来聊一聊Lightning Web Components里一个比较底层的知识点:DOM API的使用限制。这个内容听起来可能有点抽象,但我会尽量把它讲得通俗一些。 首先,你要知道,在Salesforce的Lightning平台上,为了确保每个组件都是独立的、不会相互干扰,Salesforce对不同运行模式下的DOM操作做了特殊处理。 我们先说第一种情况,就是在原来的“合成阴影”模式下,也就是我们常说的Lightning API环境。在这种模式下,当你从组件外部尝试去操作DOM的时候,有一组非常重要的标准DOM API会被直接阻止掉。这些被阻止的方法包括什么呢?像document.getElementById、document.querySelector、document.querySelectorAll,还有document.getElementsByClassName、getElementsByTagName、getElementsByName。注意哦,不仅document级别的方法被阻止,对应body级别的方法也一样会被拦住。为什么要这么做呢?就是为了防止你跨组件去访问或修改别人的DOM结构,从而保证组件的封装性。每个组件就管好自己内部那一亩三分地,你不能随便伸手到别的组件里面去。 那到了比较新的LWS模式,也就是原生阴影模式下,情况就变了。在这种模式下,刚才说的那些API并不会被直接阻止或屏蔽。那这样岂不是就可以随便跨组件访问了?并不是。LWS有它自己的办法:它会把所有组件的ShadowRoot模式强制设为“关闭”(closed)。一旦shadow root是关闭的,外部代码就没法通过元素的shadowRoot属性去拿到它内部的影子树,自然也就访问不到里面的内容了。所以本质上还是保护了封装,只是实现手段不同。 这里要特别提醒大家一个非常重要的坑:如果你在组件里使用了MutationObserver,也就是用来监听DOM变化的那个API,那你一定要记得,在不需要的时候把它断开连接,也就是调用disconnect方法。如果不这样做,很可能会造成内存泄漏。为什么特别强调这个呢?因为影子DOM的多边填充(polyfill)会对这个API做一些修补,如果你没处理好,它可能会一直持有引用,导致内存无法释放。另外还有一点,你的MutationObserver只能在自己的组件模板里观察变化,绝对不能去观察其他组件的影子树,这是做不到的,也是不允许的。 最后,如果你想把自己的代码从旧的合成阴影模式迁移到新的原生阴影模式,Salesforce提供了一个很方便的自动化工具,叫lwc-codemod。它可以帮助你自动转换代码,让你省去很多手动修改的麻烦。 好,这一页的内容我们就讲到这里。大家只要记住三个核心点:合成阴影下API被阻止,原生阴影下shadow root关闭,还有注意MutationObserver的断开和迁移工具。有什么问题,随时可以提出来。
同学们,今天我们聊一个稍微轻松一点的话题,就是“合成阴影”这玩意儿。你可能会觉得这个名字听起来有点玄,别急,我慢慢跟你说。 想象一下,几年前,Salesforce刚开始推Lightning Web Components的时候,浏览器对Web组件的支持还不够完美,特别是阴影DOM——也就是Shadow DOM,它能把组件的样式和结构隔离开,不让外面的东西乱影响。但有些旧浏览器根本不认识这个技术。那怎么办?Salesforce就写了一段补丁代码,叫Polyfill,它模拟出类似原生Shadow DOM的效果,这,就是合成阴影。 在Lightning Experience和Experience Builder里,合成阴影一直是默认启用的。它就像一个临时过渡方案,帮我们在那些不支持原生阴影的浏览器上也能正常运行组件。但是,这些年情况变了,所有现代浏览器都已经完美支持原生Shadow DOM了。我们现在再写新组件,完全不需要那个补丁了。Salesforce也意识到了,现在正积极地通过一种“混合阴影模式”,一步一步把系统迁移到原生阴影,将来合成阴影会被淘汰。这个过程你不需要做什么,底层会自己进化。 那么,合成阴影具体怎么工作的呢?它其实就是在每个组件元素上偷偷加上一堆奇怪的属性,比如 `lwc-66 unc 5l 95 ad-Host` 这种。这些属性就像独特的标记,CSS可以用它们来做样式的限定,模拟作用域。比如你的组件样式会自动被编上这些标记,就不会影响到外面了。 但是你要注意,这些奇怪的属性是Salesforce的内部实现细节。你可千万别依赖它们的格式去写代码或者写测试。因为开发团队随时都可能改,而且不会特意通知你。事实上,从API版本59.0开始,这些标记的格式就已经变了,以前可能是用组件名字生成的,现在换成了基于哈希的模糊字符串,更随机了。你要是写代码去检查 `lwc-` 开头的属性,那将来版本一升级,你的代码可能就莫名其妙坏掉了。 所以,记住两件事:第一,合成阴影正在被原生阴影取代,新组件放心用原生能力就行;第二,绝对不要碰那些奇怪的属性标记,它们是内部的,像幽灵一样,今天长这样,明天可能换了张脸。 好,这一小节课就到这里,你们明白了吗?有什么问题直接问。
同学们,咱们接着来看合成阴影是怎么处理样式的。这和原生阴影走的是完全不同的路子。 原生阴影会把样式封在组件自己的影子树里面,但合成阴影不一样。它把带有作用范围的样式,直接注入到整个文档的 `<head>` 标签里。而且它不是用普通选择器,而是利用属性选择器,只精准地命中这个组件自己的元素。这样一来,我们就可以在文档级别去写一套共享样式,来统一设置所有组件的样式了。SLDS 在 Lightning Experience 里正是这么工作的,组件不用各自再加载一遍样式表,既统一又省事。 那如果组件自己带了样式,优先级是怎么排的呢?你记住这个顺序就好:优先级最高的,是组件自己写的 CSS,它带有作用范围,是最具体的;接着,是通过 `@import` 引入的 CSS,它同样也带有作用范围;最后,才是通过 `loadStyle` 这种全局方式加载的样式,它会影响所有组件,所以优先级最低。 还有个要留意的变化。从 API v59.0 版本开始,CSS 作用域的令牌格式改了。以前会带上组件名字,一眼就能看出来是哪个组件的样式,现在换成了基于哈希值的模糊令牌,更难从名字上直接辨识了,但封装效果其实是一样的。 最后咱们说说性能。你可能会担心,如果好几个组件都拷贝了相同的样式表,会不会重复加载拖慢性能?完全不用担心,LWC 会自动帮你把重复的样式去掉,所以就算你多复制几份,也不会造成额外负担。 好了,关于合成阴影的样式处理,我们就先聊到这儿。
同学们,今天我们来聊一个非常实用的话题,叫做 Light DOM。你可能已经知道,我们平时写的 Lightning Web Component 默认用的是 Shadow DOM,它会把组件内部的 DOM 结构封装起来,样式和脚本都不会轻易跑到外面去。这当然有好处,但有时候,这种封装反而会碍事。这时候,Light DOM 就出场了,它就像是 Shadow DOM 的一个更灵活的替代方案。 那 Light DOM 到底怎么做呢?简单来说,它的内容不再藏在那个神秘的 `#shadow-root` 里面,而是直接挂到宿主元素上,也就是直接出现在主页面的 DOM 树里。就像你把组件内部的东西,直接搬到了阳光下,人人都能看到,而且能和周围的环境自由互动。 这样做有三个很实在的好处。 第一,全局 CSS 样式终于能自然生效了。在 Shadow DOM 里,外部的 CSS 一般打不进去,你想做主题化、品牌定制的时候可头疼了。但用了 Light DOM,全局样式可以直接穿透进来,你写个全局的 `.theme-dark` 或者 `.brand-header`,组件内部一下子就跟着变了,非常方便。 第二,第三方工具再也不会被影子根难住了。比如你用 Google Analytics 追踪页面行为,或者跑自动化测试的框架,它们习惯遍历整个 DOM 树。遇到一层层的 `#shadow-root`,它们就得做特殊处理,甚至什么都看不清。Light DOM 完全扁平化,这些工具可以毫无障碍地看穿一切,省心很多。 第三,可访问性变得更友好。因为 DOM 平铺了,不同组件里的元素 ID 不再被隔离。在 Shadow DOM 里, id 是各自为政的,你不能从一个组件调用另一个组件的 id,这给像 ARIA 属性关联带来很大麻烦。但在 Light DOM 里,所有 id 是全局可见的,你可以轻松地用 `aria-labelledby` 等属性把不同组件里的元素关联起来,让屏幕阅读器理解得更好。 所以,什么时候该考虑用 Light DOM 呢?就是当你需要构建高度可定制的 UI,比如让用户可以随意改样式;或者必须无缝集成那些会扫描整个 DOM 的第三方工具;又或者,你希望父组件的样式能够自然地继承到子组件里。这些场景下,Light DOM 就是一个很棒的选择。 如果你想动手试试,官方有个很好的学习资源,就是 lwc-recipes 仓库,里面有好几个以 "lightDom" 开头的例子,你可以自己跑起来看看效果。 好了,今天的讲解就到这里,光说不练假把式,赶紧去写个 Light DOM 组件感受一下吧。
我们来看看这个关于“轻型 DOM”和“影子 DOM”的要点。 简单来说,Lightning Web Components 里,你可以选择两种封装模式:一种叫“影子 DOM”,另一种叫“轻型 DOM”。 影子 DOM 就像给你的组件加了一层保护罩,外面的代码无法随便看到或改动里面的结构,安全性很高。但缺点是不够灵活,有时你想让外面能控制一些样式,就得费点劲。 轻型 DOM 恰恰相反,它完全敞开,页面上任何地方的代码——哪怕是别的公司写的组件——都能读取甚至修改你组件的内部结构。这就带来了灵活性,但也牺牲了安全感。 所以,这里给了一个很实用的建议:别直接用轻型 DOM 来装敏感数据。推荐的架构是,在顶层用一个影子 DOM 做“安全外壳”,里面再放轻型 DOM 的子组件。这样既能在影子根内部共享全局样式,又能在边界处保证安全,一举两得。 不过要注意,轻型 DOM 里有些功能是用不了的。比如: - 不能限制命名空间 - 没办法在包之间分发组件 - 基础组件只能永远是影子 DOM - 不能嵌入 Aura 组件 - 没有插槽的生命周期钩子 - 也不支持在循环里用内部插槽 最后,如果你想从影子 DOM 把样式穿透到轻型 DOM 的子元素,正确的方式还是用 CSS 自定义属性,或者用 `::part` 伪元素,这两个是专门用来传递样式的扩展点,安全又规范。 好,这一页的重点就是这些,记住:用轻型 DOM 要谨慎,敏感数据一定要包在影子 DOM 里。
好,同学们,我们接着讲。今天聊一个在Lightning Web Components里做选择时,非常关键的一点——到底什么时候用,浅色多姆,,什么时候用,阴影多姆,。 首先,你必须把,安全性,放在第一位,这是首要考虑因素。 想象一下,你的组件如果放在整个结构的最顶层,而且是浅色多姆,那它就像一栋没有围墙的房子。其他命名空间的代码,可以轻而易举地“读”到它里面的内容,这就不安全了。所以,一个黄金法则是:,永远把你的浅色多姆组件,包裹在一个阴影多姆的祖先组件里面,。这样,外层的阴影多姆就像一道坚固的围墙,保护了里面的一切。 当然,这两种模式各有利弊,我们来口头“翻译”一下这个比较表格。 选择,阴影多姆,,你得到的是强大的封装性,你的组件就像一个个独立的黑盒,可以放心地到处移植,不用担心样式冲突或内部逻辑被干扰。但代价是,从外部给它定制样式会变得困难,一些需要深入组件内部DOM来工作的第三方工具,集成起来也更费劲。 反过来,选择,浅色多姆,,样式定制和第三方集成会变得非常容易,就像普通网页一样顺手。但代价呢?就是你放弃了宝贵的封装性和可移植性。 这里有个很常见的场景,比如你要集成一个分析工具之类的第三方库。很多人觉得,为了让工具能抓取到按钮,是不是必须得把整个组件搞成浅色多姆?,不一定!, 一个更好的思路是:你的阴影多姆组件只要向外部暴露正确的API就可以了。 举个例子,别让那个工具费劲地钻进你组件内部去找一个按钮。你可以在自定义元素本身这个层级,就直接添加一个点击处理器。工具只需要知道你的组件被点击了就行,完全不用关心内部是哪个按钮触发的。 记住这个原则:,只把你最少、最必要的东西暴露出去,给工具提供它真正需要的部分就可以了,核心内部结构最好还是保持封装。, 这样,你就在安全性和集成便利性之间,找到了最佳平衡点。
同学们,今天我们来聊一个在Lightning Web Components里很实用的特性——启用Light DOM。简单说,Light DOM就是让我们的组件不使用影子DOM,而是直接渲染到主文档的DOM树里,这样全局CSS样式就能直接作用到组件内部,也更方便从外部访问组件里的元素。 那要怎么开启Light DOM呢?其实只需要做两个小改动,记住缺一不可。第一,在组件的JavaScript类里,添加一个静态属性 `static renderMode = 'light'`。第二,在组件的HTML模板文件里,找到最外层的根标签,给它加上 `lwc:render-mode="light"` 这个属性。这两步必须同时设置,如果只做了其中一个,组件就还是会按默认的影子DOM来渲染,所以千万别漏掉。而且这个`renderMode`属性是在组件创建的时候读取的,之后你再怎么改它都不会生效了,所以一开始就要配置好。 那Light DOM和Shadow DOM的组件能混用吗?当然可以。它们之间可以自由嵌套,不管是外面是Light DOM里面是Shadow DOM,还是反过来,都非常灵活。实际项目里,我推荐大家这样设计:在组件树的根节点用单个Shadow DOM组件,就像一个安全的外壳,把内部的子组件都做成Light DOM组件。这样既能在最外层保持封装边界,防止样式泄漏出去,又能在内部让子组件轻松使用全局样式,方便共享设计和快速访问DOM,是个很实用的最佳实践。 好了,这块内容很简单,记住两个强制改动和这个嵌套原则就好。有什么问题随时问。
好,我们来看一下 Light DOM 一个特别突出的优点,就是它打破了 ID 的隔离,这对可访问性的提升有很大的帮助。 你可以先回想一下经典的 Shadow DOM,它就像一个封闭的小房间,每个组件里自己的 ID 都只在这个房间里有效。也就是说,一个组件的按钮,它的 ID 是没办法被另一个组件里的标签引用到的。但 Light DOM 就不一样了,它的元素都直接放在整个页面的共享文档里,ID 是全局可见的。这就允许我们在两个不同的组件之间,建立像 aria-labelledby 这样的 ARIA 关联。比如,一个组件里的标题,可以直接用它自己的 ID,去给另一个组件里的某个区域做标签说明,浏览器或屏幕阅读器就能正确理解它们的关系,让无障碍访问更顺畅。 再说到 CSS 部分,Light DOM 也带来了更自然的表现。在 Shadow DOM 里,外面父母组件的样式,默认是穿透不到影子里的。但在 Light DOM 中,样式可以像我们熟悉的普通网页那样,从父组件自然地“流”下来,也就是级联。父组件的样式表里定义的规则,会直接作用到 Light DOM 子组件的内部元素上。 不过这里有个重要的例外,你要注意一下:这种级联特性只有在原生 Shadow DOM 环境下才工作得很好。如果你用的是合成 Shadow——也就是为了兼容老版本浏览器,框架模拟出来的那层影子——目前还有个限制,样式级联到 Light DOM 子组件还不支持。所以,为了能处处享受到这个便利,通常我们更推荐在支持原生 Shadow 的环境中运行。 另外,有时你也许并不希望父组件的样式乱入到子组件里,造成样式泄露。解决这个问题很直接,给 Light DOM 组件单独建一个作用域样式表,也就是文件名以 .scoped.css 结尾的那个。这样一来,组件里的样式就只会影响自己,不会意外地跑出去。 还有一点很关键:Light DOM 组件的渲染顺序,会决定样式表被注入到页面里的先后顺序。因为 CSS 的优先级规则,同样的选择器,谁在后面被声明,谁的权重就更高。所以,如果页面上有多个 Light DOM 组件,它们加载和显示的顺序,就可能默默地影响最终哪个样式生效。开发的时候要记得留心这个顺序,避免出现难以排查的样式覆盖问题。 简单来说,Light DOM 通过共享 ID 空间,让跨组件的无障碍标记成为可能;通过自然的样式级联,简化了父子组件的样式传递;同时,我们也有工具去控制范围,防止泄露。只是要记住,渲染顺序会影响样式特指性,这个得作为一条潜在规则记在心里。
同学们,今天我们来聊聊Light DOM在LWC中带来的一个核心变化:怎么在组件里获取元素。这在以前用Shadow DOM的时候,我们是通过 `this.template.querySelector` 来找元素的,模板被封装在影子根里。但现在启用了Light DOM,影子根也就不存在了,所以组件的 `template` 属性会直接返回 `null`。那该怎么办呢? 很简单,所有元素查询都要改用 `this` 来直接操作了。像 `this.querySelector`、`this.querySelectorAll`,还有 `getElementById`、`getElementsByClassName` 这些标准的 DOM API,全都变得能用了。所以,从Shadow DOM迁移到Light DOM,你主要做的一件事就是把代码里的 `this.template.querySelector` 统统替换成 `this.querySelector`。 但是,这里有两个需要特别小心的点,也就是所谓的“警告”。 第一,`this.querySelectorAll` 可能会把别的轻量级组件里的元素也选出来,因为Light DOM下大家的标记都是平铺在宿主元素下的。所以你在写选择器的时候,一定要加一些特定的前缀或者类名,确保只拿到自己想要的元素。 第二,在Light DOM里,元素的 `id` 属性会被原样保留在运行时,不像以前合成阴影那样会被修改,这就意味着你可以直接用 `getElementById` 来获取元素,id选择器能正常工作。 尽管标准DOM API现在这么好用,但我们还是推荐大家优先使用 `this.refs`。为什么呢?因为无论你的组件采用Shadow DOM还是Light DOM,`this.refs` 的用法和访问方式都是一模一样的,这让你的代码更统一、更好维护。所以记住,`this.refs` 依然是最稳妥的推荐方法。 好,这个小节我们就讲到这儿,大家明白了吗?
嗨,同学,咱们今天花几分钟来聊聊 Light DOM 里的事件和插槽,这跟 Shadow DOM 的差别挺大的,你只要抓住“就像普通网页”这个感觉,就很容易理解了。 先说事件。在 Light DOM 里,事件的表现其实跟你熟悉的原生浏览器事件一模一样,因为这里没有 Shadow DOM 那层影子边界来对事件做隔离和重新定位。所以,当你在 Light DOM 里点击某个元素,event.target 永远直接指向真正触发事件的那个元素,不会因为跨了组件边界就被改写。事件也会在所有 Light DOM 的层级里自由自在地向上冒泡,一路传到根节点,没有任何阻挡。 还有一个容易让人困惑的地方:即使你把事件设为 composed: true,它在 Light DOM 下面仍然会冒泡。为什么呢?因为没有影子根来把事件包在里面,composed 这个属性在 Light DOM 里实际上没什么需要破除的边界,事件本身就没有被限制,所以它照样冒泡。 好,说完事件,我们来看看插槽。Light DOM 里的插槽是刻意去模仿浏览器原生影子 DOM 的机制,但注意它是在模仿,很多功能其实没有。最关键的一点是,slot 元素本身在 DOM 树上是不渲染的。你看不到它,它也不会生成任何实际的盒子。这样一来,像 slotchange 事件、::slotted 这种 CSS 伪类选择器,还有插槽附属的生命周期钩子,统统都不能用。那你放进去的内容到哪里去了呢?它们会被直接“展平”到父元素的 DOM 结构里,就好像这些内容是直接写在那儿的一样。 如果你想给插槽命名,机制和原来一样:子元素上放一个 slot 属性,值就是插槽的名字,然后父组件里写一个 name 属性相同的 slot 元素,两边一匹配,内容就分配过去了。但有一件事要特别小心——如果没有匹配到任何命名插槽,也没有放到默认插槽里,那这段内容根本就不会渲染出来,它的生命周期钩子也完全不会触发,等于这段代码就被静默扔掉了。所以开发的时候一定要确认好插槽的匹配,不然可能会莫名其妙地丢失内容。 总结一下:Light DOM 让事件变得透明直接,让你用原生 DOM 的方式去思考;而插槽变得更朴素,丢了 shadow 那一套 API,但依然能靠属性匹配实现内容投影。记住这几个要点,写 Light Web Components 就不会踩坑了。好了,下节课我们再深入实践,下课!
同学们,今天我们来讲一个关于 Light DOM 和 Shadow DOM 样式继承的小知识点,它跟 slot 插槽有关。这个内容如果搞不清楚,可能会让你的组件样式出现意料之外的效果。我们一起来把它拆解开。 首先,想象一个场景:你有一个父组件,在它的模板里使用了一个子组件,并且往子组件的 slot 里放了一些 HTML 元素,比如一段文字或者一个按钮。现在父组件自己定义了一些样式,比如把字体颜色改成红色。那问题来了:这些放在 slot 里的内容,会继承父组件的样式吗? 答案是:会继承,但只继承到这些 slotted 的元素上。也就是说,父组件的样式会“穿透” slot,作用于你塞进去的那个元素的 DOM 上。这在技术上称为“级联”到 slot 里的元素。但是,有一个关键的例外你要记住:作为 slot 容器的那个子组件本身,也就是提供插槽的那个 shadow DOM 组件,它自己是不会收到父组件样式的,除非它主动导入或者用其它方式把这些样式引进来。换句话说,父样式会影响 slot 里的内容元素,却不影响套着 slot 的那个盒子。 这就让样式的作用范围很清楚了:内容样式跟着父组件走,容器组件的内部样式依然是它自己说了算。 现在再讲一个关于 slot 这个 HTML 属性的行为变化。它虽然不起眼,但在版本升级后可能会影响你现有的代码。 在组件模板里,你会给被插入的元素写上 `slot="某个名字"`,这是用来告诉浏览器:“我要去匹配子组件里叫这个名字的插槽”。在过去,API 版本 60.0 及更早的时候,LWC 会在最终渲染出来的 DOM 里保留这个 `slot` 属性。也就是说,你在浏览器检查元素时,能看到 `<div slot="header">` 这样的东西。 但是从 API 版本 61.0 开始,这个 `slot` 属性在 Light DOM 里的 slotted 元素上会被移除,因为它本身只是给 slot 分配算法看的,不影响最终的渲染,所以运行时不再需要保留它。移除的好处是让 DOM 更干净。但要注意,这项清理工作只有一个例外:当你进行 slot 转发的时候,也就是把内容再传给另一个更深层的 shadow slot 时,这个属性必须保留,否则内容就找不到最终的目的地了。 那么,如果你以前的代码或者 CSS 选择器是依赖 `[slot="header"]` 这种属性选择器来工作,升级版本后就可能会失效。官方建议是迁移到使用 `lwc:ref` 指令,或者改用 class、id 等其他选择器,而不要依赖运行时存在的 slot 属性去定位元素。 总结一下今天的内容:父样式能管到 slot 里的内容,但不管提供 slot 的盒子;而 slot 属性在新版本里默认会被删掉,除非你故意转发 slot,否则别用它来做选择器。这样一来,你的组件在样式和属性使用上会更健壮。下次我们聊聊 lwc:ref 的用法,到时你会发现它比 slot 属性要靠谱得多。 好,这节课就到这里,有问题的随时打断我。
同学们,咱们今天讲一个挺有意思的特性,叫做“插槽转发”。听起来有点绕,别担心,我拆开一说你就明白了。 我们先回忆一下,LWC里的插槽(slot)是用来挖坑的——父组件可以往组件预留的坑里塞内容。那如果你有这样一个结构:最外层的父组件用了一个中间组件,中间组件内部又包了一个真正想要展示内容的子组件。这时候,父组件塞进来的内容,怎么穿过中间这一层,最终到达里面的子组件呢? 这就是插槽转发要解决的问题。就像快递转运,包裹要从A市经B市转运到C市,B市这个中转站得把包裹原封不动地传下去。 在早期的版本,也就是API 60.0及以前,我们想做到这一点,得在中间组件里套一个普通的`div`,让它作为一个中转包裹的“包装箱”。写法上稍微有点啰嗦,但能用。 好消息是,从API v61.0开始,LWC把这个过程大大简化了。现在你根本不需要那个多余的`div`,直接在中间组件的`slot`元素上加一个`slot`属性就可以了。听着有点像同义反复?其实就是说:这个`slot`元素,它自己既是接收内容的“坑”(通过`name`属性),又是把内容继续转发出去的通道(通过`slot`属性)。干净利落。 两种写法效果完全一样:最外层父组件的内容,先流进中间组件(比如叫c-outer)命名的插槽,然后中间组件直接转发给里面真正的目标组件(比如c-inner),内容丝滑地到达最终展示的地方。 之所以保留老方法,主要是为了向后兼容。如果你维护的是老代码,`div`包装的方式仍然能用,不用担心升级就崩掉。 这种模式在我们构建组件库的时候特别重要。很多时候中间组件就是个壳,用来封装一些逻辑或样式,但真正的展示要交给内层组件去完成。插槽转发就让这个“壳”能够透明地传递内容,既保持了封装,又不牺牲灵活性。 好了,小结一下:插槽转发让内容像接力棒一样,稳稳地从外传到内,而新版的做法就是给`slot`元素直接加`slot`属性,省掉了无用的包装。简单吧?有什么问题随时提出来。
好,咱们今天来讲讲一个挺实用的概念,叫“有范围的插槽”。你可以把它想象成子组件和父组件之间的一种合作方式,专门用来处理列表渲染这种场景。 打个比方,子组件手里有一堆数据,比如一个产品列表,但是它不知道怎么展示每个产品才好看;父组件呢,它懂得设计漂亮的卡片样式,但它没有数据。这时候有范围的插槽就派上用场了。 具体怎么玩呢?子组件会在它的插槽上绑定数据,用的是 `lwc:slot-bind` 这个指令,相当于说:“嘿,我这里有每个项目的具体信息,我把它传给你。”而父组件接收这个数据,通过 `lwc:slot-data` 和一个模板片段(template fragment),然后按照自己的想法把每一条数据渲染成界面元素。 有个关键点要记住:这种模式要求子组件必须使用轻型 DOM,也就是说,插槽不能在影子 DOM 里运行,但父组件就不受限了,它可以自己决定用哪种模式。另外,因为插槽内容的最终结构是由父组件决定的,所以父组件自己的 CSS 样式也能直接作用上去,这很方便控制外观。 那为什么要叫“有范围”呢?因为虽然是子组件在循环数据——它会根据数据有多少项,就创建多少个插槽实例——但每个实例接收到的数据只属于那一条,父组件只是照着模板把这一条数据画出来。这就相当于,子组件管理“循环逻辑”,父组件只负责“把一条数据变成界面”,两个组件的职责分得很清楚。最终,我们就实现了一个灵活的列表:父组件定义行模板,子组件处理数据循环,彼此不干扰,又能完美配合。 这就是有范围的插槽,它让组件之间的复用性和灵活性都提高了不少。
同学们,我们现在来看一下Lightning Web Components里一个非常强大的特性——有范围的插槽,也就是scoped slots。它让我们在处理复杂的数据绑定时,有了更大的灵活度。 首先,在一个组件里,你可以定义多个有名称的作用域插槽,每个插槽都可以有自己的数据源,互不干扰。也就是说,每个插槽都能传递不同的数据给使用它的父组件。当然,多个插槽也可以绑定到相同的那个数据,这没问题。但要注意,单个插槽名称,也就是同一个名字的插槽,你不能试图给它绑定上不同的数据源——如果你那样写,编译器会直接报错。 还有一点很关键,标准插槽和作用域插槽是两种不同的东西。你不能在同一个组件里,对同一种类型——也就是默认插槽或者同名插槽——混用这两种。简单说,如果子组件里某个插槽被定义成标准插槽,而父组件里却用了作用域插槽的写法来对应它,那这个插槽根本不会渲染。所以,父组件和子组件必须对插槽的类型达成一致,大家都说好它是标准插槽,或者它就是作用域插槽。 作用域插槽还能嵌套,这可是个真正厉害的地方。内部的、被嵌套的作用域插槽,不仅可以访问它自己直接收到的数据,还能访问到外层所有作用域插槽传下来的数据。这种模式非常适合在表格行和单元格这样的场景里,你可以按每个单元格去做自定义的渲染,非常灵活。 最后,作用域插槽本身可以同时引用两种数据来源:一种是来自父组件的组件绑定数据,另一种是来自子组件通过插槽传给它的作用域绑定数据。所以,数据绑定的能力就非常全面了。 简单总结一下,作用域插槽就像是一个数据传递的高级通道,让我们能在组件间完成既结构化又个性化的内容展示。好,这部分我们就讲到这儿。
同学们,今天我们来聊聊Lightning Web Components里一个很实用的功能,叫Scoped样式表,文件名后缀是.scoped.css。这个功能给轻型多姆组件带来了类似Shadow DOM那种样式封闭的效果。 简单说,就是LWC编译器会帮你自动处理CSS选择器,给它们加上一个组件专属的作用域class。这样一来,你写的样式就只会对当前组件里的元素生效,不会到处乱跑污染其他组件。 那在浏览器最终渲染出来的页面里,你会看到,组件注入了带有转换后选择器的样式标签。比如你写的按钮样式,它会自动变成带特定class的选择器,只匹配组件内部的按钮。 还有一点要注意,如果一个组件里同时有普通的.css文件和作用域的.scoped.css文件,加载顺序是无作用域的先注入,作用域的后注入。什么叫后注入?就是作用域样式会覆盖前面冲突的规则。所以作用域样式拥有更高的优先级。不过呢,无作用域样式仍然有可能泄露到页面其他元素上,而作用域样式会被牢牢限制在组件内部。 另外,如果你的轻型多姆组件是放在基于Aura的容器里用的,那么它只支持作用域样式表,这一点要记住。 最后一点,强烈不建议在作用域样式里用!important这种粗暴规则,因为作用域样式通过转换后的选择器,本身就已经能自然获得比较高的特异性了,足够覆盖大多数情况了。 好,这一页我们就讲到这里。有什么问题随时问。
我们现在来讲讲Lightning Web Components中样式作用域的一个关键概念。 首先,什么是“范围区域”呢?其实就是组件HTML模板里定义的所有元素的集合。换句话说,组件模板内的一切元素构成了一个作用范围。而作用域样式,就是专门针对这个范围里面的元素生效的。它是通过CSS类来实现作用域的,这一点和合成阴影那种基于属性的作用域不太一样,不过这只是外观上的差异,功能目的相同。 接下来要说的是`:host`伪选择器。它在有作用域和无作用域的样式上下文里,行为是有区别的。在无作用域样式中,`:host`指向的是最近的影子根宿主。但在有作用域样式中,编译器会把`:host`转换一下,让它指向轻量DOM组件的根元素。这样做的目的,就是让你能方便地对组件最外层的元素进行样式设置,就像给组件自身穿上外套一样。 不过,轻量DOM作用域样式相对合成阴影有三个局限性,你们需要知道。第一,它使用类而不是属性来做作用域标记,这属于刚才提到的表面差异。第二,它不支持作用域样式表里的`@import`规则。第三,它不能应用于通过`lwc:dom="manual"`手动注入的内容,比如用`appendChild`或`innerHTML`加进来的元素,这些内容的样式不会自动带上作用域。 最后要留意,无论是哪种模式,都不支持`:host-context()`选择器。 好了,这一部分就讲到这里,大家对作用域样式应该有个清晰的轮廓了。有什么问题随时打断我。
同学们,今天我们来聊聊Lightning Web Components里一个挺酷的功能,叫“混合阴影模式”。听起来有点技术化,但别担心,我会用简单的话解释清楚。 想象一下,Salesforce的组件通常用一种叫“合成阴影DOM”的技术来渲染,这像是我们自己搭的一个保护罩,但它是模拟的,不是浏览器原生的。现在,Salesforce正在往浏览器自带的“原生阴影DOM”迁移,因为原生性能更好,也符合标准。混合阴影模式就是这个迁移过程中的一个桥梁,它还处于测试阶段,让你能慢慢过渡,不必一次性大改所有代码。 这个模式的好处是啥呢?首先,性能提升特别明显。在一些测试里,渲染速度能提高50%,这意味着页面加载更快、响应更敏捷。另外,一旦未来完全移除旧代码,也就是那个模拟用的Polyfill,Lightning Web Components的JavaScript文件大小可能会减半,这对移动端或性能敏感的应用来说,简直是福音。 但有个重要的限制要注意,就是组件层次结构。你可以让外层组件继续用合成阴影,里面包着用原生阴影的子组件。也就是说,合成阴影组件是“爸爸”,能包容原生阴影的“孩子”。反过来可不行——如果外层组件已经是原生阴影,它不能嵌入合成阴影的子组件。这就像一个大容器,只能从一个方向过渡。所以,迁移时,我们最好从最底层的“叶子组件”开始,这些组件通常没有子组件,改起来风险小。然后逐步往上,每改一个层级,都测试一下,确保没问题。 在浏览器开发工具里检查时,你也能直观看出区别。如果组件用了原生阴影模式,你会看到DOM结构里有个“#shadow-root”标签,像一个明确的标记。合成阴影组件呢,就没有这个标签,看起来更平淡。这对调试很有帮助。 具体怎么迁移呢?很简单,在你的组件JavaScript文件里,加一个静态属性叫“shadowSupportMode”。它可以设置三个值:选“native”就直接启用原生阴影模式;选“any”是让组件自己决定,但官方不推荐用,因为容易出意外;选“reset”则是回到默认的合成模式。实际工作中,建议先在小范围用“native”试试,比如一个叶子组件里,验证性能提升,再慢慢推广。 最后提醒一下,这是个测试版功能,虽然很诱人,但还是要谨慎使用。在生产环境中,一定要充分测试,尤其注意组件间的交互,比如事件传递或样式穿透,可能会受影响。总之,混合阴影模式是个迈向更优性能的好机会,只要我们一步步来,就能平滑升级。 好了,大家对这个概念有什么疑问吗?我们接下来可以深入聊聊实践的细节。
同学们,今天咱们聊聊Lightning Web Components里的混合阴影模式。这个概念听起来有点绕,但其实就一句话:你想让某个组件用浏览器原生的Shadow DOM,就在它的JavaScript类里加一个静态属性,像这样: static shadowSupportMode = 'native'; 一旦你给一个父组件设成了“native”,它的整个子树里所有的子组件,不管它们自己怎么设置,都会被迫用原生模式。简单说,就是父级说了算,原生模式会向下强制继承,哪怕子组件想逃也逃不掉。 但有没有例外呢?有,但不多。LWC提供了一个“reset”值,如果某个子组件明确设置为“reset”,它表示“我想退出继承来的原生模式”,但前提是它上面没有祖宗组件设成了“native”。注意,我说的是“没有祖宗”,也就是说,只要祖辈里有一个原生的,那整个链条全都被钉死了,reset也没用。所以原生像一个从上往下的铁幕,reset只能在没有原生祖先的情况下起效。 那父子关系出现“插槽”时怎么办呢?插槽的内容由谁决定?规则是这样的:插槽里的内容,看的是拥有这段内容的那个组件的设置,而不是提供插槽的组件。比如一个合成模式的组件往一个原生组件的插槽里塞内容,这段内容仍然保持合成模式,不会被原生模式吞掉。反过来也行,混合使用就很灵活。 最后,我们把这个继承关系用表格说得更清楚一点:父组件是“native”,下面无论子孙设什么都没有用,全是native;父组件是“reset”或者没设置,子组件如果坚持设“native”,那它就勇敢地变成原生,旁边的兄弟不受影响。 所以核心就这三板斧:native父级强制所有子级原生;重置只有在无原生祖先时生效;插槽内容的模式看内容拥有者。这样设计,既保证了局部选择原生带来的性能好处,又保留了合成模式的灵活注入,咱们在真正项目里就能按需混用了。
好,我们来聊聊原生阴影和合成阴影的区别,这可是开发中容易踩坑的地方。你可以把它想象成两种不同的房间布置方式:合成阴影像开放式厨房,东西容易共享;原生阴影则是独立隔间,各自管各自的。运行时很简单,用`this.template.synthetic`一查,返回`true`就是合成模式,`undefined`就是原生模式。 先说原生独有的一些功能:它支持服务端渲染(可能叫SSR,这里写成了SSSR)、`::part`选择器、表单相关的自定义元素,还有命名插槽。这些在合成里是没有的。 元素渲染上有个明显的差异:原生阴影下,就算你没定义插槽,组件内的所有元素照样哗啦啦全渲染出来;而合成阴影就很“务实”,只渲染那些真正放进插槽里的内容,不用的一概不显示。 最大的坑其实是样式。合成阴影允许你加载全局样式表,比如Salesforce的SLDS,一加载就能让所有组件直接用,很方便。但原生阴影要求每个组件必须导入自己的样式文件,全局的那套不管用了。如果你需要跨组件共享样式,最好用CSS自定义属性(也就是变量),或者把样式值当作属性传进去。 可访问性也受影响。每个组件里的ID都是本地作用域,所以像`aria-labelledby`这种跨组件引用ID的做法就行不通了。解决办法是把被引用的元素放在同一个组件里,或者改用`aria-label`。 还有几个点要留意:原生阴影目前还不支持某些基础组件;像`innerHTML`这类Web API的行为也变了,合成模式会暴露阴影内部的内容,原生模式为了封装,会把这些内容藏得严严实实。 所以迁移原生阴影时,样式、ID引用和API行为都得仔细检查,特别是全局样式的习惯要改掉。明白了吗?