Component Accessibility
同学们,这一讲我们来聊一聊Lightning Web Components的无障碍设计。我们都知道,一个好的应用应该让所有人都能用,包括有各种障碍的朋友。所以Salesforce在设计Lightning基础组件的时候,就已经把无障碍功能内置进去了,比如按钮、表单这些基础组件,你拿来直接用,它就自动帮我们做了很多可访问性的处理。 但关键是我们自己写的自定义组件,就得我们自己操心了。这一页Slide其实给我们汇总了一个完整的无障碍工具包,也就是我们在写自定义LWC的时候,可以从哪些方面去保障可访问性。 首先,最基础的就是HTML属性,比如给输入框加个标签label,或者用aria-label给元素提供一个文字描述,这些是最常用的无障碍手段。 然后,更强大的一类是ARIA属性。在LWC里,我们可以直接在模板中用camelCase的方式写ARIA属性,比如ariaLabel,它会映射到标准的aria-label。而且还有默认的ARIA值和静态ARIA值的设定,能帮我们更精细地控制读屏软件的理解。 再往下,有个很实用的技术:在模板之间链接ID。因为我们LWC的Shadow DOM会隔离样式和DOM结构,所以如果两个不同的组件之间需要引用ID,我们可以用lwc:dom="manual"这种轻量DOM操作方式,让ID能够跨边界关联起来,保证无障碍的关联关系不会断掉。 焦点管理也是无障碍的核心。用tabindex控制键盘导航顺序,用delegatesFocus让组件能够把焦点委托给内部的第一个可聚焦元素,这些都能让键盘用户流畅操作。 另外,整个组件的生命周期我们也要心中有数,从constructor到errorCallback,每个钩子里都不能乱操作,否则可能破坏无障碍状态。比如在constructor里不要操作DOM,在renderedCallback里可以设置焦点,但要注意时机,避免重复渲染搞乱焦点。 最后是总的最佳实践:遵循WCAG指南,也就是网页内容无障碍指南的国际标准,同时利用Salesforce的SDS蓝图来设计界面,这样视觉上和无障碍实现上都能保持一致。 简单总结一下,就是基础组件已内置无障碍,自定义组件要自己动手,运用HTML属性、ARIA映射、ID链接、焦点管理,配合正确的生命周期写法,再参照WCAG和SDS,就能做出包容性很好的组件。 好了,这节就讲到这里,大家可以在练习里亲手试试给组件的ARIA属性和焦点,感受一下。
本课程共有 13 个章节
同学们,我们聊聊可访问性里最基础也最好理解的一点——正确的标签。 你看,在Lightning里,像lightning-input这种基础组件,它们很聪明。只要你设了label属性,组件就会自动通过for和id的关系,把标签和输入框关联起来。屏幕阅读器读的时候,就知道这个标签是描述下面那个输入框的,很方便。 但有时候我们可能觉得,哎,画面上不想显示标签,就用aria-label代替吧。这里一定要注意:aria-label这个属性,只有屏幕阅读器能读到,正常用眼睛看屏幕的用户是完全看不到的。所以如果那段文字对所有人都重要——比如“用户名”、“密码”,那你还是老老实实让它显示出来,别藏起来。 举个按钮的例子。有人写了个按钮,既写了label="Log In",又写了aria-label="Log In"。这aria-label就多余了,因为眼睛看到和耳朵听到的信息一模一样,不会带来任何额外好处。只有在一种情况下用aria-label才合理:那就是你需要给屏幕阅读器用户额外说明,或者画面上实在没法放可见标签的时候。比如一个只有图标的按钮,没有文字,那aria-label="关闭"就能告诉视障用户这个按钮是干什么的。记住,aria-label是补充,不是替代,别滥用。 好,这一点就跟大家交代清楚啦。
好,现在我们来看一个容易被忽视、但用不好就会出问题的点,是关于无障碍属性(比如 `aria-label`)和 `@api` 装饰器配合时的行为。 你可能会觉得:我用 `@api` 把一个属性标记成公开的,那它不就应该自动出现在最终渲染的 HTML 里了吗?其实不是这样。`@api` 只是让你的属性在 JavaScript 组件间可以传递、可以绑定,但它,不会自动反映到真实的 DOM 属性上,。也就是说,如果你在父组件里给子组件传了一个 `aria-label` 的值,在子组件里用 `@api` 接收了,你到页面上检查元素,很可能发现那个 `aria-label` 根本没出现在 `<button>` 或 `<div>` 上。 这对普通用户来说可能没感觉,但对使用屏幕阅读器的用户就截然不同了,因为屏幕阅读器只认 DOM 里的属性,它不会去读你 JavaScript 里的变量。所以如果你的无障碍标签没落到 DOM 上,辅助技术就完全“看不见”它。 解决这个问题的常见模式,我们会用到三个东西:一个,私有的内部字段,、一个 `@api` 的 ,getter, 和一个 `@api` 的 ,setter,。 举个例子,假设我们要对外暴露一个叫 `buttonLabel` 的属性,但在内部我们还想做一些处理,比如转换成大写,再赋给真正的按钮的 `aria-label`。那么可以这样做: - 先定义一个私有变量,比如 `_label`,来存储原始数据。 - 写一个 `@api` 的 getter,直接返回 `_label` 就好了,这样父组件可以通过模板绑定读取它。 - 关键在 setter 里:它不光要更新 `_label`,还要把经过转换后的值“推”到真实的 DOM 元素上去。比如我们可以这样写: ```javascript @api get buttonLabel() { return this._label; } set buttonLabel(value) { this._label = value; // 假设我们将标签转为大写并设置给某个按钮元素 const transformed = value.toUpperCase(); const button = this.template.querySelector('button'); if (button) { button.setAttribute('aria-label', transformed); } } ``` 你看,setter 里面我们主动地调用了 `setAttribute`,这样就把转换后的值手动注入到了 DOM 中。这时候屏幕阅读器就能发现这个 `aria-label`,并且正确播报给用户了。 注意,我这里用的是 `this.template.querySelector` 去拿具体的元素,然后调它的 `setAttribute`。也有的做法是通过模板表达式,比如直接在 `<button aria-label={computedLabel}>` 里绑定一个计算属性,然后把 `computedLabel` 暴露成 `@api`,那也行。但核心思想是一样的:,`@api` 只负责组件接口,DOM 上的属性需要你自己去维护,。 如果忘了这一步,只是简简单单写一个 `@api ariaLabel;`,然后什么都不做,那么父组件可以拿到这个属性的值,但 DOM 里就是空的,屏幕阅读器就抓瞎了。所以,每当你要暴露一个无障碍相关的属性时,一定要问自己:这个属性值最终落到 HTML 上了吗?如果没有,就得用上面那种 getter/setter 模式,或者模板里的响应式绑定,确保它反映到 DOM。 记住,框架给了你 `@api` 方便通信,但它不是一个“自动映射到属性”的魔法。尤其是在无障碍领域,多一步手动设置,就能让你的组件对所有人都友好。
同学们,现在我们来讲一下在Lightning Web Components里怎么用ARIA属性来让我们的组件对屏幕阅读器更友好。 ARIA其实就是给辅助技术提供更丰富的语义,让那些需要屏幕阅读器的用户能更好地理解页面上的东西。比如,一个切换按钮,你按一下开,再按一下关,aria-pressed这个属性就能告诉屏幕阅读器,当前这个按钮是按下的状态,还是没按下的状态。 那在我们的LWC组件里,这种ARIA属性是怎么流转的呢?通常的模式是这样的:父组件的模板会在自定义元素上直接设置ARIA属性,比如写aria-pressed="true"。然后,在子组件内部模板里,我们用驼峰命名的getter和setter来接收这个属性,就像ariaPressed,它代表的就是那个aria-pressed的HTML属性。然后在子组件的JavaScript里,我们把这个属性用@api暴露出去,这样父组件就能传递值进来了。 这里有个命名规则很重要,大家记住:在HTML模板和标签上,我们写的是短横线命名,也就是kebab-case,像aria-pressed、aria-labelledby这样。但一到了JavaScript代码里,我们就要换成驼峰命名,camelCase,比如ariaPressed,ariaLabelledby。这是LWC框架自动帮我们做的转换,保持一致就不会出错。 另外还要注意一点,模板里的id属性,LWC编译的时候会自动给它加上一些随机后缀,保证在整个页面运行时id是全局唯一的。所以,你千万别在CSS或者JavaScript里直接用id选择器去拿那个元素,因为编译后的id可能已经变了,你的选择器会失效。想要定位元素,最好用class类名,或者用data-*这种自定义属性。 最后,如果你想通过ARIA属性去关联同一个组件的不同元素,比如用aria-labelledby指向另一个标签的id,那你要确保这两个元素都在同一个shadow root下,也就是都在同一个组件的内部模板里,或者是light DOM里。这样ARIA关联才能正常工作。 好了,ARIA这部分就讲这么多,大家理解了吗?我们继续下一个。
我们来看一下这一页幻灯片,它讲的是如何为咱们的 Lightning Web 组件设置 ARIA 属性的默认值。 平时我们在写组件的时候,常常需要给像按钮、菜单这些元素加上 ARIA 属性,帮助屏幕阅读器理解我们的页面结构。有时候呢,我们作为组件的作者,知道最合理的默认值是什么,比如一个 tab 面板角色就该是 "tab",但我们又希望使用这个组件的人,在特殊情况下也能自己去修改它。 这时候,幻灯片告诉咱们一个很好的模式:在 `linkedCallback()` 这个生命周期勾子里去定义默认值,千万别在 `constructor` 构造函数里做。为什么呢?因为在构造函数执行的时候,组件里的各种元素可能还没准备好,你去设置属性可能会出错。而 `linkedCallback` 是在组件已经连上 DOM 了才调用,这个时候元素已经就绪了,设置默认值就安全了。 而且啊,消费者的显式赋值永远比我们的默认值优先级高。也就是说,如果使用组件的人明确写了一个属性值,那就听他的;如果他没写,咱们的默认值才会生效。这样的设计很灵活,对吧? 不过有一些属性是绝对不希望被人不小心改掉的,比如一个按钮的 `role` 属性,它就应该是 "button",要是被人改成了 "tab",那可就乱套了。对于这种永远不能变的静态值,幻灯片推荐用一个小技巧:写一个 setter 函数,但让它什么都不做,直接空着;然后 getter 函数永远返回那个硬编码的固定字符串。这样一来,当用户试图设置这个属性时,我们的空 setter 就把他给的值悄悄丢掉了,而外界读取这个属性时,永远是咱们设定的那个正确的值,这就保护了组件的语义完整性。 总结一下就是,普通的 ARIA 属性,在 `linkedCallback` 里给个默认值,让用户还能覆盖;对于那些必须保持不变的属性,就用空 setter 和硬编码 getter 来锁死,简单又安全。这样咱们写的组件既好用又健壮。 好了,这一页内容就讲到这里。
今天我们来聊一个在实际开发中经常会遇到的小问题,就是跨模板的ID和ARIA链接,在影子多姆里怎么处理。 你可以把影子多姆想象成一个独立的小宇宙,每个组件都有自己的影子树。在这个小宇宙里面,ID就像门牌号,是唯一的,组件内部用起来完全没问题。比如你在同一个模板里,给一个输入框设个 id="email",另一个标签用 aria-labelledby="email" 来关联它,这很正常,完全可行。 但是问题来了:如果这两个元素分别属于不同的组件,也就是在不同的模板里,它们就各住在自己的影子树里,门牌号都只在自己的小宇宙里有效。所以,你从组件 A 的影子树里想用 aria-labelledby 引用组件 B 里的 id,原生影子多姆会直接把它拦住,找不到对方。因为每个影子树都有自己的 ID 命名空间,它不允许跨过自己的边界去引用的。 那怎么解决呢?老师教你一个实用的办法:用轻型多姆。轻型多姆的意思就是,把这些需要互相引用的元素,放到同一个“阳光”下——让它们都成为同一个影子多姆容器的轻型子节点。这样一来,它们虽然来自不同组件,但共享同一个影子根,ID 就可以互相看见了。比如你把引用元素和被引用元素通过 slot 或者直接放在同一个容器的轻型多姆里,那么一个组件的 aria-labelledby 就可以顺利指向另一个组件的 id。 当然,如果你们团队的架构硬性要求只能用影子多姆,不想混合轻型多姆,那也有替代方案。要么把这些相关元素干脆放在同一个组件内部,别跨组件了;要么更灵活一点,通过 API 把 ARIA 的标签字符串传过去,而不是依赖跨组件的 ID 引用。这样就能绕开影子多姆的限制。 换句话说,就是遇到跨组件关联辅助功能属性时,优先考虑把元素放到同一个“光”下,或者用属性传递的方式来解决。这样既简单又符合标准。 好,我们总结一下要点:影子多姆让 ID 私有化,所以直接跨组件用 aria-labelledby 会失效;轻型多姆可以打破隔离,让不同组件的元素共享 ID 空间;或者把元素合并到同一组件,或者用 API 传标签字符串来代替跨组件引用。你记住了吗?
同学们,我们来看一下关于 Lightning Web Components 中键盘可访问性的一些关键知识。这对那些无法使用鼠标,必须靠键盘操作的用户来说,真的非常重要。 首先你们要记住,在普通的 HTML 里,那些交互式的元素,比如 ,a 标签、按钮、输入框、文本域,,它们天生就是能被键盘聚焦的。你按 Tab 键的时候,焦点会自动在这些元素间跳来跳去。 但是,非交互式的元素,比如一个用来展示文字的 div 或 span,默认是无法通过 Tab 键到达的。如果我们需要让用户也能用 Tab 键聚焦到这样的元素上,那就需要给它加一个 ,tabindex="0", 属性。这样它就会被加入到自然的 Tab 顺序里,和按钮、链接一样能被按顺序访问。 那么在 LWC 里有一点要特别注意,Lightning Web Components 只支持两种 tabindex 值:,0 和 -1,。 - ,0, 表示把这个元素放进自然 Tab 顺序里。 - ,-1, 表示这个元素只能通过程序方式来聚焦,比如用 JavaScript 调用 focus() 方法,但它不会出现在用户按 Tab 键的路径里。 我们不允许使用其他数字值,比如 tabindex="1" 这种来改变顺序,这在 LWC 里是不支持的。 接下来还有一个非常重要的默认行为,很多人一开始会搞错。就是,自定义组件的焦点是怎么处理的,。 你看,我们写的每个 Lightning Web 组件,在页面上最终会渲染成一个自定义的 HTML 标签,比如 `<c-my-component>`。当你在这个组件里面放了按钮、输入框这些原生可聚焦的元素,然后用户按 Tab 键时,焦点不会停在这个外层的 `<c-my-component>` 标签上,而是,直接跳过这个容器,进入到它里面的第一个可聚焦元素,。 也就是说,Tab 顺序会直接进入组件内部的按钮和输入框之间跳转,就好像组件的外壳根本不存在一样。这其实是我们通常想要的行为,因为组件本身只是一个语义上的包装,不是一个真实的界面控件,用户不需要在它上面停留。 如果你自己做过实验,会发现当焦点移入组件时,是直接亮在里面的第一个输入框上,而不是组件的边框。这就是 LWC 的默认焦点管理方式。 好,我们总结一下今天的关键点:交互元素天然可焦点,非交互的需要 tabindex="0";LWC 只允许 tabindex 值为 0 或 -1;最后,自定义组件的焦点会直接穿透到内部元素,不驻留在组件根节点。理解这些,对你构建无障碍的组件会非常有帮助。 有什么问题吗?我们接着看下一部分。
好,我们接着聊一下在 Lightning Web Components 里怎么管理自定义组件的焦点。这里有两种常用的模式,你一定要分清楚。 第一种,当你希望整个组件容器本身就能被聚焦,比如让用户在按 Tab 键时,焦点能落在一个大的区块上,并显示出焦点轮廓。这时候你可以在组件的宿主元素上加上 `tabindex='0'`。这种模式最合适那些整个组件本身就是一个交互实体的场景,比如说一张可点击的卡片。 第二种模式,是给那些包装了原生可聚焦元素的组件用的,比如自定义按钮。推荐的方法是给组件设置一个静态属性 `delegatesFocus` 并设为 `true`。这么一来,当你用 JavaScript 调用组件的 `.focus()` 方法时,焦点会自动落到影子 DOM 里第一个能聚焦的元素上。而且点击行为也会变得更智能:你在组件内部点击一个原本不能聚焦的区域,焦点也会自动移到第一个可聚焦元素上——这很像你点击 `<label>` 时,焦点跑到它关联的输入框里一样。还有,`:focus` 这个 CSS 伪类也会作用在宿主元素上,这样你就可以很方便地给整个自定义元素写焦点样式了。 最后要提醒一下,千万不要把 `tabindex` 和 `delegatesFocus` 混在一起用,那样会打乱正常的焦点顺序。所以,根据你的组件到底是一个整体交互容器,还是内部有原生控件,选对其中一种模式就好。
同学们,今天我们来看看LWC组件的,生命周期,,也就是从出生到销毁的整个过程。这事儿框架都帮我们管好了,不用咱们操心,但了解它的执行顺序特别重要,能避免很多坑。 组件生命周期有,五个关键钩子,,外加一个`render()`方法。我们主要看钩子的调用流程。 首先,创建阶段。,构造函数,(constructor)最先跑,而且是父组件先跑,然后才是子组件。接着组件要被插入页面DOM了,触发`connectedCallback`——注意,还是父组件先触发,子组件后触发。所以创建和连接,都是,父优先,的。 但是!渲染完成就反过来了。`renderedCallback`是,子组件先触发,父组件后触发,。因为得等子组件里东西都画完了,父组件才算真正渲染完毕。这个“反向”必须记住,不然调试时容易晕头转向。 再到删除环节,`disconnectedCallback`触发,顺序又回到,父优先,,父组件先断开,子组件后断开。 简单总结:,构造、连接、断开都是父优先,唯独渲染完成是子优先,。记住这个流向,代码逻辑就不会乱。 最后说一点,框架会一直盯着组件里的,反应式属性,。只要这些属性变了,就会自动触发重新渲染,不用我们手动调用任何方法。非常省心。 好了,这就是生命周期的核心要点,大家搞明白了吗?
同学们,我们来看看Lightning Web Components生命周期里的第一个钩子——,构造器,,也就是constructor。它是组件被创建时最先执行的方法,速度很快,但能做的事情非常有限。 为什么呢?因为构造器在组件真正挂载到DOM之前就已经触发了。也就是说,这个时候模板还没渲染,子元素还没生成,任何属性或公有属性也都没准备好,它们都还不存在。所以,在构造器里,你不能访问`this.template`,不能碰任何子元素,也不能读取传入的属性值。 接下来,要特别注意HTML自定义元素的规范要求。编写构造器时,,必须,先调用`super()`,而且不能带参数。这行代码必须写在第一句,它会先建立起组件的基础。另外,构造器里,不要,使用`return`语句,如果非要用,也只能是简单的早期返回,比如加个条件直接return,但正常情况下不要返回值。还有,绝不能在里面调用`document.write()`或者`document.open()`,这会破坏页面结构。 很多新手容易犯的一个错误,就是试图在构造器里给宿主元素(也就是这个自定义组件的外层元素)添加属性或CSS类。比如说,想用`this.classList.add('highlight')`来加样式,,这是不行的,。因为这个时候宿主元素还没有出现在DOM里,操作不了。正确的做法是把这些DOM相关的交互都放到`connectedCallback()`这个钩子里去做,这时组件已经挂载好,可以安全操作。 那构造器还能干什么呢?它的最佳用途就是,用默认值进行简单的字段初始化,。比如,在组件内部定义一个`count = 0`,或者`message = 'Hello'`之类的初始状态。这些纯数据的初始化不会触碰DOM,完全适合在构造器里完成。 总结一下:构造器最早触发,最受限。遵守规则——先呼`super()`,不碰DOM,不操作属性,只初始化自己的简单字段。其他任何需要“看得到”组件元素的代码,都请移到`connectedCallback`里写。 这样讲,大家清楚了吗?
同学们,今天咱们来聊聊 Lightning Web Components 里两个非常重要的生命周期钩子:connectedCallback 和 disconnectedCallback。 你可以把 connectedCallback 理解为组件“登场”的时刻——只要组件被插入到 DOM 树上,这个钩子就会自动触发。 这个时候,你可以安全地访问 this.template(也就是组件的模板)和 this(组件自身的主机元素)。 所以咱们通常在这里做初始化工作,比如去服务器拿数据、设置一些缓存、绑定事件监听、订阅消息频道,甚至是做页面导航,这些都是很合适的。 不过要注意两个特别重要的点: 第一,connectedCallback 可不是只触发一次。如果组件从 DOM 上被移走,后来又插回来,比如用 v-if 之类的条件渲染,或者被其他操作移动了位置,它就会再次触发。所以如果你有一些真的只想跑一次的操作,就得用个保护标志自己记住,避免重复执行。 第二,在 connectedCallback 执行的时候,子组件的元素你还拿不到——因为这个时候它们自己还没完成连接呢。想操作子元素,得换个时机。 说完登场,再说退场。disconnectedCallback 就正好对应“离开 DOM”的时刻。 这时候应该做一些清理工作,比如把你之前设的缓存清掉、事件监听解绑、消息频道取消订阅,避免内存泄漏或者意外行为。 另外,有一个常见的坑,一定要记住:千万不要把 connectedCallback 和 disconnectedCallback 标记成 async。 如果你加了 async,框架可不会等你,它直接就往下走了,这会让整个执行顺序变得混乱,很难调试。 那如果确实里面有异步操作怎么办?可以调用一个单独的异步辅助方法,在方法内部处理好,但钩子本身保持同步。 最后提一句,因为属性的赋值是发生在构造函数和 connectedCallback 之间的,所以在 connectedCallback 里,组件的公开属性(用 @api 装饰的那些)已经是可用的状态了。 好,这节课就这么两点一坑,把握好了,组件的初始化与清理就会很顺畅。咱们下次见。
同学们,今天我们来讲一个LWC里非常关键的生命周期钩子,叫 ,renderedCallback,。这个名字你可能觉得有点长,但其实理解起来很简单。 首先,它是 LWC 独有的一个“渲染后回调”。通俗点说就是:每次你的组件在页面上完成渲染,或者因为数据变化而重新渲染之后,这个 rendereCallback 就会自动被触发。注意是“每次”,包括第一次显示,也包括后面任何一次重绘。 那它的执行顺序有什么讲究呢?记住一个规律:,它从孩子流向父母,。也就是说,如果一个父组件里包含了子组件,那么一定是子组件先把自己的渲染做完,执行完自己的 rendereCallback,然后父组件的 rendereCallback 才会执行。孩子完成了,父母才收尾。 在这里,LWC 的引擎还做了一件很聪明的事。它使用了一种,差异算法,来尽量少地改动页面上的真实 DOM。比如你用 `for:each` 渲染一个列表,引擎会根据你给每个元素设置的 `key` 来跟踪它们,只更新真正变化的那几项。还会对插槽(slot)的内容进行智能比较,而不是粗暴地全部重画。这就是为了保证性能。 正因为 rendereCallback 每次渲染都会触发,如果你只想在,第一次渲染,时做某件事,比如初始化一个图表库,或者手动绑定事件,那就得加一道“防护锁”。最常用的模式是定义一个私有变量,比如 `hasRendered`,初始值是 `false`。在 rendereCallback 里先判断它:如果是 `false`,说明是第一次,那就执行一次性操作,然后马上把 `hasRendered` 设为 `true`。这样后面组件重新渲染时,就会跳过那段逻辑。这个就叫 ,hasRendered 防抖模式,,非常重要。 那在这个钩子里,适合放什么代码呢?官方指出,如果你想,手动通过 `addEventListener` 添加事件监听器,,可以放在里面。但一定要记得,,更推荐直接写在 HTML 模板上,,用声明式的 `onclick={handleClick}`。声明式更简洁、更安全,也是首选。 接下来,我要讲一个最最关键的警告,这也是新手最容易掉进去的坑:,绝对不要在 rendereCallback 里面修改任何响应式状态,。什么叫响应式状态?就是你组件里定义的数据字段,比如 `name`、`count`,或者用 `@api` 标记的公共属性,只要这些值的变化会触发模板重新渲染,就统统不能改。你一旦在
同学们,今天我们来看一个 LWC 里非常强大,但其实你很少会用到的功能——用 `render()` 方法实现动态模板。 首先记住一点:`render()` 它不是一个生命周期钩子,而是一个受保护的方法,必须定义在组件的原型链上,而且你的 JavaScript 文件里要显式地写出来。 那它能干什么呢?简单来说就是——“按需换脸”。你可以为同一个组件创建多个 HTML 模板,然后用 `import` 把它们引进来,再在 `render()` 方法里根据组件的当前状态,决定返回哪一个模板。每一个额外的模板都可以配一个同名的 CSS 文件,样式也会跟着一起切换。 听起来挺酷的对吧?但 Salesforce 官方其实建议,绝大多数情况下,你直接用 `lwc:if`、`lwc:elseif` 这些条件指令在一个模板里处理就够了。什么时候才需要多个模板呢?就是当不同状态需要完全不同、几乎没啥共同点的标记结构时。比如,一个组件在“浏览模式”和“编辑模式”下,整个 DOM 结构天差地别,这时候用 `render()` 返回不同模板,会让代码更干净。你也可以把它理解成其它框架里的代码拆分,只是换了一种方式实现。 所以记住:不是万不得已,别一上来就用多模板。先把简单的条件渲染用好,等真正碰到结构都完全不同的需求时,再拿出这个“大招”,让你的组件变得又清晰、又好维护。
同学,咱们今天来看一个很重要的概念:errorCallback。你可以把它想象成组件世界里的一张“安全网”,专门用来优雅地处理那些没有被捕获的错误。 为什么需要它呢?想想看,当你的组件树很深时,某个角落里的一个小错误如果不处理,可能会导致整个应用崩溃,用户体验非常糟糕。这时,errorCallback 就派上用场了。它实现了一种叫做“错误边界”的模式,原理特别像 JavaScript 里的 catch 块。但它的能力更强——它不仅仅捕获它直属子组件的错误,而是能捕获到它下面所有后代组件里抛出的、未被处理的错误。一旦后代里出错了,这个错误就会一直往上冒泡,直到被离它最近的 errorCallback 这张网接住。 那具体怎么用呢?errorCallback 是一个生命周期钩子,它接收两个参数:一个是原生的 error 对象,包含了错误的描述信息;另一个是 stack 字符串,显示了调用堆栈,帮你定位问题。典型的使用模式,是用一个标记来控制界面切换,比如我们常用 lwc:if 和 lwc:else 指令。平时页面正常显示,一旦捕获到错误,我们就更新一个状态变量,然后卸载掉出问题的组件,转而显示一个对用户友好的错误提示界面。这样,其他健康的部分还能正常工作,崩溃就被控制在局部了。 而且这个错误边界你可以按需放置。比如说,你可以把整个应用包在一个大的边界里,就像给整个房子铺了张安全网,任何地方掉下来都能接住;你也可以只包裹一部分核心或容易出错的区块,做局部的错误隔离。这给了你非常大的灵活性。 不过,有一个关键限制你一定要记住:errorCallback 它只捕获得那种在模板里直接绑定的事件抛出的错误,比如说你在 HTML 里写了个 onclick={handleClick},如果 handleClick 内部报错了,errorCallback 可以抓到。但如果你是通过 addEventListener 这种编程方式动态添加的事件处理器里出错了,errorCallback 是抓不到的。所以别把所有的错误处理都指望它,自己代码里该 try-catch 的地方还是要做好防护。 好了,关于 errorCallback 的使用和限制就讲到这里,下节我们会动手演示一下,让你更直观地感受它的作用。咱们下节见!