Fields, Properties, and Attributes
同学们好,今天这一章,我们来系统梳理一下Lightning Web Components的核心概念——性质和反应性系统。别被这些术语吓到,其实不难,我们一步步看。 首先,咱们搞清楚三个容易混淆的词:字段、属性和属性。在LWC里,字段指的是组件类里面定义的那些变量,普通的不带装饰器的;属性呢,通常指的是用@api标记的公开属性,用来接收父组件传值;而HTML中的那些属性,比如`class`、`id`,是DOM层面的东西。简单说,字段是内部私有的,属性是公开接口,HTML属性是元素的外衣。 接下来是反应性,这是LWC的魔法核心。框架会观察字段的变化,一旦值变了,界面就自动重新渲染。这就像你厨房的灯,开关一按,灯立马亮——响应式地反映状态。默认下,对字段的简单赋值会触发这个机制,但对象或数组的深层变化呢?就需要@track来帮忙了,它能让框架深入观察,好比放大镜,看透对象内部的变动。 说到公开属性,@api是关键。用它装饰的属性,父组件就能传递数据进来。比如你在JS里定义一个`@api message`,HTML里就用kebab-case写成`<c-child message="你好">`。注意命名约定:JS用驼峰式,HTML用连字符分隔,千万别搞混了。 DOM属性访问也很实用。通过`this.template.querySelector`,你就能拿到模板里的元素,直接操作它,就像指点房间里的家具一样方便。 再记住,所有组件都继承自LightningElement基类。它不仅提供生命周期的钩子,还有一堆特有属性,像`this.label`,这些是LWC贴心地帮你准备好的。 还有反射的Web API模式,就是把组件属性同步到HTML元素上,比如为了无障碍访问,你把`ariaLabel`属性反射出去,屏幕阅读器就能识别。布尔属性也类似,像`checked`,直接切换,简洁高效。 getter和setter是数据转换的利器。你可以在设置值时做验证,或者在获取时格式化,比如把时间戳转成可读日期。这样数据进出都有把关。 最后,千万别忽略属性之间的依赖关系。一个属性变化可能影响另一个,用getter或在回调里手动更新,确保逻辑不乱。 这一章的内容虽然多,但都是实战必备。记住这些模式,你的组件设计会又灵活又可靠。好了,咱们下节课再深入具体案例,谢谢大家。
本课程共有 41 个章节
同学们好,今天我们来聊聊 LWC 里几个特别基础,但又很容易混淆的概念——字段、属性,还有它们背后的反应性系统。咱们慢慢来,把它讲透。 首先,假设你正在写一个 Lightning Web 组件,需要在画面上动态显示一些文字,比如一段欢迎语。这时候你会在组件的 JavaScript 类里面声明一个变量,我们管它叫“字段”,比如 `message = 'Hello'`。然后在模板里用花括号 `{message}` 引用它,页面就会显示“Hello”。当你用代码修改 `this.message = 'Hi'` 时,神奇的事情发生了——画面自动更新成“Hi”。这就是 LWC 的反应性系统在工作,它会追踪字段的值,一旦变化就去更新界面。 那么,“字段”和“属性”又是什么关系呢?从写组件的人(组件作者)的角度看,你在类里声明的变量就叫字段。但这个类的实例一旦生成,实例上的这些变量就变成了属性。对于使用你组件的人(消费者)来说,他们只看得见那些被你用 `@api` 装饰过的字段,这些字段摇身一变成了公开的“属性”,允许父组件传值或者外部读取。可以这么说:内部声明叫字段,对外暴露叫属性。只有加了 `@api` 的字段,才会作为对象的属性提供给消费者使用,组件内部没加 `@api` 的字段是私有的,外面碰不到。 记住,反应性系统不光看公开属性,内部字段的值变了,它也会重新渲染模板,让组件始终保持最新的样子。 接下来还要澄清一对容易犯晕的概念——HTML 里的“属性”和 JavaScript 里的“属性”。你可能会两处都听到“属性”这两个字,但其实在英文里它们分别是 attribute 和 property。举个简单的例子,一个 `<div class="box">`,这里面的 `class` 就是 HTML attribute;而用 JavaScript 拿到这个 div 后,你通过 `div.className` 去访问,这就是在操作 property。在 LWC 中,组件会自动把很多标准的 JavaScript property 映射(或者说“反映”)成 HTML attribute,比如你设置了 `this.tabIndex = 0`,渲染出来的标签上就会出现 `tabindex="0"`,这对无障碍访问特别重要。而且,对于你用 `@api` 公开的 JavaScript 属性,还可以通过一些配置来控制它到底要不要表现为 HTML 里的 attribute,比如有些纯逻辑属性不需要出现在生成的 HTML 上,就可以关闭这个映射。 总之,这节课我们了解了三个点:第一,在类里声明字段,模板用花括号绑定,反应性系统自动更新;第二,用 `@api` 装饰才能让字段变成公开属性给外界用;第三,JavaScript property 和 HTML attribute 虽然有区别但往往互相反映,为了让屏幕阅读器等辅助技术好的,我们可以控制这个行为。 好了,今天就到这儿,下节课我们会接着深入,看看 LWC 怎么优雅地处理事件和数据流。
今天我们来聊聊 Lightning Web Components 里一个特别核心的概念:反应性。 你可以把反应性想象成组件里会“盯着”数据变化的智能引擎。当你改了一个属性或者一个字段的值,框架就会马上察觉到,然后自动把模板里用到这个值的地方都重新计算一遍,并且把组件的界面重新渲染成最新的样子。整个过程根本不需要你手动去更新页面。 默认情况下,组件模板里直接绑定的公共属性,就已经是反应性的了。也就是说,只要这个属性的值变了,并且它在模板里,或者在某个 getter 方法里被用到,组件就会自动刷新。 不过呢,针对对象和数组,框架默认只能发现一些表面的变动,比如你直接把整个对象换成了一个新的。但如果你只是修改了对象里的某个嵌套属性,或者数组里某个元素,框架可能就不会自动响应。那怎么办呢?这时候,我们就需要请出一个装饰器——`@track`。给属性加上`@track`,框架就能深度观察它的内部变化,哪怕你只改了嵌套对象里的一个小字段,组件也会聪明地重新渲染。 简单总结一下:公共属性默认就带一点反应性,可要想让对象和数组里的嵌套数据也引发界面更新,记得用`@track`来标记它。这样就掌握了 LWC 里让界面动起来的基础驱动力。
同学们,今天我们来聊聊Lightning Web Components里一个非常重要的知识点——@api装饰器。 你可以把它想象成组件对外开放的大门。当你用@api标记一个字段时,就相当于告诉这个组件:“嘿,这个属性,外面的父组件可以随意读取和修改它。” 这样,父组件就能通过它向子组件传递数据,或者读取子组件的状态,这就是组件的公共API。 这里有一个特别重要的特性:如果这些公开属性被用在了组件的模板里,它们就是反应式的。也就是说,一旦父组件改变了这个属性的值,子组件就会自动重新渲染。重新渲染的时候,会发生两件事:一是模板里所有用到这个属性的表达式都会用新值重新计算,二是组件的`renderedCallback()`这个生命周期方法会被调用。简单来说,改属性→界面自动更新,很省心。 大家要注意,@api装饰器是Lightning Web Components独有的,它不是JavaScript原生就有的装饰器。而且,同一个字段上只能放一个装饰器。比如你不能在一个属性上同时用@api和@track,这样会冲突。 最后,这个属性可以是你自己定义的,也可以是从标准HTML元素接口继承过来的,比如`title`或`className`。这样就给了你很大的灵活性。 好了,@api装饰器的核心就是这些:它是组件的公共大门,父组件可以读写,用在模板里就能自动更新界面,还是LWC特有的。记住这些,你就能很好地控制组件间的沟通了。
同学们,今天我们来聊聊怎么在Lightning Web Components里创建自定义的公共属性,也就是让父组件能传数据给子组件。 首先,我们要从lwc模块里导入一个叫api的装饰器。这个装饰器就像是一个开关,能把类里面的字段暴露出去。比如我们有个待办事项组件,想让父组件传一个事项名称进来,我们就可以在JavaScript里声明一个itemName属性,用@api来装饰它,还给它一个默认值,比如“新项目”。这样,如果父组件不传值,它就会显示默认的“新项目”。 然后在模板里,直接用{itemName}这个表达式显示出来就行了,很简单。 接下来是命名规范,这个点特别重要,但也容易混淆。在JavaScript里,属性名我们习惯用驼峰写法,比如itemName,单词首字母大写。但在HTML模板里,属性名必须用短横线分隔,也就是kebab-case,写成item-name。这是为了遵守HTML标准。你可能问为什么?因为浏览器只认全小写加连字符的属性,Salesforce框架会自动把kebab-case的属性名转换成camelCase的JavaScript属性。所以父组件用的时候,要写item-name,框架会聪明地把它映射到itemName上。 举个例子,父组件可能是这样写的:c-todo-item,然后给它一个属性item-name="买牛奶"。这时框架就会把“买牛奶”这个值传给子组件的itemName属性,实现数据传递。 这里要记住,为了演示清晰,咱们这个例子用了静态值。但实际项目里,你不太会传死值,通常是用for:each指令循环一个动态数组,比如一堆待办事项,每个事项都是不同的值,这样才能真正发挥组件的复用性。 好,这节课的核心就是:导入api,用@api公开属性,注意代码里的驼峰和模板里的短横线转换,父组件用kebab-case传值。掌握了这个,子组件和父组件就能顺畅地“对话”了。
来来来,咱们聊一下 DOM 属性访问,这是一个非常实用的技巧。简单说,,DOM 属性就是给你的组件开了一扇编程访问的窗口,,让你能在父组件里直接用 JavaScript 去读写子组件对外暴露的数据。 比如,你在子组件类里写 `@api itemName;`,这个 `itemName` 就会直接变成子组件元素上的一个公开属性,就是所谓的 DOM 属性。然后,父组件只要拿到子组件的元素引用,就能通过,点号,直接操作它了。 那怎么拿到引用呢?典型做法是 `this.template.querySelector('c-child')`,这样就能获取到子组件实例。接着,你就可以写 `childElement.itemName = '新值'` 来修改,或者用 `console.log(childElement.itemName)` 来读取。这种读写访问,完全是在代码里编程控制的。 你把这种编程方式,和直接在模板里写 `<c-child item-name={someValue}></c-child>` 这种声明式的绑定放在一起看,其实它们是互补的。声明绑定适合在 HTML 里直接传值,而 DOM 属性访问则适合需要在代码里动态判断、再手动干预的场景。 如果你想去看看完整的运行效果,可以翻翻 `lwc-recipes` 仓库里的 `compositionBasics` 例子,它把整个模式都演示得很清楚。这样理解起来就非常直观了。
同学们,今天我们来聊一聊 Lightning Web Components 里一个非常核心的概念——反应性。简单说,反应性就是让界面上的数据跟 JavaScript 变量保持同步,这样一来,你改了变量,界面就自动更新,不需要手动去操作 DOM。 那 LWC 是怎么知道数据变了呢?它默认用的是“浅层”跟踪。什么意思?就是它只检查你在给一个字段赋值的时候,用等号去比较新旧值是不是同一个引用。 这就带来一个问题。对于数字、字符串这种“原始值”,你每次给它们赋一个新值,自然就创建了一个新引用,所以 LWC 能检测到变化,界面就刷新。 但对象和数组就不是这样了。比如你有一个对象 `person = {name: '张三'}`,如果你只改里面的属性,比如 `person.name = '李四'`,对象本身的引用没变,LWC 就察觉不到,界面就不会更新。同样,往数组里 push 一个新元素,数组引用也没变,界面也不会动。 那怎么办呢?老师给你两个办法。 第一种,最简单粗暴:你直接创建一个全新的对象或数组,然后赋值给这个字段。比如 `person = {...person, name: '李四'}`,或者 `arr = [...arr, newItem]`。这样就改变了引用,LWC 就能捕获到。 第二种,你也可以用 `@track` 装饰器。把它加在属性前面,就告诉 LWC:“这个属性深层次的内部变化,我也要跟踪。” 这样你直接修改 `person.name` 或者 `arr.push()`,界面都能跟着更新。不过要注意,`@track` 只跟踪一层内部属性,更深层的嵌套它不是完全递归观察的,但它会帮你跟踪普通对象和数组的内部变化。 好了,记住这两点,你就能轻松驾驭 LWC 的数据响应了。
好,我们来看这个例子,它非常清楚地展示了浅层反应性和深层反应性到底差在哪里。 在标准的、默认的浅层跟踪机制下,框架只检查属性本身有没有被替换掉。如果只是修改了对象里面的某个字段,它是“看不见”的。 你看,这里第一行,把 `bool` 改成 `true` —— 这是一个全新的原始值,所以被检测到了。 第二行,把 `number` 又赋值为 `42`,因为跟之前的值完全相等,比较下来没变化,所以没有触发任何更新。 第三行,改为 `43`,值不同了,立刻被检测到。 最关键的是对象的情况。 当我们写 `this.obj.name = 'Bob'` 的时候,实际上只改了对象里面的一个嵌套属性,`obj` 这个引用本身还是原来的那个对象,因此浅层检查认为“没变”,没有反应。 可是,一旦我们给它换成一个全新的对象,比如 `this.obj = { Name: 'John' }`,哪怕内容看起来一样,因为引用变了,框架立即就能发现。 后面这个 `this.obj = { ...this.obj, title: "CEO" }` 也是同样的道理,展开运算符会创建一个全新的对象,所以同样会触发反应。 那如果我们确实希望只改嵌套字段、又不想反复创建新对象的时候,该怎么办呢? 这就需要用到 `@track` 装饰器了。给属性加上 `@track`,就可以启用深层跟踪,让框架能够注意到对象内部的变化,而不用你每次手动复制出一个新对象。 这样是不是一下就清晰多了?浅层默认看“外壳”变没变,而 `@track` 能让它“看进里面去”。
同学们,我们来看看这个@track装饰器,它能帮我们深入观察数据变化。 想象一下,你有一个普通的JavaScript对象,比如用花括号`{}`创建的,或者一个数组用方括号`[]`创建的。当你用@track修饰这个属性时,Lightning Web Components会仔细观察这些对象和数组内部的值,哪怕是很深层次的嵌套。 比如说你有一个对象,里面又套了数组,数组里再套对象……@track都能追着每一个变化走,循环嵌套、互相关联的复杂结构它都能正确处理,甚至对象自己引用自己这种循环引用也不怕。 但这里有个关键点,@track它“只看得懂”这些普通的对象和数组。如果你用的是自定义类的实例、Date日期、Set或者Map这些复杂类型,它就没法自动感知到内部属性的改动了。 举个例子,你有一个属性是`new Date()`,然后你在后面调用了`setDate`之类的方法修改它内部的值,@track是察觉不到的,页面不会自动刷新。那怎么办呢?你需要重新创建一个新的Date实例,替换掉整个属性值,才能触发界面更新。 所以记住,@track的深度观察只对普通的JavaScript对象和数组生效,其他复杂结构需要你手动整个替换。这样画面才会随之刷新。 好了,这一讲就到这里。大家理解了吗?我们下次继续。
同学,今天咱们来聊聊LWC里一个挺重要的装饰器——@track。你可能已经知道,在组件里我们定义属性来绑定数据,比如这里有个`fullName`对象,里面包含了`firstName`和`lastName`。 那么问题来了:如果我直接修改`this.fullName.firstName = 'John'`,页面会自动更新吗?答案是不会,除非你用了`@track`。为什么?因为框架默认只观察属性本身的引用有没有变。你给它赋个新对象,引用变了,它会知道。但你只是修改了它里面的某个值,引用没变,框架就无感,所以不会重新渲染。 这时候`@track`就派上用场了。你只要在属性前面加上`@track`,比如`@track fullName = {...}`,那框架就会深入进去,观察这个对象内部属性的变化。一旦你改了`this.fullName.firstName`,它立刻就能检测到,组件也会随之刷新。 同样的规则也适用于数组。比如你有一个数组,你用`push`往里加东西,引用没变,不加`@track`就不会更新;加了`@track`,框架就能捕捉到这些内部变化。 所以记住这个简单的规则:如果你的属性是对象或数组,并且你需要追踪它内部的改动来驱动视图更新,那就一定要用`@track`注解它。这样你的组件才能如你所愿地响应数据变化。 好了,这一part先到这里,有什么问题随时提。
同学们,今天我们来聊聊 Lightning Web Components 里一个很有意思的优化机制,叫“选择性跟踪”。即使你在属性上加了 `@track`,框架也不是傻傻地、一有风吹草动就重新渲染,而是非常聪明地记住了:上一次渲染的时候,到底用了哪些具体的属性。 举个简单的例子,假设我们的组件里有一个对象 `obj`,只有两个字段,`value1` 和 `value2`。在第一次渲染时,我们的模板里只用了 `obj.value1`,根本没碰 `value2`。这时候,LWC 的框架就会记录下:“嗯,这次渲染用到的关键数据是 `value1`。” 好,那接下来如果我们在 JavaScript 里,只把 `obj.value1` 改成另一个值,框架一比对,发现记录里的 `value1` 变了,那自然而然就会触发重新渲染,界面更新。 但是,如果我们只是给 `obj` 新增一个属性 `value2`,或者修改 `value2` 的值,而不去动 `value1`,那框架就会想:“你这次改的 `value2`,上次我压根儿没用到它来画界面啊,改它不会影响已经显示的内容,那我就不用费力气重新渲染了。” 所以,这种情况下,界面是不会更新的。 那假如我们确实需要两个值都生效,让界面重新渲染呢?这时候,就不能只新增属性,而是要直接给它一个全新的对象,里面同时包含新的 `value1` 和 `value2`。比如 `obj = { value1: newValue1, value2: newValue2 }` 这样整体替换。这样一来,框架检测到 `obj` 本身的引用变了,而且新对象里的 `value1` 也变了,就会妥妥地触发重新渲染。 这样做的好处是什么呢?就是防止不必要的重新渲染,大大提高我们组件的性能。框架只关心真正影响界面的数据变化,这就是 LWC 选择性跟踪的聪明之处。
同学们,我们来说说在 Lightning Web Components 里一个非常经典的小陷阱,以及它干干净净的修复方法。 你也许已经发现,如果你用 `@track` 标记了一个对象,比如 `this.obj = { name: 'Alice' }`,然后你直接在模板里展示它的属性,一切都很正常。但假如你突然想加一个新的属性,比如 `this.obj.age = 30`,你会发现:咦,界面怎么纹丝不动?这是因为 LWC 的变更检测,对于对象的内部属性变更,只会跟踪那些在渲染时已经被读取过的已知属性。你凭空塞进去一个新属性,框架根本不知道这件事发生了,自然不会去重新渲染。 那怎么解决呢?方法特别简单,而且是一种最佳实践:,不要直接在原对象上添加新属性,而是用一个全新的对象来替换它。, 具体怎么做呢?用 JavaScript 的,扩展运算符,,也就是那三个点 `...`。比如你原本的 `this.obj` 里有 `name`,现在想加个 `age`,你可以这样写: ```javascript this.obj = { ...this.obj, age: 30 }; ``` 我来一句一句解释给你听。`...this.obj` 会把原对象里所有可枚举的自有属性,一个一个复制到新对象的花括号里。然后后面紧跟着 `age: 30`,这就好比在新对象上又添加或者覆盖了这个 `age` 属性。最终,你得到了一个内容上包含了老属性加上新属性的对象,但是它是一个,全新的对象引用,。 这里最关键的就是“新的引用”。因为 `this.obj` 现在指向了一块全新的内存地址,LWC 框架会立刻检测到这个字段级别的变化。然后,它会重新计算所有依赖于 `this.obj` 的表达式,包括那些 `getter` 函数,比如你可能会用到的 `Object.entries` 去遍历它。现在 `getter` 重新运行,自然就能看到新加的属性了。 这种写法妙在哪里呢?它给了你两全其美的效果。平时你修改对象已有的属性值,比如 `this.obj.name = 'Bob'`,框架能够精准追踪,性能很高;一旦你需要改变对象的“形状”——也就是添加或者删除属性——你就用扩展运算符创建一个新对象,强制触发一次全面的重新评估。这样既保证了日常操作的轻快流畅,又有了应对结构变化时的清晰可控,代码还特别简洁。 所以记住这个模式:在 LWC 中,要让对象结构变化能被追踪到,就用 `this.obj = { ...this.obj, 新属性: 值 }` 这种不可变更新的方式。这会让你的组件反应更可靠,调试起来也省心得多。好了,这就是今天这个小技巧的全部内容,你学会了吗?
好,我们接着看数组在Lightning Web Components里的反应机制。它跟对象的逻辑基本一致,但你得注意几个关键点。 首先,如果你没用 `@track` 装饰器,只有当你把一个全新的数组赋值给属性时,框架才会触发重新渲染。比如直接写 `this.arr = [1, 2, 3]`,就会更新视图。但是,如果你通过索引去改某个元素,像 `this.arr[0] = 'x'`,或者用 `push` 这类会改变原数组的方法,框架是察觉不到的,因为数组的引用没变,所以页面不会刷新。 那加上 `@track` 呢?这时候框架就会观察数组内部元素的变化了。你直接给某个索引赋值,或者用 `push`、`pop` 这些变异方法,它都能检测到,并安排重新渲染。 不过这里有个容易忽略的小细节:即使 `@track` 能检测到单个元素变了,框架并不会自动把数组转换成字符串来显示。假如你在模板里直接写 `{myArray}`,它显示的可能是 `[object Array]`,而不是内容。所以,你通常得写一个 getter,比如叫 `formattedArray`,在里面调用数组的 `join` 方法,或者其他序列化方式,然后把 getter 用在模板里。这样的话,当 `@track` 发现元素有改动,就会重新计算 getter,模板就会显示出更新后的字符串。 总结一下:用 `@track` 可以追踪数组内部变化,但要正确显示内容,记得用 getter 做序列化。不用 `@track` 就只认整个数组的替换。这样讲清楚了吗?
同学们,咱们今天聊聊Lightning Web Components里一个比较特别的细节,就是关于Date、Set和Map这种复杂对象的响应式追踪问题。 你们可能已经知道,在LWC里,用@track装饰符可以让LWC引擎追踪一个属性的变化,只要属性值变了,组件就会自动重新渲染。但这里有个特例,即使你写了@track,如果这个属性的值是Date、Set或者Map这些复杂对象,引擎是不会观察到它们内部状态变化的。为什么呢?因为它们不是普普通通的JavaScript对象,它们是继承了自己特有的原型的,像是Date.prototype、Set.prototype这些。引擎的观察机制只关注属性引用的变化,它并不深入对象内部去监视。 咱们用个具体点的例子来说明。假设咱们模板里有一个属性叫x,它的值是一个Date对象。页面上有两个按钮,一个Init,一个Update。当你点Init按钮的时候,代码里会新建一个Date对象,然后把它赋值给this.x。这时候x的引用变了,从旧对象变成了全新的对象,所以LWC能检测到这个变化,重新渲染就发生了。 但当你点击Update按钮的时候,代码可能写的是this.x.setHours(7),直接调用了Date对象自己的方法,去修改它的内部小时数。注意,这个操作只改变了对象内部的状态,x变量指向的还是原来那个Date对象,引用根本没变。LWC引擎没有它内部改动的通知,所以就无动于衷,不会重新渲染。你可能觉得自己改了时间,页面上怎么没反应呢? 这时候,如果你打开浏览器的开发者工具控制台,它其实会给出一个挺友好的警告。大概内容是,属性x被设置成了一个“不可追踪的对象”,意思就是LWC没法看到这个对象内部的变化。然后它会建议你,在对对象做内部修改之前,先克隆一下这个对象。 所以,碰到这种需求,咱们该怎么做呢?解决思路很简单,就是不要直接在原对象上动手脚。你可以在修改前,先克隆一份,比如用new Date(this.x.getTime())来创建一个新的Date对象,然后在这个新对象上做setHours(7)之类的操作,最后把新对象赋给this.x。这样引用就变了,引擎就能捕捉到,重新渲染就会触发。对于Set和Map也是类似的道理,先创建新的集合,操作完再赋值。 总结一下:@track是监视属性引用的,Date、Set、Map这些对象内部变化它看不见。要让它生效,你就得通过改变对象的引用来触发,最常用的办法就是先克隆,再修改,再赋值。 好了,这个小知识点就讲到这里,希望你们以后写代码时遇到这种“改了没反应”的情况,能第一时间想到是这个原因。
今天我们来看一个在LWC里非常重要的模式,叫克隆模式。你可能会问,为什么我们需要它呢?简单说,它就是用来强制让复杂的对象重新渲染的通用办法。 我们先理解一下本质的问题。LWC是怎么知道数据变了,需要更新界面呢?它是通过“引用比较”来做的,也就是说,它看的是内存地址有没有变,相当于用三个等号来对比。所以,就算你把对象或数组里面的值改了,如果内存引用还是原来那个,LWC就认为没变化,画面就不更新。 那怎么办呢?关键就是:我们必须改变引用。克隆模式就是围绕这个想法来的,做起来其实就三步,跟切菜一样简单。第一步,把现有对象克隆出一份全新的;第二步,在这个克隆出来的副本上随便修改;第三步,把修改好的副本赋值回原来的字段。这样子,引用就变了,LWC马上就知道要去重新渲染了。 举个具体的例子,比如说你有一个日期类型的数据。如果你直接改它的内部字段,LWC是察觉不到的。正确做法是,像这样写:new Date(this.x.getTime()) ,这样就拿到一个全新的日期对象,值一样但引用不同。对于集合类型,Set就写 new Set(oldSet) ,Map就写 new Map(oldMap)。数组呢,用扩展运算符非常方便,就像这样: [...原数组, 新元素] ,这会产生一个新数组。普通对象也一样,用大括号扩展,像这样: { ...旧对象, 新属性 } 。这样每次赋值都是全新的引用。 有一个常见的疑惑:我有没有用@track?其实这个模式,不管你有没有标@track都有效。因为默认的浅反应性,永远能检测到引用的变化。所以哪怕你不用装饰器,只要按克隆模式改了引用,刷新就肯定会发生。 最后给你个小技巧:浏览器控制台其实是你的好朋友。如果你运行的时候看到它警告说这是个“不可跟踪对象”,那意思就是,你现在正试图直接观察一个复杂对象的内部变化,而LWC做不到。这时候,就该立刻想起咱们今天讲的克隆模式,用三步走,问题就迎刃而解了。
同学们,我们来看一个非常实用的例子,叫 helloExpressions。这个组件把咱们之前学到的反应性、数据绑定和 getter 巧妙地结合在了一起。 想象一下,页面上有两个很简单的输入框,分别让用户填名字和姓氏。这两个输入框,用的都是 Lightning 的闪电输入组件,而且它们都通过 onchange 事件,也就是每次内容变化时,同时触发同一个叫 deliverChange 的处理函数。 当用户打字的时候,底层的数据就会被更新。然后,在显示区域,我们不是直接显示数据,而是通过一个叫 uppercasedFullName 的 getter,把名字和姓氏拼在一起,并且全部转成大写字母再展示出来。为了让布局好看一点,我们还用了 Salesforce Lightning Design System 里的工具类,加了一点间距。 这里最关键的就是反应性系统在暗中工作。只要 firstName 或者 lastName 的值发生了变化,uppercasedFullName 这个 getter 就会自动重新计算,页面上的显示也会立刻刷新,完全不用我们手动去更新。 总结一下,这就是一个很经典的开发模式:用输入字段捕捉用户数据,用事件处理器更新组件的状态,再用计算型的 getter 把状态转换成需要展示的样子。整个过程都由反应性系统驱动,代码简洁,维护起来也特别轻松。
同学们,咱们今天来聊聊 LWC 里的 JavaScript 反应链是怎么工作的,这一段虽然简短,但把响应式的精髓都点出来了,咱们一句句拆开看。 首先,你看这页面里有两个输入框,分别绑定着 firstName 和 lastName 这两个属性。它们一开始都是空字符串,所以页面刚加载时,你看到的是空的输入框。 接着,当你在输入框里打字,比方说你在姓的框里输入内容,框架就会通过一个叫 handleChange 的方法来响应。这个方法很聪明,它不针对每个输入框单独写处理函数,而是用事件对象的 target.name 来区分到底是哪个字段被修改了。这样,不管你有多少个输入框,只要名字匹配类里的属性名,这一个方法就全能处理,代码特别干净。 然后,我们定义了一个叫做 uppercasedFullName 的 getter 访问器。它用模板字符串把 firstName 和 lastName 拼在一起,中间加个空格,再去掉前后空白,最后整个转换成大写。你可能会问:为什么我在页面里直接用 {uppercasedFullName} 就能看到大写全名呢?这就是反应链的核心了。 因为咱们在 getter 里用到了 firstName 和 lastName,而且在模板里又用到了这个 getter。当你在任何一个输入框里输入,LWC 的响应式引擎就会检测到这两个属性的变化,接着它会重新计算 uppercasedFullName 这个 getter,拿到新的值,然后再把模板里对应的地方更新掉。整个流程是自动的,你不用手动去叫框架刷新。 这里要特别注意一点:只有我们在类里提前声明好的字段,像这里的 firstName、lastName,才是反应式的。如果你在运行时随意给对象新增一个属性,比如用 this.someNewProp = 'abc' 这种 expando 属性,框架是不会理你的,它不会触发重新渲染。所以一定要在类体里把需要的属性都声明好,这样才能享受到响应式带来的自动更新。 好了,简单总结一下:反应链就是把属性变化、getter 重新计算、模板自动渲染串在一起的一条线。你改数据,我自动算,页面跟着变,这就是 LWC 让我们省心的地方。
同学们,我们这一页要聊的是LWC组件里属性的命名约定和一些关键的规则。别看只是命名,这里面要是搞错了,组件可能就渲染不正常,或者你写的代码不生效。咱们慢慢梳理一下。 首先,最核心的一个规则是:JavaScript里面我们写属性名,用的都是驼峰式,也就是camelCase,比如 `myProperty` 这种形式。但是在HTML模板里引用这个属性的时候,必须转换成短横线分隔的形式,也就是kebab-case,变成 `my-property`。举个例子,你在 JS 里定义了一个叫 `accountName` 的公开属性,那么HTML里就得写成 `<c-my-component account-name="...">`。这是框架自动帮我们做映射的,但你必须遵守这个规律,否则绑定不上。 接下来,有些开头的词,是专门留给平台用的,你不能随便用。以 `on` 开头的属性,会被当作事件监听器处理;以 `aria` 开头的,是给无障碍访问用的ARIA属性;以 `data` 开头的,会变成自定义数据属性。所以,你自己的属性名千万别用这些前缀,比如别定义一个叫 `onclickHandler` 的公开属性,那样会引起冲突。 还有几个词是完全被保留的,无论你放在哪里,都不能作为公开属性名。它们是 `slot`、`part` 和 `is`。这几个词有特殊的框架语义,比如 `slot` 是做插槽分发用的,`part` 是影子DOM的样式穿透,`is` 在自定义元素规范里有特殊含义。所以你绝对不能把公开属性起名叫 `is`,这不行。 再说HTML属性名称本身,它有一套自己的规则。特别要注意的是,如果你用一个以单个大写字母开头的属性名,比如 `MyProp`,浏览器在处理时会把它自动转成全小写,但在标准DOM里,它实际上会被映射成一个特殊的形式。框架要求所有HTML属性名只能包含小写字母、数字、连字符和下划线,而且不能以数字开头。所以最稳的做法就是全部用小写的kebab-case,别用大写字母,这样可以避免很多奇怪的行为。 最后,我们还会谈到如何在JavaScript里正确访问HTML全局属性和ARIA属性。比如,HTML里的 `class` 属性,在JS里访问时不能用 `class`,因为那是保留字,你得用 `className`。类似的,`for` 属性要转换成 `htmlFor`。ARIA属性呢,比如HTML里写的是 `aria-label`,在JS里要通过 `ariaLabel` 来访问,也就是去掉了短横线,变成驼峰式。这些映射你一定要心中有数,不然在代码里动态设置属性时就会出错。 总结一下,记住这几点:JS用驼峰,HTML用连字符;避开保留前缀和保留词;属性名全小写,安全又可靠;访问全局和ARIA属性时注意驼峰映射。这样就为你构建出健壮的LWC组件打下扎实的基础了。我们接下来可以看几个实际的例子,加深一下印象。
咱们今天聊一聊Lightning Web Components里属性命名的规矩。这个很重要哦,因为一旦不小心用了不该用的名字,组件就容易出些奇怪的冲突,调起错来还挺头疼的。你不用紧张,听懂这几条原则,以后起名就不容易踩坑了。 首先呢,LWC的JavaScript属性,咱们代码里写的,都得用驼峰式命名,就是首字母小写,后面每个单词首字母大写,比如 `itemName`。但是到了HTML模板里,对应的属性就得变成短横线连接的方式,叫kebab-case,写出来就是 `item-name`。框架会自动帮我们把这两种写法互相转换,所以两边保持一致就行,只是形式不同。 好,接下来是真正的禁区——有些开头是不能用的。如果你的属性名以“on”开头,比如 `onChange`,这会和标准的HTML事件处理程序冲突,因为所有事件处理器都是 `onclick`、`onchange` 这样的,所以框架禁止你这么用。同样的,以“aria”开头也不行,像 `ariaLabel`,因为 `aria-` 是留给无障碍属性的。还有以“data”开头也不行,比如 `dataId`,因为在HTML里自定义数据属性都用 `data-` 开头。用了这些开头,很可能框架就分不清这是你的属性还是系统属性了。 另外还有三个单词是完全保留的,绝对不能当属性名。第一个是“slot”,因为 `<slot>` 专门用来做内容投影的;第二个是“part”,这个是配合CSS,阴影部件,样式的;第三个是“is”,它跟元素类型检查有关系。如果你用这些单词作属性名,就会和DOM内部的API或概念撞车,导致不可预期的行为。 你只要记住这几条:写驼峰对应短横线,避开 on、aria、data 开头,再躲开 slot、part 和 is,你的属性命名就很安全啦。这样咱们的组件就能顺利和标准Web兼容,运行起来稳稳当当。
好,我们来看这一页的内容,聊聊 HTML 属性命名和 LWC 里一个挺巧妙的设计。 你有没有发现,在 HTML 里给元素加属性的时候,命名其实比 JavaScript 严格得多?比如你不能随便用大写字母开头,也不能包含某些特殊字符。HTML 属性的名字,本质上必须能直接写在标签里,所以规则就定得很死。 具体来说,合法的开头字符只能是大写字母、下划线或者美元符号。然后后面还可以跟连字符组合,比如常见的 data- 这种。有些特殊组合,像双下划线、下划线连字符、连字符下划线,在特定位置也是允许的,这些看起来有点怪,但确实是规范里允许的写法。 那问题来了,在 Lightning Web Components 里,我们的 JavaScript 属性有时候会以大写字母开头,比如你定义一个 @api 装饰的属性叫 Upper。如果直接把 “Upper” 塞进 HTML 属性里,是不符合规范的,因为属性名不能以大写字母开头。 LWC 想了个很巧妙的办法:它通过编译器做一个映射。当一个 JavaScript 属性名以大写字母开头时,就会把它转成带前导连字符的属性名。具体做法就是把首字母大写变小写,然后在前面加一个连字符。所以 @api Upper 最终在 HTML 里对应的属性就变成了 -upper。 这个“前导连字符”其实是一个信号,告诉模板编译器:“注意了,这个属性映射的是一个以大写字母开头的 JavaScript 属性。” 这样编译器就能正确处理,同时也没有违反 HTML 属性命名的规范。 你可以把它理解成一种翻译规则:把 JavaScript 里“不守规矩”的大写开头,穿上一件 HTML 认可的“连字符外衣”,既保住了语义,又合规合法。这就是 LWC 应对 HTML 命名限制的一个优雅实现。 所以这一页的核心,就是记住两点:HTML 属性命名很严谨,开头不能用大写;而 LWC 通过加连字符前缀的方式,把大写开头的 JS 属性安全地映射到了 HTML 中。好,这部分我们就讲到这里,有没有什么疑问?
好,我们来看这一页,讲的是HTML全局属性在LWC里的特殊情况。 你可能知道,像 `class`、`title`、`accesskey` 这些属性,几乎所有HTML元素都能用。在LWC里,我们通常不直接把它们暴露出去,而是用组件自己定义的特定属性,这样做更安全、更符合组件化的思想。但有时候,你可能真的需要去操作这些全局属性,那该怎么办呢? 这时候,你就得用 `@api` 装饰器来标记那个属性。因为只有这样,外部才能给这个组件设置这些HTML级的属性。但要注意,有一个坑:这些属性名从HTML转换成JavaScript的时候,并不全是我们熟悉的驼峰规则。 举个例子,HTML里的 `for` 属性,在JavaScript里不能叫 `for`,因为 `for` 是保留关键字,所以它变成了 `htmlFor`。再比如 `maxlength`,按驼峰本应该是 `maxLength`,那它确实就是这样;但有时候容易弄混,所以记住它确实是 `maxLength`。还有 `readonly`,转换过来是 `readOnly`,首字母小写,中间O大写。 所以,当你在LWC里写 setter 或 getter 来捕获这类属性时,一定要用这些非标准的名字。这页幻灯片里提供了一个明确的映射表,如果你记不住,就随时查这个表,千万别自己想当然地去转换。 总之,记住两件事: 第一,LWC里首选组件专属属性,尽量别去碰HTML全局属性。 第二,如果必须用,就用 `@api` 暴露,并且严格按照这个映射表来命名,比如 `htmlFor`、`maxLength`、`readOnly`。 这样就不会因为名字写错而 debug 半天啦。
同学们,咱们今天聊一个特别重要的话题——在Lightning Web Components里怎么让我们的组件对所有用户都友好,特别是那些依赖屏幕阅读器的用户。这就要用到ARIA属性,也就是“可访问的富互联网应用程序”属性。 在LWC的模板里,写起来很简单,你直接用标准的HTML ARIA属性就行,比如`aria-checked`表示复选框是否选中,`aria-label`给元素加个标签。这些属性写在HTML里,跟在普通网页里一模一样。 但是,当你在JavaScript里通过组件实例去访问这些属性时,规则就变了。你不能直接用连字符的形式了。得记住一个转换模式:把开头的“aria-”去掉,然后把剩下的部分里,所有的连字符都去掉,改成驼峰式命名(camelCase)。连字符后面的字母要变成大写。 举个例子: - 模板里的`aria-checked`,到JavaScript里就变成`ariaChecked`。 - `aria-label`变成`ariaLabel`。 - 如果有个复杂的比如`aria-describedby`,那就变成`ariaDescribedby`,注意“by”的首字母没有大写,因为原来连字符后面是“d”,所以`described`的d大写,`by`没有前面的连字符,所以小写。但更常见的如`aria-errormessage`,就变成`ariaErrormessage`?其实我们只要遵循:去掉“aria-”,然后把每一个连字符后的字母大写。所以`aria-errormessage`,去掉“aria-”得到`errormessage`,这没有连字符了,所以就是`ariaErrormessage`?不对,我们先理解成:原始字符串是`aria-errormessage`,去掉“aria-”后剩下`errormessage`,因为已经没有连字符了,所以直接首字母大写?实际上camelCase的规则是除了第一个单词首字母小写外,后续每个单词首字母大写。但这里`errormessage`本身是一个单词还是两个?按照ARIA属性,`aria-errormessage`是一个整体。实际转换是:`aria-errormessage`去掉`aria-`得到`errormessage`,然后转camelCase,因为只有一个词,所以就是`ariaErrormessage`。但文档例子是`ariaWRMessage`?原Slide里提到:ariaSYS, ariaLabel, ariaWRMessage。可能是个别例子。实际规则就是去掉“aria-”并将剩下的部分转成camelCase。比如`aria-labelledby` -> `ariaLabelledby`。总之,记住:去掉“aria-”,剩下的部分中,每个连字符后的字母变成大写,并去掉连字符。 其实这个映射规则和HTML属性到JavaScript的转换是一样的。比如HTML里的`tabindex`,在JavaScript里是`tabIndex`;`readonly`是`readOnly`。所以ARIA属性也是一样的道理。 LWC全面支持了WAI-ARIA的状态和属性,也就是说,所有ARIA相关的东西,你都可以在组件实例上用这些camelCase属性去访问和设置。这非常重要,因为只有正确使用ARIA,我们构建的应用才是包容的,让所有人都能有效交互。 简单总结一下:模板里用带连字符的标准ARIA属性,JS里用驼峰式,去掉“aria-”前缀,然后转驼峰。记住这个模式,你就能游刃有余地在LWC里做好无障碍开发了。 好了,这一小节课就到这里,大家有任何问题随时提问。
同学们,今天咱们来聊聊每个闪电 Web 组件最核心的那个根,就是 ,LightningElement, 这个基类。你可以把它想象成盖房子的地基,所有的 LWC 组件都必须从这个基类扩展出来,没有它,我们的组件就跑不起来。 那这个基类给我们带来了什么宝贝呢?首先,它有一套独特的属性,支撑起了整个框架的核心功能。咱们一个一个看。 第一,,构造函数,。当你写一个组件类的时候,构造函数会自动运行,帮你初始化组件。比如设置一些初始状态,就在这里做。 第二,,hostElement, 属性。它让你可以直接访问到组件背后那个原生的 HTML 元素。如果你想对最外层的宿主元素做些操作,通过它就非常方便。 第三,,template, 属性。它让你能访问组件自己的影子 DOM 模板。不过这里有个重要的区别:如果你用的是标准的影子 DOM 模式,通过 `this.template` 就能拿到模板里的元素;但如果你用的是轻型 DOM 组件,也就是没有影子根,那么你就不能再用 `this.template` 了,而是直接使用 `this` 来引用组件内部的元素。这一定要记清楚,不然找元素时会踩坑。 第四,,refs, 属性。它提供了一种更快捷的方式来获取 DOM 元素。你只需要在模板里的元素上加上 `ref` 指令,然后直接通过 `this.refs.名字` 就能拿到那个元素,非常省事。 好,讲完属性,再来说说组件一生要经历的各个阶段,也就是,生命周期回调,。LightningElement 一共提供了五个生命周期钩子,让你能精准地在不同时间点插入自己的逻辑。 我把它们按常见顺序说一下: - ,constructor,:也就是前面提到的构造函数,组件被创建时最先触发,用来做最基本的初始化。 - ,connectedCallback,:中文常叫“挂载”。当组件被插入到 DOM 的时候会调用,非常适合去拉取数据、设置监听器这些。 - ,disconnectedCallback,:对应“卸载”。当组件从 DOM 里移除的时候触发,用来清理定时器、取消订阅,防止内存泄漏。 - ,renderedCallback,:每次组件渲染完成后都会调用。不管是首次渲染还是后续更新,只要界面渲染完毕,它就跑一次。在这里你可以做一些跟界面更新后的操作,但要小心,别引起循环渲染。 - ,errorCallback,:这是一个专门用于错误边界的回调。如果组件里的某个子组件在运行或渲染时抛出错误,这个回调就能捕获到,避免整个应用白屏,你可以在这里展示一个友好的错误提示。 记住这五个生命周期,你就掌握了组件从出生到消亡的全过程。今天这一页说白了,就是告诉大家,你所写的每一个 LWC 类,都有这些血脉传承下来的能力。理解了它,后面的学习就轻松多了。
同学们,咱们这节课来看个很有意思的点。在LWC里,你创建的每一个组件,它的底层其实继承自标准的HTMLElement接口。这意味着什么呢?意味着你从前端开发学到的那些原生的DOM操作知识,在LWC里原封不动就能用。 咱们平时写JavaScript,常用的那些API,比如给元素绑定事件用的 `addEventListener`,或者主动触发事件用的 `dispatchEvent`,直接就能在组件里调用。想获取某个属性,`getProperty`,检查有没有某个属性,`hasProperty`,或者删除属性 `removeProperty`,这些都一模一样。 还有布局相关的,比如想拿到元素在页面上的位置尺寸,用 `getBoundingClientRect`;想在组件内部找某个class的元素,用 `getElementsByClassName`;找某个标签的元素,用 `getElementsByTagName`——全都是原生的用法。就连那些属性,比如 `children` 拿到子元素列表,`classList` 管理样式类,`hidden` 隐藏元素,`dir` 设置文字方向,甚至 `draggable` 让元素可拖拽,这些全部都可以直接用,LWC根本没有改过它们,也没有限制。 所以,这个继承最棒的地方就是,你之前积累的DOM知识一点都不会浪费。你只要按标准的Web开发方式去用就行了,LWC对它们是完全兼容的。这对咱们快速上手、写出交互复杂的组件,帮助非常大。
同学们,我们接着来看LWC组件继承的属性列表。这部分非常重要,因为它让我们理解LWC组件在底层其实就是一个标准的、功能齐全的DOM元素。 首先,你有一个,id,属性,用来标识元素。但要注意一点,LWC框架会自动转换你设置的id值,以防止在页面上出现重复,所以最终呈现的id可能和你在模板里写的不完全一样,这一点在调试时特别要留心。 然后是,isConnected,,一个布尔值,它告诉你组件当前是否已经连接到DOM树上。你可以在生命周期中用它来判断组件是不是真的在页面上可见,比如在做异步操作时防止更新一个已经移除的组件。 接下来是,lang,属性,它直接反映HTML里的语言设置,对辅助技术很重要。 说到DOM查询,我们有一对好用的方法:,querySelector, 和 ,querySelectorAll,。它们允许你在组件的内部DOM中查找子元素,注意,默认是在影子DOM里查找,除非你用的是轻型DOM模式。 属性管理方面,,setProperty, 和 ,removeProperty, 可以动态操作元素的CSS自定义属性,也就是CSS变量。这样我们就能在JavaScript里灵活控制样式。 ,shadowRoot,属性非常关键,它返回对组件影子DOM根节点的引用。如果你把组件配置为轻型DOM,那`shadowRoot`的值就是`null`。所以用它之前最好检查一下。 还有控制交互行为的属性,比如,spellcheck,开启拼写检查,,tabIndex,控制元素的可聚焦顺序。这些都能直接在组件上设置,就像普通HTML元素一样。 从API版本62.0开始,我们还能直接通过`style`属性来获取元素的样式对象,这给了我们更直接的样式操作能力。 别忘了,tagName,,它返回组件对应的自定义HTML标签名称,全部大写,比如`C-MY-COMPONENT`。 最后要强调,完整的,WAI-ARIA属性,也都被继承下来了。也就是说,你在组件上设置`aria-label`、`role`等无障碍属性,它们会直接作用在底层元素上,帮助我们构建无障碍的应用。 所以你看,所有这些继承来的属性让LWC组件完完全全就是一个一等公民的DOM元素,它可以无缝参与浏览器所有的标准操作,比如事件冒泡、DOM遍历、样式计算等等。理解了这一点,你就能更自信地把LWC当成原生HTML来使用了。
同学们,今天我们来聊聊一个很有趣的话题——LWC组件是怎么像原生HTML元素一样在页面上表现的。 你看,当你使用Lightning Web Components时,你的组件其实反映了一个很重要的接口——标准Element接口。什么意思呢?简单说,就是你的LWC组件可以假装自己就是一个普通的HTML元素,这样你就能像操作div、span那样去操作它了。 它为咱们提供了一组关键的功能。比如: - 你想在组件里遍历子元素?有,children,、,firstElementChild,、,lastElementChild,这些属性,跟DOM一样方便。 - 操作CSS类名?有,classList,和,className,,加类、删类完全没问题。 - 想动态读取或修改属性呢?,getAttribute,、,setAttribute,、,hasAttribute,、,removeAttribute,都齐活,而且它们还支持命名空间,比如可以处理带“xmlns”或“xlink”前缀的属性。 - 查询子元素也有,querySelector,、,querySelectorAll,,以及经典的,getElementsByClassName,、,getElementsByTagName,方法。 - 获取元素的大小位置信息,可以用,getBoundingClientRect,。 - 如果你需要访问影子DOM的根节点,还可以用,shadowRoot,属性。 - 另外,还有一个,slot,属性,可以拿到组件内部的slot元素,插槽内容就是这样控制的。 不过,这里得提醒大家注意一点:如果你在组织里启用了Lightning Web Security(简称LWS),有些方法的行为会稍有变化。比如,setAttribute,和,shadowRoot,的getter会被LWS的安全沙箱“扭曲”一下。别担心,它这么做是为了保证安全,同时尽量保持兼容性,只是底层实现稍微修改了一下,一般使用感受还是差不多的。 所以,正是这些接口,让你的LWC组件在页面上表现得像个地地道道的原生元素,又安全又灵活。明白了吗? 今天就讲到这里,下课!
好,我们来聊聊LWC组件背后的几个关键Web API。 你可能知道,LWC组件本质上是一个自定义的HTML元素。既然是Web标准的一部分,它就自然继承了一些浏览器内置的能力。具体来说,LWC组件反映了三个核心Web API的属性,再加上它本身的接口,一共是四个。 第一个,来自EventTarget。这让你能在组件上使用完整的事件系统。比如addEventListener添加监听,removeEventListener移除监听,还能用dispatchEvent主动派发事件。这让组件之间的通信非常灵活。 第二个,来自HTMLElement。它给你带来很多布局相关的属性,像offsetHeight、offsetLeft、offsetTop这些,方便你获取元素的大小和位置。还支持contentEditable让元素可编辑,以及data-*自定义属性,不过注意,LWS对这个数据集属性做了一些调整。另外还有hidden、title、lang这些与显示和国际化相关的属性,用起来很顺手。 第三个,来自Node接口。它提供了DOM树导航能力,比如childNodes、firstChild、lastChild,让你能遍历子节点。还有一个很实用的属性叫isConnected,能告诉你这个元素当前是否插入到了DOM中。这在检查某些同步回调时特别有用,能避免操作一个已经不在文档里的元素。 这四个接口加起来,就让LWC组件成了一个完完全全的DOM公民,享有原生元素该有的能力。你可以在组件里像操作普通HTML元素一样,使用这些属性和方法,非常自然。 这样讲,你是不是对LWC组件的底层能力更清楚了呢?
好了,今天我们来聊聊 Lightning Web Components 里一个非常贴心的设计,就是它对无障碍访问的支持,也就是 WAI-ARIA。 简单说,ARIA 是给 HTML 加上一些额外的属性,帮助屏幕阅读器这些辅助技术更好地理解页面上那些动态交互的内容。在 LWC 里,你可以直接在组件上使用这些 ARIA 属性,而且写法上特别自然——它们会自动映射成 JavaScript 里那种驼峰命名的属性。 举个例子,原生 HTML 里你写成 aria-label,在 LWC 模板里也这么写,但到了 JavaScript 端访问组件实例时,它自动变成了 ariaLabel。同样,aria-expanded 就变成 ariaExpanded。这样你在写逻辑的时候,直接用熟悉的驼峰变量就能读取或设置这些状态,不用来回转换。 接下来,我们先快速看一下最常用的第一组 ARIA 属性,从 A 到 G 开头这一批。 首先是 `ariaActiveDescendant`,这个在复合组件里特别有用,比如一个下拉列表或者一个网格,当前焦点在哪个子项上,就用它来标记,让辅助技术知道现在谁是被激活的后代元素。 `ariaAtomic` 是控制实时区域更新行为的。当你一个区域的内容会动态变化,如果希望屏幕阅读器把整个区域的文字都重新读出来,就把它设为 true;要是只想读变化的那部分,就是 false。 `ariaAutoComplete` 管的是输入建议,像搜索框自动补全的场景,用它告诉辅助技术这个输入框是不是有自动完成功能,值是 inline、list 还是 both 或者 none。 `ariaBusy` 就很简单了,表示一个元素正在加载或更新中,这时候辅助技术可以等一等,避免读到不完整的内容。 然后是表格列相关的属性,`ariaCol` 系列,比如 `ariaCol` 本身用来标记一个单元格属于表格的第几列,很适合复选框和单选按钮那种控件。还有扩展出的 `ariaColCount`、`ariaColIndex`、`ariaColSpan` 这些,用来描述表格列的元数据,告诉辅助技术整个表有几列,当前元素跨越几列。 再来看导航相关的,`ariaControls` 标识的是当前元素控制着哪个区域,比如一个标签页按钮控制着对应的面板;`ariaCurrent` 则是在导航里标记当前处于活动状态的项,值可以是 page、step、location、date 或 time。 `ariaDescribedBy` 是用来关联描述文本的。例如输入框下面的提示文字,通过这个属性把输入框和提示的 id 绑定起来,屏幕阅读器就能一起读出来。 状态指示方面,`ariaDisabled` 表示一个控件当前是否禁用,`ariaExpanded` 则表示一个可展开折叠的控件,现在是展开还是收起的状态。 这些 cammelCase 属性在 LWC 组件里都可以直接赋值或者读取,让你的组件既能提供丰富的交互,又能对所有人都友好易用。记住,在模板里还是大写字母连字符的写法,比如 aria-expanded,但在 JS 里处理就是 ariaExpanded。 好了,今天这部分就到这里,下一节我们接着看后面的属性。
大家好,欢迎来到我们的Lightning Web Components课程。这节课我们来聊聊第二组ARIA属性,这些属性覆盖了很多无障碍场景,能够让我们的组件对各种辅助技术更友好。 首先啊,ariaHasPopup,它的意思是告诉屏幕阅读器:“嘿,这个元素会弹出一个菜单或者对话框”。比如一个按钮点了会出下拉菜单,我们就可以用这个属性标记它,让用户提前知道。 下一个是ariaHidden,这个挺重要的。它可以控制元素对辅助技术是不是可见,而不管它视觉上是不是显示。也就是说,你可能屏幕上看着有东西,但把它设为hidden,屏幕阅读器就跳过去了。反过来也行,视觉上隐藏但让辅助技术能读到。 然后有aria-invalid,专门用在表单输入上。当用户填错了格式或者验证没通过,我们就给输入框加上这个属性,告诉辅助技术:“这个字段目前有错误”。这样视障用户也能立刻知道哪里需要改正。 ariaLive是个很有趣的属性。它用来管理“直播区域”,就是动态更新的内容怎么被朗读出来。有两个主要模式,一个叫“polite”(礼貌),它会在当前朗读任务结束后再宣布更新;另一个叫“assertive”(自信),会打断当前朗读,立刻宣布。比如聊天消息,你可能希望用polite,不打扰用户;但紧急提醒可能要用assertive。 ariaModal表示一个对话框是不是模态的。模态对话框就是那种打开后,后面的内容不能操作,只能先处理对话框。这个属性帮助辅助技术理解当前焦点的上下文。 接下来是一组表行相关的属性,ariaRow* 系列。这些提供了表行的索引、行数,以及行头等信息,让表格导航更精准。 然后针对范围小部件,比如滑块,我们有ariaValueNow(当前值)、ariaValueMin(最小值)和ariaValueMax(最大值)。屏幕阅读器会告诉用户当前的数值范围,这样调整滑块就变得无障碍了。 ariaPressed专门给切换按钮用的,比如加粗、斜体按钮,按下去是“开”,弹起来是“关”。这个属性就能表达按钮是被按下去了还是没按。 ariaSort指示表格列的排序状态,升序、降序还是无排序。这样用户能理解数据是怎么排列的。 两个比较新的属性,ariaBrailleLabel和ariaBrailleRoleDetails,它们是专门给盲文显示器提供额外信息的。可能普通屏幕阅读器用不到,但盲文设备能展示更细致的标签和角色描述,非常贴心。 最后提醒一下,如果你用的是Lightning Web Components的开源版本,也就是LWC OSS v4.0.0及以后,上面这些属性里面有些已经不再是全局Polyfills了。这意味着如果你不主动引入,它们可能不会自动生效。所以一定要去查看官方的弃用文档,看看哪些需要额外配置,免得无障碍支持出问题。 好了,这一组属性大家先有个整体印象。下节课我们会详细看怎么在组件里使用它们。我们下次见!
今天我们来聊聊 Lightning Web Components 里一个特别有用的模式——获取器和设置器,也就是 getter 和 setter。 简单说,getter 和 setter 能让你控制别人怎么读取或者修改组件的公共属性。有两条很简单的规则要记住:如果你提供一个 getter,也必须提供对应的 setter,反之也是一样。但是,你只需要用 @api 装饰器标注其中一个,通常咱们习惯标注在 getter 上。 那么,真正存数据的地方在哪里呢?别直接放在公共属性里,而是用一个私有字段,以_ 开头,比如 _uppercaseItemName。这样既能隐藏内部实现,又能让你在 setter 里加各种逻辑。 有了 setter,你可以做很多事。举个例子,把用户输入的值自动转成大写,或者验证数据是否符合要求,也可以格式化成你想要的形式,甚至触发一些副作用,比如记录日志或更新其他东西。 再说 getter,你可以在里面用 try-catch 包起来,这样就算读数据的时候出了错,也能优雅的处理,不会让组件崩溃。 想看看实际的代码吗?lwc-recipes 这个官方样例库里就有 illustrate,其中 apiSetterGetter 和 todoList 这两个例子都展示了这些模式,特别直观。 所以,记住这个模式:私有变量存数据,用 @api 的 getter 公开读取,用 setter 来控制写入,让组件更安全、更灵活。
同学们,现在咱们来看这样一个例子,它完整展示了 LWC 里 getter 和 setter 的用法。想象一下,你的组件模板里只需要显示一个 `{itemName}`,看起来很简单对吧?但其实背后藏着数据转换的秘密。 首先,在 JavaScript 类里,你会看到一个带下划线开头的 `_uppercaseItemName`。这个命名约定就是在暗示:嘿,这是一个私有字段,别从组件外面直接碰它。它就是我们存放最终要显示的值的地方。 真正对外的门面是什么呢?是那个用 `@api` 装饰的 getter,名字就叫 `itemName`。注意,是 getter 被设成公开属性,这样父组件就可以用 `item-name` 这个属性给它传值。比如父组件写了 `item-name="milk"`,这时候 setter 就会收到这个 `"milk"`。 Setter 拿到原始值后,第一件事就是把它转换成大写,变成 `"MILK"`,然后存到刚才那个私有字段 `_uppercaseItemName` 里。接着,getter 登场了,它什么都不用做,直接把私有字段的值返回给模板。于是,用户最终在界面上看到的就是大写的 `MILK`。 整个数据流可以这样记:父组件通过属性传给 setter,setter 做一番转换,存进私有字段,getter 再取出来给模板渲染。你看,通过这个模式,我们就能在组件边界上完全掌控数据该如何变化,又不暴露内部细节。这样,组件封装性保持得很好,显示逻辑也足够灵活。
同学们,咱们今天来聊聊在写Lightning Web Components组件的时候,一个非常重要的习惯——让组件能“优雅”地处理错误。 你想想看,你在生产环境里跑的组件,如果碰到一点小毛病就整个白屏、或者直接崩溃,那用户体验得多差呀。所以我们得学会一种叫,防御性编程,的思路,尤其要护住组件的公共API,让它变得特别“皮实”。 先说,getter,,也就是获取器。你想,getter常常用来对外暴露一些计算后的状态,比如格式化过的文本、拼接好的标签。如果它内部访问的某个属性意外变成了乱码,或者代码逻辑触发了异常,组件可能就彻底报错不显示了。 这个时候,一个非常管用的模式,就是把getter里面的逻辑用`try-catch`块包起来。就像给脆弱的部分加了个保护气囊。万一try里面的代码“炸”了,catch就能接住它,然后我们返回一个安全的备用值,比如一个空字符串。这样一来,就算内部状态有点损伤,组件仍然能渲染出点有意义的东西,或者至少是空白,而不是直接崩掉。你可以把getter里的这个try-catch看成是,最后一道防线,,它确保组件总是能体面地站稳。 同样的原则,也要用到,setter,,也就是设置器里。当外部传入一个值要更新组件状态时,你不能无条件地照单全收。你得先验证一下:传进来的是不是空或者未定义?格式对不对?如果不对,可以有两种做法:一种是直接抛出一个描述清晰的错误,让调用方知道“喂,你传的东西不对”;另一种是悄悄地把值规范化,比如把首尾空格去掉,或者给一个默认值。总之,不能让脏数据溜进内部。 那么,这一切的核心思想是什么呢?就是,组件的公共API必须是稳健的,。它要能处理各种稀奇古怪的边缘情况,处理无效的输入,而且在碰到问题时绝不能轻易崩溃。getter里的try-catch就是你的安全网,setter里的检验就是你的门卫。养成这些习惯,你写出来的组件会可靠得多。 好了,关于错误处理的防御性编程,咱们就讲到这里。大家在日常开发里,试着给getter套上try-catch,给setter加上校验,让组件真正经得起折腾。
同学们,今天咱们来聊聊 Lightning Web Components 里面一个看似简单却很容易踩坑的知识点:布尔属性。 首先,布尔属性是啥呢?就跟咱们在标准 HTML 里经常见的 disabled、checked 一样。你有没有写过 `<input disabled>`?只要这个 disabled 一出现,输入框就禁用,不写就是可用的。LWC 里自定义组件的布尔属性完全遵循这一套规则:属性出现在标签上,就代表 true;不出现,就代表 false。注意啊,这里的关键在于“出现”还是“不出现”,跟等号后面写的啥值没关系。 那为什么要有这个规则呢?因为它能让我们很自然地设定默认状态。在组件的 JavaScript 文件里,定义布尔属性时,默认值一定要设成 false。比如 `@api show = false;`,这样如果父组件没传这个属性,子组件就按 false 处理,也就是不展示。这非常符合直觉,默认就是“假”嘛。 接下来,咱们得重点避坑——很多开发者以为在标签上写 `show="false"` 就能把属性关掉,这就错了。在静态标记里,不管你写 `show="true"` 还是 `show="false"`,甚至 `show=""`,只要这个属性写在了标签上,它就会被解析成 true。为啥呢?因为 LWC 跟标准 HTML 一样,只检查属性是否存在,不读它的字符串值。你给它一个 "false",引擎可不会自动帮你转成布尔假,它只看有没有这个属性。所以,想用静态方式传一个 false,唯一的办法就是把这个属性删掉,别写它。 那如果我们需要在运行时动态切换布尔值呢?这时候就要靠模板表达式了。比如 `<c-child show={isVisible}></c-child>`,这里的 isVisible 是父组件里的一个布尔变量。当 isVisible 是 false 的时候,LWC 压根就不会把 show 属性渲染到 DOM 上,既然不存在,子组件收到的自然就是 false。所以动态绑定才是控制真假的好办法。 总结一下,记住两点:第一,在组件里声明布尔属性,默认值一定设成 false;第二,想设置成 false,要么不传这个属性,要么通过动态绑定一个值为 false 的变量,千万别用 `属性名="false"` 这样的静态写法。把这两点搞懂了,布尔属性这块你就不会再被坑了。 好了,关于布尔属性的规则和陷阱,咱们就先讲到这儿。下个小节继续。
今天我们来聊聊 LWC 组件里一个很常用但又特别容易踩坑的小知识点——布尔属性。 你肯定在 HTML 里见过像 disabled、checked 这样的属性吧?在标准 HTML 中,布尔属性的规则很特别:只要这个属性出现了,不管你有没有给它赋值,都表示“真”。要想表示“假”,唯一的办法就是压根不写这个属性。 LWC 里的布尔属性也严格遵守这个约定。所以,你在定义组件属性的时候,,默认值必须设成 false,。 为什么一定要这样呢?想象一下,你在 JavaScript 里把一个布尔属性的默认值设成了 true。那使用这个组件的人就惨了,因为不管他在标签里写不写这个属性,组件拿到的值永远是 true。他完全没办法在页面上静态地把它关掉——因为默认就是 true,加上属性还是 true,不加属性也是 true,这条路就堵死了。 所以,正确的做法是,把默认值设成 false。当别人想把它打开,也就是设为 true 的时候,只需要在组件标签上加上这个属性名,后面不用写任何值。比如你写一个 `<c-bool show>`,这就表示 show 的值是 true。 这里要特别注意一个非常常见的错误:千万不要写成 `show="false"`。你可能会觉得,我明明写了 false 啊,但其实在 HTML 布尔属性的世界里,只要属性出现了,不管它的值是什么,都算 true。而且 `"false"` 在 JavaScript 里是一个非空字符串,这本身也是个“真值”,所以你会得到一个你意想不到的 true。 另外还有一个小细节,当 show 为 true 的时候,这个属性并不会自动出现在最终渲染出来的 HTML 元素上,除非你手动去调用 setProperty 把它反映到 DOM 上。这也是 LWC 的一种优化机制。 简单总结一下: - 默认值永远设成 false。 - 想打开属性,标签上加属性名就行,别写值。 - 千万不要写 `show="false"`,它其实是 true。 - 属性为 true 时,不一定会在 HTML 上显示出来。 记住这几点,你就能绕开 LWC 布尔属性大部分的坑了。我们下一节继续聊。
同学们,我们来看这一页,讲的是“动态布尔属性”。这个概念其实特别实用,很多常见的组件交互都离不开它。 简单说,动态布尔属性就是用 JavaScript 表达式在运行的时候,动态算出一个 true 或者 false,来决定一个属性最终的值。这和静态写法完全不一样。 咱们先回忆一下静态写法。比如你在模板里写一个 `<c-child show>`,没有等号,也没有引号,就只写一个 `show`。在 LWC 里,这就意味着 `show` 属性存在,它就等于 true。也就是只要属性出现在标签上,它就是 true,删掉才是 false。这种叫“静态的布尔属性”,它的特点是:presence 等于 true,一出现就是 true。 而动态布尔属性,用的是 `{表达式}` 这种花括号绑定。比如 `<c-child show={computedValue}>`。这里的 `computedValue` 通常是父组件里定义的一个 getter,它会根据现在的组件状态返回 true 或 false。比如某个开关打开了,getter 返回 true;关上了,getter 返回 false。 当父组件的状态发生变化时,这个 getter 会立刻重新计算,算出新的布尔值,然后自动传给子组件的 `show` 属性。子组件收到新的 true 或 false,就会重新渲染自己,比如显示出来或者隐藏掉。 这就实现了一种标准模式,你一定接触过——显示/隐藏、启用/禁用、展开/折叠,这些切换行为全都可以用动态布尔属性搞定。而且跟静态属性比,它直观多了:表达式的实际运算结果是什么,属性就是什么,不需要猜“属性存不存在”。 最后再强调一下关键区别,帮助大家记住:对于静态属性,只要你在标签上写上属性名,它就等于 true,存在即为真;而对于动态表达式,JS 表达式算出来的布尔值才决定最终的属性值,是 true 就是 true,是 false 就是 false,更直接、更灵活。 好了,这一页就这些。现在大家应该很清楚动态布尔属性的用法和好处了,接下来我们看看实际代码怎么操作。
同学们,今天我们来聊一个虽然不大、但对用户体验影响非常大的知识点:怎么让我们组件里的 JavaScript 属性,真正出现在页面的 HTML 里面。 你可能已经习惯了用 `@api` 公开属性,让父组件传值进来。但有没有想过一个问题?当你在代码里通过 `@api` 定义一个属性时,比如 `labelText`,它在浏览器最终渲染出来的 DOM 元素上,其实是看不见的,不会自动变成一个 HTML 属性。这就好像你有一个名牌,但你把它放进了口袋里,别人看不到。 在大多数业务场景下,这确实没什么影响,因为数据在 JavaScript 里流转就够了。然而,一旦涉及到,无障碍访问,,情况就变了。屏幕阅读器和其他辅助技术,它们是靠读取 HTML 上的属性来理解页面内容的,比如 `aria-label`、`role` 这些,它们根本不知道你的 JavaScript 属性里存了什么。如果信息只停留在 JS 里,视障用户就听不到了。 所以,为了让这些辅助技术能“看见”我们的数据,我们必须主动把属性,反射,到 HTML 上。在 Lightning Web Components 里,这个操作是通过 `this.setProperty()` 来完成的。而且放心,这样反射出去的 HTML 属性也是,响应式,的,一旦值变了,组件会自动重新渲染,不需要你额外操心。 那具体怎么写呢?模式其实很简单。我们会定义一个私有的变量来真正存储值,然后用 `@api` 装饰 getter 方法,让它对外公开。关键的魔法在 setter 里:先把新值存到私有变量里,然后立刻调用 `this.setProperty()`,把同样的值推送到渲染出来的 HTML 属性上。 举个例子:假设我们要让组件支持无障碍,需要动态设置 `aria-label`。我们可以声明一个叫 `labelText` 的公开属性。在 setter 中,除了更新内部变量,我们调用 `this.setProperty('aria-label', value)`。这样一来,组件渲染出来的根元素上就会实实在在地多出一个 `aria-label` 属性,屏幕阅读器就能顺利读到它了。 你看,做法其实很优雅。记住这个口诀就行:私有藏值,getter 公开,setter 里存值并反射。这样一来,父组件传什么,HTML 上就出现什么,辅助技术再也不会错过你的信息。 好了,这个点就讲到这里。希望以后你设计组件时,能多想一步,让每一个用户都能顺畅地“看到”你写的东西。我们下节课见。
现在我们来看一个“反射图案”的完整小例子。别被名字吓到,“反射”其实就是让组件的一个公开属性,自动映射到它渲染出来的HTML元素属性上,这样就能同时被其他的HTML特性利用,比如title属性用来显示工具提示,或者ARIA的无障碍属性。 你看这段代码:组件通过一个@api装饰的getter,公开了一个叫title的属性。当父组件像这样用的时候:`<c-my-component title='Hover Over the Component to See Me'>`,这个title值就会进来。 重点是setter里做了两件事。第一,它把接收到的值用某种方式转换一下——这里可能是转成了大写或者首字母大写(你把RST理解成某种格式转换就行),然后把转换后的新值存到一个私有的_privateTitle字段里,这样就保证了内部状态干净。第二,它主动调用了 `this.setProperty('title', this._privateTitle)`。这是关键一步:它告诉底层的HTML元素,“把转换后的title值设置为我的真实DOM属性”。 这样做的好处是什么?结果就是渲染出来的组件HTML里,根元素上会带上`title="HOPER Over THE COMPONENT TO SEE ME"`(假设是这种转换形式)。于是,屏幕阅读器能读出这个title,你把鼠标停在组件上,也会自动出现工具提示。而如果你忘了调用setProperty,这个title属性根本就不会出现在DOM里,这两个方便的特性也就失效了。 总结一下:反射图案通过主动调用setProperty,让一个逻辑上的公开属性同时转化为DOM的可访问属性,既保持了封装,又打通了原生HTML能力的桥梁。这是一个很常用也很优雅的设计模式。
好,我们来看这张幻灯片,它讲了一个挺重要但容易被忽略的细节——在 Lightning Web Components 里,如果你不用 `setProperty` 会出什么问题。 你看啊,就算不在组件里调用 `setProperty`,JavaScript 那边的属性访问还是好好的,getter 和 setter 都正常工作,你照样可以在代码里用 `component.title` 去读或者写。但这只是 JavaScript 层面的。 关键问题出在,渲染出来的 HTML 输出上——你打开浏览器开发者工具,看 DOM 结构,会发现 `<c-my-component>` 这个标签上完全没有 `title` 属性。明白这意味什么吗?这意味着,屏幕阅读器根本读不到这个标题,用户把鼠标悬停上去也不会看到任何提示,浏览器里检查不到,而且所有依赖属性选择器的那些 CSS 或者 JavaScript,也都找不到它。 所以,区别很明显了:属性在 JavaScript 这边存在,但辅助技术读的是 DOM,DOM 里只包含你明确“反射”过去的东西。而 `setProperty` 的作用,就是把 JavaScript 里的值同步映射到 DOM 属性上,让辅助工具能识别,让你的组件真正可访问。这张对比图,就是告诉大家,为了无障碍,你绝不能省略这一步。
同学们,今天我们聊聊在Lightning Web Components里“反映属性”时的一个最佳实践。 想象一下,你的组件要暴露一个属性,比如 `tabindex`。你当然希望它有个合理的默认值,方便键盘操作。但更重要的是——得先看看使用者有没有自己设定过这个值。 这就是我们要牢记的原则:,在应用你自己的默认值之前,先检查消费者是不是已经设置了值。, 举个例子,在 `linkedCallback` 这个生命周期钩子里,你可以通过 `this.getProperty('tabindex')` 来检查。如果它返回的是 `false`,说明外部什么也没传,这时候你再去把它设成 `'0'`,保证键盘可访问性。如果外部已经传了值,比如 `'-1'`,那你就别覆盖它了,尊重使用者的选择。这样既提供了贴心的默认行为,又不会随意篡改别人的意图。 另外,有一组特殊的属性,像 `for`、`aria-activedescendant`、`aria-controls`、`aria-describedby`、`aria-details`、`aria-errormessage`、`aria-flowto`、`aria-labelledby` 和 `aria-owns`,你需要,始终,使用 `setProperty` 和 `getProperty` 来存取它们,因为它们涉及到无障碍特性,直接操作原生HTML属性有时会不生效。 最后提醒一下,如果你想从渲染出来的HTML里,彻底移除,某个属性,而不是仅仅把它清空,那应该调用 `removeAttribute()` 方法。 这样来处理反射属性,组件就会既灵活又安全。好,这个小技巧就讲到这里,下次我们接着看别的细节。
好,我们接着看下一个知识点,叫做属性依赖关系。这个听起来有点绕,但其实就是一个小陷阱,不注意的话,你写的代码可能会出现莫名其妙的bug。 想象这样一个场景:你有一个子组件,父组件一口气给它传了两个属性,比如一个叫“行数据”,一个叫“是否选中”。你可能会在子组件里写两个setter,分别是“行数据”的设置器和“是否选中”的设置器。然后你以为,父组件传值的时候,一定是按你写的顺序先设置行数据,再设置是否选中。 但事实上,Lightning Web Components 的框架并不保证这个顺序。它可能是先给你“是否选中”,再给你“行数据”。这样一来,如果你在“是否选中”的设置器里立刻想去用行数据做一些操作,那行数据可能还没到,你就会拿到undefined,程序就出错了。 这个问题的根源在于,你不能假设JavaScript属性被赋值的顺序。那怎么解决呢?很简单,用一个“懒评估”的模式。你把依赖多个属性的逻辑写在一个getter里,而不是写在setter里。在这个getter里,你先检查一下,所有需要的数据是不是都已经到了。如果没到全,就直接返回,什么也不做。一旦所有数据都齐了,你再执行你真正想做的逻辑。 这样不管父组件先传哪个属性,后传哪个属性,你的代码只会在所有东西都准备好的时候才真正跑起来。这就是所谓的“惰性评估”,它保证了你的逻辑执行时,依赖的数据肯定已经在了。 记住了,属性赋值顺序不可靠,用getter做懒加载检查,这就是今天的小技巧。
同学们,我们来看一个数据表里的经典例子,它展示了怎么优雅地处理“你先我后”的依赖问题。 假设你有一张表格,里面每一行的数据,和用来处理选中状态的方法,它们到达组件的时间顺序可能不一样。比如有时行数据先加载好了,选中方法还没传进来;有时候方法先到了,行数据还没准备好。我们怎么保证无论谁先来,最终都能正确执行,而且只执行一次呢? 这个例子里用了一个共享的响应式状态对象,里面放着所有属性。重点是行数据那个设置器,它被调用的时候,会先把值存起来,然后问两个问题:一是“处理选中的方法到了没?”,二是“我是不是还没处理过?”。只有这两个条件都满足,它才会调用一次 markSelectedHandler,同时立一个标志位,表示这件事已经干过了。 这个标志位很关键。设想一下,如果行数据先来,处理选中的方法还没到,那它就把值存好,但跳过处理,相当于先到的人在门口等。等过一会儿方法到了,一看,哦行数据已经在等了,就立刻处理。反过来,如果方法先到,行数据后到,行数据设置器发现方法早就等着了,也会直接处理。而如果因为某些原因两个设置器同时都触发了操作,那个标志位就能拦住,避免重复干活。 这样就巧妙地解决了依赖顺序不确定的问题,不管你谁先来,最终都能协调好,而且只做一次。这种依赖关系管理模式,在复杂组件里很实用。大家理解了吗?可以试着想一想,还有哪些场景也适合用这种“先到等,后到触发,加个标志防重复”的思路。
同学们,今天咱们来看一个在 Lightning Web Components 里非常实用的模式——,用 setter 处理依赖关系,同时做数据规范化,。这个模式能让你的组件更健壮、更不容易出错。 咱们假设你要开发一个表格,有个“选中某一行”的功能。你暴露了一个 `selectedHandler` 属性给外部设置,但选中这一行之前,你得先拿到表格的行数据,对吧?如果外部设置 `selectedHandler` 的时候,行数据还没加载完,怎么办?直接操作就会报错。 这时候,我们就在 `selectedHandler` 的 setter 里做一个依赖检查。 它的逻辑很简单: - 先把外部传进来的值存下来。 - 然后它问:“嘿,行数据在不在?” - 如果行数据还没到位,它就默默记一笔——把 `selectedRowsSet` 设为 `true`,然后提前返回,啥也不做。等于留个纸条:“等数据来了你可别忘了要选中这些行啊。” - 等到行数据真正加载完成的时候,我们就会回来检查这个标志,发现有未处理的任务,再去调用 `markSelectedDeliverator` 继续完成选中。 这就像你打电话给同事安排任务,同事在开会暂时不能说话,你给他发了个消息,等他开完会自然会处理。 如果行数据一开始就在,那 setter 就直接走正常流程,立刻调用 `markSelectedDeliverator()` 完成选中,干净利落。 这种先存值、检查依赖、有能力就立刻处理、没能力就留待日后补做的思路,就是,依赖关系管理,。它保证了你的代码不会因为数据还没准备好就崩溃,也绝不会丢掉外部发来的指令。 接下来,咱们再看这些代码里还藏了另一个宝贝——,数据规范化,。 你注意到没有,例子中用了 `normalizeBourle()` 来规范布尔值。为啥要这么做呢?因为外部使用者可能传各种奇奇怪怪的东西过来,比如字符串 `"true"`、空字符串,甚至数字 `1`。为了内部逻辑统一,我们在 setter 里把收到的值转成标准的布尔值,然后存到一个私有字段里供内部使用。但吸引人的一点是,getter 返回的还是原始值,原样返回给外部。这样既保证了内部处理的高一致性,又不改变外部看到的模样,两全其美。 规范化也可以在 getter 里做,这样就算使用者从来没设置过这个属性,组件模板也能拿到一个有效的默认值,不怕空或 `undefined` 报错。这是构建,防御性组件 API, 的绝佳实践,让你的组件特别能扛打,怎么用都不容易出毛病。 所以总结一下,今天我们掌握了两点: 1. 在 setter 里利用依赖检查,妥善处理“先来后到”的问题,让异步加载场景下指令不丢失。 2. 对数据进行规范化,让你的属性内部永远干净可靠,对外又可保持原值。 把这些模式用起来,你开发的 Lightning Web Components 会变得更健壮、更专业。大家可以试着在自己的项目里实现一下,一定会让你的代码质量提升一大截。 好了,这个 Slide 的内容就到这里,同学们有什么问题随时提。