DEX475

Composition

课程介绍

大家好啊,今天我们要来系统地学习一下如何编写Lightning Web组件。这节课内容丰富,但我会尽量用简单易懂的方式,一步一步讲清楚。记住,这些概念后面都有代码示例,你可以马上打开开发环境跟着练,理解会更深。 首先我们得搞清楚组件之间的关系。在LWC里,有四个关键角色:所有者、容器、父级和子级。简单理解啊,所有者就是创建这个组件的那个人,通常也是最先把它放进模板的那个组件;容器是直接把组件包在里面的组件;而父级和子级,就更直观了,一个包裹另一个。这几个角色的能力是不同的——比如父组件能传属性给子组件,但不能直接改子组件内部的东西;所有者有更大的控制权,比如决定子组件的创建和销毁。掌握这些层次,你才能把数据流理得清清楚楚。 好,接下来就是属性。当父组件给子组件传属性的时候,一定要区别对待原始值和非原始值。原始值,就像是字符串、数字、布尔值这些简单的数据,非原始值就是对象和数组。在LWC里,有个很重要的规则:子组件接收到的对象或数组,其实是只读代理。意思就是,你不能在子组件里直接修改这些从父组件传过来的对象。如果尝试修改,控制台会报错。这背后就是我们常说的单向数据流。 这个单向数据流,指的是数据只能从上往下走,从父组件流到子组件。子组件不能反过来直接改父组件的数据。那子组件想要反馈怎么办?就得通过事件,把要改的东西告诉父组件,让父组件自己去更新。这是保持组件可预测性的核心原则。 除了属性传值,父组件还能用 `@api` 来标记子组件里的公共方法。这样父组件就能通过模板引用,直接调用子组件暴露出来的方法,比如让子组件重置表单、聚焦输入框,这就是另一种主动通信的机制。 为了写模板更方便,咱们还有 `lwc:spread` 这个指令。它可以把一个对象里的所有键值对直接展开成子组件的多个属性,省去一个一个绑定的麻烦。就像一个超快速的批量设置工具。 事件监听方面,除了静态绑定,还可以用 `lwc:on` 来动态添加事件监听器。这在处理一些条件渲染或者动态生成的组件时特别有用,让代码更灵活。 接下来是插槽系统,这是组件组合的大头。LWC支持未命名插槽,就是默认的内容投射点;也支持命名插槽,让你能把内容精确分发到不同的位置。你还能用条件渲染决定插槽内容显不显示,甚至可以监听 `slotchange` 事件,知道插槽里的内容什么时候发生了变化。 我们还会把这种基于插槽的声明式合成,跟数据驱动的方法做个对比。声明式合成呢,直接通过插槽把现成的模板片段塞进组件,适合视觉布局强、内容结构固定的情况。数据驱动则是传数据进去,让组件自己决定怎么渲染,适合样式统一但数据多变的情况。两种方式各有各的好,要看场景选用。 最后,我们会看看如何查看组件的依赖关系。这能帮你理清项目中组件之间谁引用了谁,避免循环依赖,让结构更健康。 好了,这节课的脉络就是这样。每个部分我都准备了简洁的代码例子,大家跟着敲一敲,就会感觉清晰很多。我们接着开始深入第一节吧。

课程章节

本课程共有 32 个章节

  • 1

    What Is Composition?

    第 64 页

    同学们,我们来看组合这个概念。组合是Lightning Web Components最基本的一种设计模式,它的思路就是把一些简单的小组件拼在一起,像搭积木一样,逐步搭出复杂的界面。你可以在一个组件的标记里面,直接放进去另一个组件。 虽然技术上来说,LWC是支持继承的,但Salesforce非常强烈地建议大家使用组合。组合通常效率更高,也避免了继承带来的一堆限制。特别是,在Lightning命名空间下面,那些标准组件你是不能用继承来扩展的。如果你真的想在不同的命名空间之间玩继承,就必须先开启Lightning Web Security,也就是LWS。而且就算开了LWS,你也永远没法去扩展Lightning命名空间。那如果要在组件之间共享逻辑怎么办呢?最好把共享的代码抽出来,放到一个独立的JavaScript模块里,然后导入到各个组件里用,而不是用继承。 那么,在实际做组合的时候,模板所有者,也就是我们常说的父组件,会把子组件嵌套在自己的容器里。父组件可以很方便地给子组件设置属性、调用它暴露出来的方法,也可以把属性值直接传播过去,甚至可以把一段标记内容传送到子组件的插槽里。插槽就像是子组件预留好的一个占位区域,父组件可以把设计好的界面片段填进去。 简单讲,组合就是靠嵌套和配置来复用功能,比继承灵活得多。记住这个原则,以后开发的思路就会更清晰。

    查看详情
  • 2

    Compose Components — Basic Pattern

    第 65 页

    同学们,今天我们要聊聊 LWC 中的一个核心概念——组件组合。 你可以把它理解成搭积木。一个大的组件页面,是由很多小零件拼起来的。这就是基本的“成分模式”。 在 Lightning Web Components 里,系统已经给我们准备好了很多现成的基础组件,都在 `lightning` 这个命名空间下,比如按钮、输入框、卡片等等。你只需要直接拿过来用就行。 那具体怎么拼呢?我们通过一个经典的待办事项应用(todoApp)来理解。它里面清楚地展示了三个角色: 第一个是,所有者,,也就是我们的 `c-todo-app` 这个顶层组件。它就像整个积木城堡的底板,掌控着一切,拥有自己的模板,所有布局、逻辑都由它说了算。 第二个是,容器,,叫 `c-todo-wrapper`。它虽然被包含在所有者里面,但它自己内部也包含了其他组件,就像一个中转箱,负责把一堆小零件组装好再放进大框架里。 第三个是,子级,,例如 `c-todo-title`,它是最末端的叶子组件,只负责展示一条标题,不会再包含别的东西了。 为了让你看得更清楚,咱们这个例子里面用的是写死的静态值。但在真实项目里,肯定不会这么傻,对吧?通常你会从所有者的 JavaScript 文件里拿到一个数据集合,然后用 `for:each` 这种循环指令,动态地把列表渲染出来。 如果你想看更复杂的组合方式,可以到 GitHub 上去找 Salesforce 官方的 `lwc-recipes` 仓库,里面有很多高级的示例,你可以去扒一扒。 好,这部分就讲到这里。组合是 LWC 组件化开发的基石,你掌握它之后,就能开始像拼乐高一样构建复杂的应用界面了。

    查看详情
  • 3

    Owner — The Template Owner

    第 66 页

    同学们,我们继续来看Lightning Web Components中一个非常核心的概念——所有者,英文叫Owner。你可以把它想象成这个组件小王国里的国王,是所有嵌套组件的“老板”。 那么在todoApp这个例子里,谁是所有者呢?就是最外层的那个`c-todo-app`。它的模板里放了很多子组件,比如`c-todo-item`,所以它就是所有者,是拥有模板的那个组件。 所有者有三个很厉害的功能,我们一个一个说。 第一个,,它可以直接通过HTML属性去设置任何子组件的公开属性。, 比如说,子组件有一个用`@api`声明的title属性,所有者在自己的模板上就这么写:`<c-todo-item title="买牛奶"></c-todo-item>`,数据就直接传进去了,简单又直接。 第二个,,它可以拿到子组件的引用,然后直接调用子组件的方法。, 怎么拿呢?用模板查询。在JavaScript里,我们写`this.template.querySelector('c-todo-item')`,这样就能抓到子组件的实例。然后,如果子组件用`@api`公开了一个叫`refresh`的方法,我们就可以直接调用它,像`child.refresh()`。这里注意一下,slides上可能印的是querytext,那是因为笔误,咱们记住是`template.querySelector`,标准的Web API。 第三个,也是最强大的一个,,所有者可以监听子组件触发的任何事件。, 比方说,子组件里用户点了个按钮,它用`this.dispatchEvent`发了一个自定义事件叫做`close`。那所有者只要在模板里用`onc lose`这种声明式的监听,或者在自己的JavaScript里用`addEventListener`,就能立刻收到通知,然后想干什么就干什么。这样一来,子组件里发生了什么,所有者完全看得见。 你看,这三个功能结合起来,所有者就成了一个完美的协调器——它控制数据往下流,处理从下冒上来的事件,让所有嵌套的组件按一个统一的节奏来工作。这就是LWC里所有者模式的核心思想,是不是很清楚?好,我们继续看下一页。

    查看详情
  • 4

    Container — Contained Within the Owner

    第 67 页

    同学们,我们接下来聊聊组件之间的一种特殊关系——容器、所有者和子组件。想象一下,一个页面结构就像一棵树,根组件在最上面,下面挂着各种子组件。在这棵树上,有时会出现一个处于中间的组件,我们叫它“容器”。容器夹在所有者和子组件之间,它比所有者更“强大”一些,因为它可以直接调用子组件上的方法,就和自己拥有子组件一样。 但是,这种强大有两条严格的规矩,这正是LWC单向数据流的核心设计。 第一条,容器只能读,不能改。它可以访问子组件暴露出来的公共属性,但绝不能修改它们。任何属性的变更,必须由真正的所有者来执行,就像只有物品的主人才有权决定换掉它一样。 第二条,关于事件监听。容器只能接收那些通过DOM正常冒泡上来的事件。如果子组件发出了一个不冒泡的,或者在中途被停止冒泡的事件,容器是根本感知不到的。这就好比在一栋楼里,容器只听得见通过楼梯层层上传的喊话,如果有人关了房间门小声嘀咕,它就听不见了。 为什么要这样设计呢?是为了防止“中间人乱插手”。如果每个容器都能随意改子组件的状态,数据流会变得不可预测,很容易产生难以调试的副作用。所以,LWC强制只有组件的创建者——也就是真正的所有者,才有资格修改内部数据,从而保证整个应用的数据流动是清晰、单向的。这样我们写代码时心里就更有底,知道变化只会来自明确的地方。 好了,这就是关于容器权限边界的解释,大家理解了吗?

    查看详情
  • 5

    Parent and Child

    第 68 页

    我们来讲一下 Lightning Web Components 里面,父组件和子组件是怎么配合工作的。 你可以这样理解,父组件和子组件就像是一对直接的包容关系。想象一个俄罗斯套娃,大娃里面装着小娃,父组件就是那个大娃,子组件就是被装在里面的小娃。怎么装进去的呢?很简单,就是在父组件的 HTML 模板里,直接用子组件的标签把它放进去。就像写普通的 HTML 元素一样,比如你有一个子组件叫 `<c-child>`,那你在父组件的模板里直接写上 `<c-child></c-child>`,这就建立起了父子关系。 那这两个组件在项目里是怎么放的呢?在我们的 Salesforce DX 项目结构里,它们通常在同一个 `lwc` 文件夹下面,但是各住各的文件夹。每个组件都有自己的小窝,里面装着它自己的 HTML 模板、JavaScript 逻辑文件,还有一个可选的 CSS 样式文件。为什么要分开放呢?这就是为了强制执行封装的概念:每个组件都独立管理自己的长相、行为和样式,互不干扰。子组件不会去乱动父组件的东西,父组件也不能随便改子组件的内部逻辑。 这种物理上的分离,保证了封装性,而组件之间的组合,就是通过父模板里的那个标签包含来完成的。所以,整个组件树就是在模板这一层,用标签包含的方式一层一层搭起来的。这就是 LWC 里父子组件的基本构成了,简单、清晰,也很容易维护。

    查看详情
  • 6

    Set Properties on Child Components

    第 69 页

    同学们好,今天我们来聊聊在 Lightning Web Components 里,父组件怎么把数据传给子组件。主要的方法,就是通过设置子组件的属性。 你看,在一个父组件的模板里,我们会给子组件的标签写一些属性,比如 `<c-child my-value="hello">`。那这个 HTML 属性,到了子组件里,会直接变成对应的 JavaScript 属性。换句话说,`my-value` 就会映射到子组件类的 `myValue` 这个属性上。注意啊,JavaScript 习惯用驼峰命名,而 HTML 属性要求用短横线分隔的小写,也就是 kebab-case。这是 LWC 自动帮我们做的转换,但咱们写代码的时候,脑子里得有这根弦。 接下来重点来了,传数据时,原始值和非原始值的表现完全不同。什么是原始值呢?就是字符串、数字、布尔值,这些简单的家伙。你直接设一个字符串 `"张三"`,或者数字 `25`,子组件拿到立刻就能用,非常直觉,不会出什么意外。 但如果是对象和数组这一类非原始值,那规矩就变了。它们被传到子组件时,会被包装成只读的代理对象。说白了,就是你没法直接修改它里面的某个字段,比如 `this.person.name = "李四"`,或者往数组里直接 `push`。这么干,LWC 不但不会更新父组件的数据,控制台还会给你一个警告。这其实是框架的刻意设计,是为了保证数据流的清晰,避免子组件偷偷篡改父组件状态。 那如果我真想在子组件里改动这个对象,该怎么办呢?正确姿势是——先做一个浅拷贝。用扩展语法最方便,比如 `let newPerson = { ...this.person }`,然后去改这个副本,`newPerson.name = "李四"`,最后把整个新对象重新赋给属性:`this.person = newPerson`。数组也一样,先拷贝再修改,然后再赋值回来。只有这样,才符合 LWC 的数据更新机制,框架也才能追到变化,重新渲染。 简单总结一下,记住两点:第一,属性命名,JS 是驼峰,HTML 是横线;第二,传对象数组时它们是只读的代理,要改就先展开拷贝一份,再整体替换。掌握这个,父传子的数据通道就用得很顺了。 好了,这一页的知识点就是这些,大家可以在脑子里过一遍例子,加深印象。有什么疑问,随时提出来。

    查看详情
  • 7

    Set a Primitive Value on a Child

    第 70 页

    同学们,我们今天来看一下,在Lightning Web Components里,怎么从一个父组件给子组件传一些简单的原始值。 这个过程其实非常简单。首先,在子组件里面,你要告诉外界:“我这个属性是公开的,父组件可以给我传值。”怎么做呢?你只需要在类里面,用一个叫`@api`的装饰器,来声明一个公共属性。比如,我们定义一个`itemName`字段,加上`@api`,这就意味着,父组件可以设置这个`itemName`的值了。 那么,父组件怎么传值呢?它是在自己的HTML模板里,通过子组件的标签来设置的。这里有一个命名规则要注意:在HTML模板里,属性名要用“短横线分隔”的形式,也就是kebab-case,比如`item-name`;而到了JavaScript里,它会自动映射成驼峰命名的`itemName`。所以,你在父模板里写`<c-child item-name="Milk"></c-child>`,子组件里的`itemName`就会收到这个字符串“Milk”。 在我们这个例子里,为了讲清楚,我直接用了一些静态值,比如“Milk”和“Bread”。但真实项目里,肯定不会这么写死,数据一般都是动态的。通常你会用到`for:each`这样的迭代指令,从一个数据集合里循环渲染出子组件,然后分别给它们传不同的值。 如果你想看更完整的案例,可以到GitHub上找lwc-recipes仓库,里面有`compositionBasics`这个组件,场景更丰富。或者你也可以直接在lwc.dev的交互式游乐场里动手试试,加深理解。 好,关于设置原始值,就是这么简单。我们下个小节再见。

    查看详情
  • 8

    Set a Non-Primitive Value — Parent Side

    第 71 页

    同学们,今天我们来看一个非常重要的概念:父组件向子组件传递对象或数组这类非原始值的时候,背后到底发生了什么。 想象一下,在父组件的 JavaScript 里,我们定义了一个简单的对象,比如给它一个 msg 属性,值是 "hello"。然后在父组件的 HTML 模板中,用花括号语法把它传给子组件,就像这样写:`<c-child data={obj}></c-child>`。 为了让我们看得更清楚,父组件还会有一个叫 serializedObj 的 getter,它内部只是调用了 JSON.stringify 把这个对象转成字符串,再显示在界面上。这样,当对象发生变化时,我们能立刻看到效果。 好,关键点来了。当这个对象真的传给子组件的时候,子组件拿到的并不是原始对象本身,而是一个,只读代理,。这是 LWC 框架偷偷做的一层包装。 只读代理就像一个透明的保护壳,它会拦截所有试图修改数据的操作。假如子组件里有人写了 `this.data.msg = 'world'`,这个赋值不会生效,甚至连嵌套属性的修改都会被阻止。这样做,就是为了强制单向数据流——数据只能从父流向子,子不能反过来偷偷改父的数据。 换句话说,父组件始终是这份数据的唯一主人,拥有绝对的控制权。子组件只能读,不能直接写。如果需要修改,子组件必须通过发送事件,去通知父组件来改。 记住这个设计,它让我们的组件关系更清晰,应用的状态也更可预测、更好调试。大家好好体会一下这只可爱的“只读代理”。

    查看详情
  • 9

    Set a Non-Primitive Value — Child Side & Mutation Rules

    第 72 页

    我们现在来深入聊聊一个在LWC构图中超级重要的模式,它可能会让你在刚开始接触组件通信时感到有点困惑,但搞懂之后就一通百通了。 想象一下,你有一个父组件,把一个对象传给了子组件。子组件通过 `@api obj` 来接收这个对象,然后在界面上显示它的某个属性,比如 `obj.msg`。接着,你在子组件里放两个按钮,一个叫“更新原始”,一个叫“更新浅”。当你点击“更新原始”时,代码试图直接修改 `this.obj.msg`,比如把它改成 `this.obj.msg + ’!’`。结果呢?浏览器控制台马上会甩给你一个错误,上面写着“无效变异”。这是为什么呢? 因为在LWC里,从父组件传递过来的非原始类型的值,比如对象和数组,会被自动包装成一个只读的代理。你可以把它想象成外面套了一层透明的防护罩,你可以读取它,但不能直接更改它内部的嵌套属性。所以,想通过 `this.obj.msg = ...` 这种方式去修改,是行不通的。这是为了强制单向数据流,保证数据的变化是可追踪的。 那我们想要更新这个对象该怎么办呢?这就是“更新浅”按钮要做的事。它的做法是使用展开运算符,创建一个新的对象副本,像这样:`{...this.obj, msg: this.obj.msg + ’!’}`,然后把这个新对象重新赋值给 `this.obj`。因为你现在是替换了整个对象的引用,而不是修改原有对象的内部属性,所以完全没问题,框架允许。 但是,这里藏着一个很容易踩的坑,也是一种非常重要的理解:你这种做法,仅仅是在子组件内部创建了一个浅拷贝,并且只更新了子组件自己的副本。父组件手上持有的那个原始对象,并没有发生任何改变。也就是说,子组件的视图看着变了,可父组件那边还是老样子。这通常不是我们想要的效果,因为如果父组件还要依据这个数据做别的逻辑判断,就会出现不一致的状况。 所以,正确的、完整的协作流程应该是:如果子组件需要让父组件也知道这个变化,那么子组件在本地更新完副本后,必须派发一个自定义事件,把修改后的数据传出去。父组件监听这个事件,拿到新数据后,用它去更新自己数据源里的那份原始对象。这样,两端就同步了,整个数据流动也是清晰、可控的。 简单总结一下:不能直接突变父组件传下来的对象属性;要先拷贝再替换;浅拷贝只是本地更新,要同步给父组件,必须通过事件。这就是LWC里处理对象传递的核心模式,希望你以后在设计组件时,心里一直记着这条流水线。

    查看详情
  • 10

    Data Flow — One-Way from Parent to Child

    第 73 页

    同学们,咱们今天聊一个很重要的概念,叫单向数据流。这是 Lightning Web Components 框架里一个核心的设计原则,就像盖房子的地基一样,理解了它,写代码才能更稳当。 简单说,单向数据流就是——数据应该,永远从父组件流向子组件,,不能反过来。打个比方,就像瀑布,水只能从上往下流,不可能往回流。父组件拥有数据,它可以把数据传给子组件,但子组件不能直接去改父组件的数据。 那子组件怎么接收数据呢?通过被 ,@api, 装饰的属性。比如父组件把某个值传给子组件,子组件用 @api 声明一个公开属性来接收。但注意,这个属性只能在初始化的时候由父组件设置一次。之后呢,只有拥有这个数据的父组件才有权修改它。 那如果子组件需要改变这个数据怎么办?比如用户在子组件里做了个操作,需要更新父组件的某个状态。这时候不能直接赋值,而是要派发一个事件上去,通知父组件:“嘿,我要改数据了。” 父组件收到事件后,自己动手去改自己的数据。数据一变,因为单向绑定,这个新值又会自动流向子组件,页面就更新了。 为什么搞这么麻烦?直接让子组件改父组件的数据不是更省事吗?其实,这是为了避免代码复杂和意想不到的副作用。如果是双向绑定,子组件改了数据,父组件可能完全没察觉,状态悄悄变了,bug 就难找了。而单向数据流让数据的变动路径清清楚楚,谁拥有数据谁负责改,其他组件只是用和通知。这样代码更容易理解、调试和维护。 所以记住三点:第一,数据只从父流向子;第二,子组件通过 @api 接收数据,但自己不直接改;第三,要改数据,就向父组件发事件。牢牢把握这个原则,你的组件通信就会清晰又安全。 好了,单向数据流就讲到这儿,有什么问题可以随时问。

    查看详情
  • 11

    Objects Passed to Components Are Read-Only

    第 74 页

    同学们,我们接着来看这个 slide 里讲的一个非常重要的安全机制:传递给组件的非原始值,像是对象和数组,它们的只读性质到底是怎么被 LWC 框架强制执行的。 想象一下,父组件把一个对象——比如一个用户信息对象——传给子组件,子组件非常自然就想直接修改里面的某个属性,比如把用户名改掉。但在 LWC 里,如果你不加处理直接这么干,框架会直接给你报一个错误。这是为什么呢? 答案就藏在一个叫 ,JavaScript 代理, 的东西里。当我们把对象或数组从父组件传到子组件的时候,LWC 框架并不会直接把原始数据交过去,而是悄悄地在外面包了一层 ,Proxy,。这层代理就像一个严格的保安,它会拦截所有试图在嵌套属性上进行的赋值操作。一旦你尝试去修改对象的某个属性,比如直接写 `this.user.name = '新名字'`,这个保安就会立刻跳出来,抛出一个错误,阻止修改。 那么,如果你确实需要更新这个数据,该怎么办呢?唯一的办法就是:对整个属性重新赋一个新值。这里的关键是“整个属性”,而不是去动它里面的某个字段。 你可以怎么做呢?要么你从头创建一个全新的对象,比如 `this.user = { name: '新名字', age: 30 }`;要么用我们熟悉的展开运算符,也就是 spread 操作符,做一个浅拷贝,像这样:`this.user = { ...this.user, name: '新名字' }`。这里要特别提醒一下,这种浅拷贝只复制顶层属性,如果对象里面还嵌套着别的对象,那么这些嵌套对象依然是原来的引用,它们并没有被复制。 拥有数据的组件,也就是最初创建这个对象或数组的那个组件,才有权利自由地重新给这个属性赋值。子组件只是接收方,并不拥有数据,所以不能偷偷去改内部细节。这个限制听起来有点麻烦,但它其实是 LWC 安全架构里非常关键的一环。有了这一层基于代理的保护,整个组件树的数据完整性就被牢牢锁住了,不会因为某个小组件的无心之失,把其他组件依赖的数据悄悄改坏,让整个应用变得难以排查。 所以,大家只要记住:,传入的对象是只读的,想更新就整个换掉,浅拷贝只复制第一层,。这样写出来的组件才安全、可预测,也更符合 LWC 最佳实践。好,这个 slide 的内容我们就先讲到这里。

    查看详情
  • 12

    Use Primitive Values for Public Properties

    第 75 页

    同学们,我们来看一个非常重要的 Lightning Web Components 最佳实践。Salesforce 官方明确建议:在声明公共的 `@api` 属性时,尽量使用原始数据类型,比如字符串、数字、布尔值,而不是直接传递对象或者数组。 为什么要这样呢?咱们一条一条说。 第一点,用单个的原始类型属性,数据形状一目了然。什么意思呢?比如你在组件上定义一个 `@api title`、一个 `@api count`,别人一看就知道这个组件需要一个标题和一个数量,它们是自我说明的,不用额外解释。这就好像交给你一个盒子,上面贴着标签写着“杯子”,你马上就知道里面是什么。 但是,如果你直接传一个对象进去,比如传一个叫 `config` 的东西,里面藏着很多信息,那消费者就得去看外部文档,才能弄明白这个对象里面到底该放哪些字段。一旦你某天修改了对象的内部形状,比如重命名了一个字段,所有用你这个组件的地方就可能悄悄崩溃,而且最糟糕的是,编译阶段根本不会给任何警告。这种隐性的 bug,排查起来非常头疼。 第三点,也是特别容易被忽视的:标准 HTML 元素只认原始值的属性。你看普通的 `<img>` 标签,`src` 是一个字符串,`width` 是一个数字。你从来没见过哪个原生 HTML 元素通过属性接收一个大对象。那种通过 `@api` 传对象的模式,其实是 Lightning Web Components 特有的,它并不符合 Web 标准。 那 Web 标准在需要复杂结构的时候怎么办呢?它用子元素。想想表格:`<table>` 自己不接受一个巨大的数组,而是靠 `<tr>` 和 `<td>` 这样的子元素来构建行和列。再比如 `<select>`,它的选项是通过一个个 `<option>` 子标签来定义的,而不是一个属性丢进去一个列表。LWC 是 Web 标准的一部分,我们的组件也应该遵循这样的模式。 所以,正确的做法是,在架构的更上层,把那些复杂的对象或数组“切片”,拆解成一个一个简单的数值,然后通过属性向下传递到底层的子组件里去。这样一来,每个组件职责清晰,接口稳定,也更贴近 Web 本身的思路。 好,这个要点就讲到这里。下次设计公共 API 的时候,记得多用原始类型,多用子组件来承担复杂结构,你的组件就会变得更健壮、更好维护。

    查看详情
  • 13

    Call Methods on Child Components

    第 76 页

    好,我们来看一下父子组件之间怎么用方法进行通信。 在 Lightning Web Components 里,有时候父组件需要直接调用子组件里的某个方法,这就像是家长叫孩子去做一件事情。这时候我们就用 `@api` 这个装饰器,把它放在子组件的方法前面,这个方法就变成了公开的 API,父组件可以访问到它。 那父组件怎么找到子组件呢?它会用 `this.template.querySelector`,就像在 DOM 树里根据选择器定位到这个子组件的元素,然后拿到它的引用,接着就可以直接点出那个公开的方法,像这样:`this.childComponent.someMethod()`。 这里有一个很重要的设计思路,就是“方法向下流动,事件向上冒泡”。简单来说,父控制子用方法,子通知父用事件。这是一种非常清晰的单向数据流模式,能帮你避免代码乱成一团。 你可以在官方提供的 lwc-recipes 示例库里找到一个叫 `apiMethod` 的组件,它就是用这种方式,父组件调用了一个时钟子组件的方法,很直观。 不过要注意一点,`this.template.querySelector` 只会在组件自己的模板范围内查找,不会穿过影子 DOM 的边界。也就是说,如果子组件内部还嵌套了更深层的组件,你是访问不到的。它只在当前组件的那层模板里找。 好,这样我们就理解了怎么用 `@api` 装饰器暴露子组件方法,以及父组件怎么去调用它。这是 Lightning Web Components 组件之间通信里很常用的一种方式。

    查看详情
  • 14

    Define a Method — videoPlayer Example

    第 77 页

    今天咱们来看一个具体的例子,看看在 Lightning Web Components 里怎么给子组件定义一套完整的公开接口,也就是公共 API。这个例子是一个视频播放器组件,叫 videoPlayer。 首先,它对外公开了一个叫 videoUrl 的属性,这个属性接收视频文件的链接。然后呢,它还公开了一个叫 isPlaying 的 getter,用来告诉外界当前视频是不是正在播放。这个 getter 内部是去查 DOM,看看视频元素的状态。另外,它还有两个公开的方法,一个叫 play,一个叫 pause,用来控制播放和暂停。 那这些公开的方法是怎么实现的呢?它们在组件自己的模板里,通过 this.template.querySelector 找到真正的 <video> 这个 DOM 元素,然后直接调用浏览器原生的 HTML5 视频 API,也就是 video.play() 和 video.pause()。这里有一个很重要的细节,就是在调用方法前会先判断一下找到的 video 元素是不是存在,比如写 if (player)。为什么需要这个空检查呢?因为当外部调用 play 或 pause 的时候,有可能组件的模板还没有渲染完,video 元素还没有出现在 DOM 里,如果不检查就直接调用,就会报错。 同时,这个组件还有一个私有的 getter,叫 videoType,它没有被 @api 修饰,所以只能在组件内部自己使用,从外面是访问不到的。这就很清晰地划分了组件的公共接口和内部实现——哪些是给外部用的,哪些是内部处理细节,一目了然。 所以这个 videoPlayer 的例子真的很典型,它向我们展示了如何用 @api 装饰器暴露属性和方法,如何在公共方法里查询 DOM 并安全调用原生 API,以及如何把内部逻辑藏起来。这种设计让组件既好用又健壮,是我们在 LWC 开发中经常用到的模式。

    查看详情
  • 15

    Call a Method — methodCaller Example

    第 78 页

    好,同学们,我们现在来看一个非常实用的小模式:,父组件怎么去调用子组件里的方法,。想象一下,你家里有一个智能音箱,音箱自己当然有播放和暂停的功能,但按钮却装在墙上的遥控器上——遥控器就是父组件,音箱就是子组件,遥控器得能告诉音箱“开始播放”或者“暂停”。 在我们这个例子里,父组件的模板上放了一个 `c-video-player` 子组件,还有两个按钮:一个写着“播放”,一个写着“暂停”。当你点击“播放”按钮时,父组件的 JavaScript 代码会做一件事:先用 `this.template.querySelector('c-video-player')` 找到那个子组件元素。这就好比遥控器朝音箱发射红外信号,准确锁定目标。拿到引用之后,直接调用 `.play()` —— 这个 `play` 可不是普通的方法,它在子组件里被 `@api` 装饰器标记成了公共方法,专门开放给外面用的。同理,点击“暂停”按钮,就调用 `.pause()`。 这里要强调一点:真实的视频播放器组件通常自己就带一套控制条,根本不用把按钮拆到外面。但我们故意把按钮放在父组件里,纯粹是为了教学,让你看清,从组件外部调用子方法,的这套机制。`this.template.querySelector` 就是标准路线,记住它,以后你想让一个组件去指挥另一个组件做事,就用这个套路。好,这个小技巧你掌握了吗?

    查看详情
  • 16

    Return Values, Parameters & Query Selectors

    第 79 页

    同学们,我们来看这一页,重点讲讲公共方法怎么返回值,还有查询选择器的使用和注意事项。 在 Lightning Web Components 里,你定义的公共方法完全可以像普通 JavaScript 那样,使用标准的 return 语句返回一个结果。比如,我们可以定义一个叫 isPlaying 的 getter 访问器,它里面用 return 返回一个布尔值,告诉外部当前视频到底是不是在播放。这样外界直接通过属性方式就能拿到状态,但其实背后是调用了一个方法。方法也可以接收参数,比如你可以写一个 play(speed) 方法,让调用方传一个速度值进来,灵活控制播放速度。 接下来谈谈查询选择器。浏览器提供了两个常用的 DOM API:一个是 querySelector,它会返回找到的第一个匹配元素;另一个是 querySelectorAll,返回的是所有匹配元素的静态 NodeList,也就是一份固定的列表快照,后续 DOM 变动不会自动反映到这个列表里。 但是,这里有一个非常非常重要的警告,千万要留心:在 Lightning Web Components 里,绝对不要在 querySelector 或 querySelectorAll 中使用 id 属性作为选择器!为什么?因为框架在渲染组件时,会自动把你写的 id 值转换成一个全局唯一的标识符,这样做是为了避免不同组件之间的 DOM 冲突。这导致你 JavaScript 里写的那个 id 选择器,在运行时根本匹配不到转换后的 id。那怎么办呢?我们应该改用 CSS 类选择器,比如 .my-class,或者利用自定义的 data-* 属性,比如 [data-id="myId"] 来定位元素。这样才是安全可靠的做法。 记住这一点,能帮你避免很多意想不到的 bug。好了,这部分就讲到这里。

    查看详情
  • 17

    Spread Properties — lwc:spread

    第 80 页

    同学们,今天我们来聊聊 Lightning Web Components 里一个很贴心的小指令——`lwc:spread`。 你有没有遇到过这样的情况?你有一个子组件,它暴露了很多 `@api` 属性,比如名字、年龄、地址、电话等等。父组件从后端拿到一个 JavaScript 对象,正好包含了这些字段,难道你要在模板里一个一个手动写属性吗?不仅麻烦,还容易漏掉或出错。这时候,`lwc:spread` 就是你的好帮手。 它的作用很简单:让你直接把一个对象“展开”,然后一次性传递给子组件。你只需要在子组件标签上加一个 `lwc:spread={对象}`,LWC 就会自动把对象的第一层属性,和子组件 `@api` 声明的属性名做匹配,并一一赋值过去。 举个例子,假设你有个子组件叫 `my-child`,它公开了 `name` 和 `age` 两个属性。父组件里你拿到一个对象 `person = { name: '小明', age: 25 }`,你就不必这样写: ```html <my-child name={person.name} age={person.age}></my-child> ``` 而可以简化为: ```html <my-child lwc:spread={person}></my-child> ``` 是不是清爽多了?尤其当配置对象有很多字段时,这个功能简直是救星。 不过用的时候有几个规矩要记住。第一,`lwc:spread` 只展开对象的 ,第一层, 属性。如果对象里面还有嵌套对象,LWC 可不会自动帮你一层层拆开,你得自己提前处理好,或者让子组件接收一个对象类型的属性。 第二,也是比较关键的一点:`lwc:spread` 总是 ,最后应用,。这是什么意思呢?意思是,如果你在同一个子组件标签上,既直接写了某个属性,又在 spread 对象里包含了同名的属性,那么最终生效的,会是 spread 对象里的值,因为它后“覆盖”上去。 比如你这样写: ```html <my-child name="小红" lwc:spread={person}></my-child> ``` 而 `person` 对象里也有 `name: '小明'`,那最终传给子组件的 `name` 还是 “小明”。这可能跟直觉有点反,但记住这个规则就好:“spread 说了算,因为它最后动手。” 第三,每个 HTML 元素上,你 ,只能用一次, `lwc:spread`。想传多个对象,需要先自己在 JavaScript 里把对象合并好。 总结一下,`lwc:spread` 非常适合那些需要传递一大串属性的场景,代码更干净,维护也更省心。要是你想看实际代码案例,可以到 Salesforce 官方的 `lwc-recipes` 仓库里搜一下 `apiSpread` 组件,跟着敲一遍印象会更深刻。 好了,关于 `lwc:spread` 我们就讲到这,下节课见!

    查看详情
  • 18

    lwc:spread — Override Behavior & Event Handlers

    第 81 页

    今天我们来讲一个很容易被忽略,但特别重要的细节,就是 `lwc:spread` 这个指令的覆盖行为。想象一下你在写一个组件,父组件往子组件传属性的时候,可能会直接在模板上写 `name="lwc"`,同时又用 `lwc:spread` 传了一个对象,对象里也有个 `name` 属性,值是 “Lightning Web Components”。这个时候你觉得子组件收到的 `name` 会是哪个呢?答案是:会收到 “Lightning Web Components”,而不是 “lwc”。 为什么会这样呢?因为 `lwc:spread` 是在所有你直接写在模板上的属性之后才应用的。它总是最后生效,所以如果两边有冲突,spread 对象里的值会直接覆盖掉之前定义的同名属性。听起来有点霸道,对不对?但这恰恰就是它的设计意图——让你能灵活地一次性传递一组动态属性,并让它们拥有最高优先级。所以如果你的代码里同时用了静态声明和 spread,一定要留心这个覆盖逻辑,不然可能调试半天都找不到原因。 那我们再看另一种情况,当你用 spread 传递事件处理函数的时候。假设你在 spread 对象里写了一个 `onClick` 属性,值是父组件里的一个方法。这完全可以,因为 `lwc:spread` 支持把函数引用作为属性值传递。但是这里有一个坑:它不会自动帮你绑定组件里的 `this` 上下文。也就是说,如果这个函数内部用了 `this`,直接传递的话 `this` 很可能不是你的父组件实例。解决方法也很简单,要么在传递时用 `.bind(this)`,要么把那个方法定义成箭头函数。这样当事件触发的时候,回调函数就能拿到正确的 `this`,在父组件的上下文中执行了。 总结一下,就是两句话:第一,spread 总是最后一个进场,冲突时它说了算;第二,传函数时记得手动绑定上下文,别让 `this` 跑丢了。记住这些小细节,你就能把 `lwc:spread` 用得稳稳当当。

    查看详情
  • 19

    lwc:spread — Reflect HTML Attributes

    第 82 页

    今天我们来聊聊 lwc:spread 的一个实用特性——HTML 属性反射。 简单说,就是当你用 lwc:spread 把一堆属性传给子组件时,那些在原生 HTML 里能自动反射的属性,全都会正常生效。比如,你传一个 className 属性,在子组件的真实 DOM 元素上,它就会自动变成 class 这个 HTML 属性。同理,你传 id,它就直接变成元素上的 id 属性。这其实和普通 Web 开发里,DOM 属性跟 HTML 属性保持同步的标准行为一模一样。 那这个功能有什么用呢?你可以在父组件里动态地控制子组件的 CSS 类、ID,还有其它标准 HTML 属性,不需要在子组件里额外写逻辑。比如你根据某种状态改变 className,子组件的样式就跟着变了,非常方便。 记住一个小规则:这个反射完全跟着 HTML 规范走。也就是 className 映射到的是 class,而不是 className 或者 Class(大写 C 的不行)。所以命名时照着 JS 里的 DOM 属性名写,它就会自动对应到正确的 HTML 属性上。 用这个特性,就能很优雅地处理动态样式和原生属性了。

    查看详情
  • 20

    Add Event Listeners Dynamically — lwc:on

    第 83 页

    同学们,今天我们来看一下LWC里一个非常灵活的功能——使用 `lwc:on` 指令来动态地给子组件绑定事件监听器。 这个 `lwc:on` 一般会和 `lwc:spread` 配合使用。简单来说,`lwc:spread` 是用来动态传递属性的,而 `lwc:on` 就是专门用来动态传递事件处理函数的。你可以在父组件里准备好一个叫 `eventHandlers` 的对象,这个对象的键是事件类型的名称,值就是对应的处理函数。然后通过 `lwc:on` 把这个对象传给子组件。 举个实际场景:父组件想要监听子组件发出的一个叫 `customEvents` 的自定义事件。当子组件内部触发这个事件时,父组件里对应的处理函数就会被调用。处理函数会收到一个标准的事件对象,而子组件传过来的自定义数据,都放在 `event.detail` 里。比如,处理函数可以从 `event.detail` 里解构出消息文本、姓名和年龄,然后更新父组件自己的状态。 父组件状态一更新,就会触发重新渲染,页面上就能显示出子组件发来的消息。同时,父组件也可能把更新后的数据再以属性的形式传回给子组件。这样一来,父子之间就形成了清晰的通信闭环——子组件通过事件把数据抛上来,父组件通过属性把数据传下去。 这种模式虽然实现了双向通信,但依然保持着单向数据流的原则:数据总是从父流向子,而事件从子流向父。所以结构很清楚,调试起来也很方便。 好,关于 `lwc:on` 和 `lwc:spread` 的配合使用,我们就先聊到这里。大家可以在实际项目里试试,感受一下这种灵活的事件绑定方式。

    查看详情
  • 21

    lwc:on — Child Component Implementation

    第 84 页

    今天我们要聊的是子组件如何跟父组件沟通,尤其是在两个不同生命周期点触发自定义事件。这是一种很干净的亲子通信模式,咱们一点点来理解。 首先,子组件一连接到DOM,也就是它的connectedCallback被调用那一刻,它会立马派发一个自定义事件。这个事件里带着它的初始数据,比如姓名、年龄,说不定还有一句问候语。你可以想象,孩子刚出门见到家长,就主动说:“你好,我叫小张,今年10岁。”这就是第一次沟通。 然后,过一会儿,用户可能在界面上点了个按钮,比如说一个闪电按钮。这时候,子组件会再派发另一个自定义事件,这次传的就是更新后的数据:姓名可能变成了“LWC”,年龄变成了“8”。就像孩子长大了一点,改了名字又过生日,跑去告诉家长:“我改名叫LWC啦,现在我8岁了。” 在这两个事件里,数据都是怎么传的呢?用的是CustomEvent构造函数,里面有个detail属性,专门用来装你要传递的数据负载。就像个小包裹,里面塞满了孩子要告诉家长的东西。 那父组件怎么接收呢?很简单,父组件的模板里用lwc:on指令来监听。就像家长竖起耳朵,孩子一喊就听到,然后马上更新自己的状态——比如在DOM上显示新的名字和年龄。 整个流程就是:孩子调度事件,家长用lwc:on处理。这么做既实现了父子组件之间的实时、反应式通信,又牢牢遵守了单向数据流原则——数据还是从父亲往下流,孩子只是通过事件请求家长更新,这很干净、很清晰。记住这个模式,后面做复杂交互会非常顺手。

    查看详情
  • 22

    Pass Markup into Slots — Overview

    第 85 页

    同学们,今天我们来聊聊 Lightning Web Components 里面一个很贴心的设计,叫做“插槽”,也就是 slot。 你可以把插槽想象成子组件留给父组件的一个“空位”。以前我们往子组件里传的都是数据,但现在,插槽能让你直接把活生生的 HTML 标记、DOM 元素传进去。这意味着父组件不仅能给数据,还能直接决定子组件某一块区域长什么样。 具体怎么实现的呢?子组件的模板里,用 `<slot>` 标签占个位置,就像在说:“这儿有个空缺,谁来填?” 当父组件使用这个子组件,并且把内容写在它的标签之间的时候,这段内容就会在渲染时,自动替换掉那个 `<slot>` 占位符。父组件给了什么,子组件里面就显示什么。 一个组件可以不设插槽,也可以设很多个插槽。插槽主要分两种类型: 第一种是“未命名的插槽”,也就是默认插槽。子组件模板里就写一个光秃秃的 `<slot>`,不指定名字。父组件在子组件标签之间随便放什么标记,只要没有特别指定,就全都会掉进这个默认插槽里。 第二种是“命名插槽”。子组件里写 `<slot name="header">`,给它起个名字,比如叫 header。父组件想往这个特定位置填内容,就要在自己写的元素上,加上 `slot="header"` 这个属性来配对。只有名字匹配上了,内容才会灌到对应的槽里。这样一来,父组件就能精准控制子组件里不同区域的显示了。 好,接下来有几个重要限制,你一定要记住,别踩坑。 首先,你不能把 Aura 组件直接塞进插槽里,这行不通。其次,就算你的 LWC 本身支持插槽,但如果这个 LWC 是包裹在 Aura 组件里面的,它也不能被当作内容传给其他组件的插槽。原因在于 Aura 和 LWC 的渲染机制有沙箱隔离,跨框架的 DOM 传递是被阻断的,所以这点在设计混合架构时要格外当心。 最后,如果你在用 Experience Cloud 的 LWR 站点,插槽还有一个很棒的用途:它可以直接变成 Experience Builder 里的拖放区域。业务人员不用写代码,就能在页面构建器里把组件拖到这些插槽位置上,实现灵活的内容布局。 好了,关于插槽的机制、类型和几点注意事项就讲到这里。简单总结一下:插槽让父组件可以把标记传给孩子,分清默认槽和命名槽,同时牢记不能跨 Aura 传递,并且在 LWR 站点里它还是个可拖放的构建区域。搞懂这些,你在设计灵活通用组件的时候就会更加得心应手了。

    查看详情
  • 23

    Unnamed Slots

    第 86 页

    好,同学们,咱们今天来看一个特别基础但又非常重要的概念,叫做未命名插槽。 你可以把它想象成子组件给自己留的一个“内容入口”。很简单,就是子组件在它的模板里放一个 `<slot></slot>` 标签,不需要给它起名字。这就好像在说:“将来会有人在这里放东西,但我现在还不知道是什么。” 那父组件怎么用呢?父组件只需要把自己想要的内容,直接写在子组件的开始和结束标签之间就行了。比如,父组件写 `<c-child><p>你好</p></c-child>`。渲染的时候,这个 `<slot>` 就会被替换成父组件传过来的 `<p>你好</p>`,最终页面就显示出“你好”这句话,而且还是在子组件原本模板结构的那个位置里。 不过这里有个很容易踩的坑,你一定要注意。如果一个子组件里不小心放了多个没名字的 `<slot>`,那会怎么样?结果是,父组件传进来的同一份内容,会同时填充到所有这些 `<slot>` 里。这基本上不是我们想要的效果,往往会导致页面上重复显示,逻辑混乱。 所以,最佳实践很明确:每个组件最多只保留一个未命名插槽。如果你发现自己需要把不同内容塞到组件里的不同位置,那就该用命名插槽了,我们后面会讲到。 好,这节课就到这里,记得动手试试看,有什么问题随时问我。

    查看详情
  • 24

    Named Slots

    第 87 页

    同学们,我们来看一下命名插槽。在Lightning Web Components里,有时候我们想在子组件中留好几个“洞”,每个洞放不同的内容,这时候命名插槽就派上用场了。 简单说,命名插槽让组件能定义多个不同的插入点。你在子组件的模板里用 `<slot>` 元素,并且给它一个 `name` 属性,比如 `name="firstName"`,这就成了一个具名插槽。没有名字的 `<slot>` 就是默认插槽。 然后,父组件在使用这个子组件的时候,通过给要插入的内容加上 `slot` 属性,把内容精确地送到对应的具名插槽里去。比方说,子组件里定义了 `firstName`、`lastName` 这两个具名插槽,还有一个默认插槽。父组件就可以这么写:外面套一个 `<c-example>` 标签,里面放三个 `<span>`,第一个写 `<span slot="firstName">Willy</span>`,第二个写 `<span slot="lastName">Wonka</span>`,第三个什么 `slot` 都不写,直接放 “Chocolatier”。这样一来,Willy 会进到 firstName 插槽,Wonka 进到 lastName 插槽,而 Chocolatier 因为没有指定插槽,就自动落入默认插槽。 每个命名插槽还可以放默认内容。什么意思呢?就是在子组件里写 `<slot name="firstName">默认名字</slot>`,这样万一父组件忘了提供对应 `slot="firstName"` 的内容,那“默认名字”就会显示出来。这对做容错、让组件更健壮非常有用。 还有两个重要的细节要记住。一个是父组件那边的 `slot` 属性可以动态绑定,你可以写 `slot={dynamicName}`,dynamicName 可以是一个 JavaScript 表达式,只要它的计算结果是字符串就行。但注意,虽然能动态,子组件里 `slot` 元素上的 `name` 属性必须是静态的字符串文本,不能动态变化。也就是说 `<slot name={someVar}>` 是行不通的,必须直接写死,比如 `name="header"`。 再有就是类型转换:父组件传给 `slot` 属性的动态值会被强制转成字符串。如果传了一个没办法转成字符串的类型,比如 Symbol,就会当场抛出类型错误。所以在动态绑定的时候,务必确保值能正常转成字符串。 好,命名插槽就讲到这里,它让我们在组合组件时布局更加灵活,内容分发也特别清晰。同学们可以自己动手试试,就能更深刻地理解啦。

    查看详情
  • 25

    Access Elements Passed Via Slots

    第 88 页

    好,我们来讲一个LWC里操作DOM时特别容易踩的坑,但是一旦搞懂了,你就能更清楚地掌握组件内部和外部的边界。 你看,咱们组件模板里如果用了`<slot>`,这个`<slot>`标签本身是待在组件自己的“影子树”里面的。所以如果你想找到这个slot元素,就要用 `this.template.querySelector`,这没问题,因为template指向的就是你的影子DOM。 但关键是,那些从父组件投递进来、落在slot位置上的内容——也就是我们说的“slotted元素”,它们并不属于你的组件。它们原本就是父组件的东西,只是暂时被投射到你这里来显示而已。所以这些元素的“家”还在父组件的DOM作用域里,我们叫它“轻量级DOM”。 那怎么在自己组件里安全地拿到这些投射进来的元素呢?这时候你就得用 `this.querySelector`,注意,这里,不要,加那个 `.template`。直接 this.querySelector 就能访问到这些轻量级DOM元素。 好,另一个常问的问题是:在哪里做这个查询最靠谱?答案是 `renderedCallback` 这个生命周期钩子。因为当你跑到 renderedCallback 里的时候,框架已经保证那些投射的内容已经被渲染到DOM里了,这个时候去查,肯定找得到,不会扑个空。 还有一个小提醒:无论是查哪种元素,都尽量别用 id 选择器。这是因为LWC框架会对id值进行转换,确保在全局唯一,你写的id最后可能会变成别的,所以直接用id定位容易失效。用类选择器或者其他属性的方式更稳妥。 所以总结一下——模板自身的slot,用 `this.template.querySelector`;投射进来的“客人”,用 `this.querySelector`;查询的最佳时机是 `renderedCallback`;避免用id选择器。这几个点记住了,以后处理slot相关的DOM访问就不会乱了。

    查看详情
  • 26

    Render Slots Conditionally

    第 89 页

    同学们,今天我们来看一个关于Lightning Web Components里插槽渲染的小知识点,简单但很重要。 你在写组件时,有时会希望在某个条件下,往插槽里放不同的内容,对吧?这就是“条件插槽渲染”。LWC专门提供了现代的指令来处理这个场景,就是lwc:if、lwc:elseif和lwc:else。用它们来包裹插槽,编译器就能做静态分析,保证在同一时间只有一个分支是活跃的。这样一来,那个被渲染的插槽元素最多只会出现一次,不会出现重复投影的问题。 那什么叫重复投影呢?可以想象一下,如果你用了旧的指令,比如if:true和if:false,它们的表达式通常是一个getter函数。这个getter在不同时间调用,可能返回不同的值,编译器没法确定它到底会渲染几次。于是,同样的插槽内容就可能被投影到父组件里好几次,导致界面上出现重复元素,甚至引发一些难以排查的bug。所以,如果你试图把遗留指令用在插槽上,编译器会直接给出警告。 因此,大家记住:当你要对插槽做条件渲染时,永远只用现代的lwc:if系列指令。别再用那个老式的if:true和if:false了,它们不具备这个保障。这样你的组件逻辑才能清晰,渲染结果也稳定可靠。 简单吧?记得在实战中坚持这个习惯,能帮你避开很多坑。好了,这个小点就讲到这里。

    查看详情
  • 27

    Run Code on slotchange

    第 90 页

    咱们今天来聊聊 `slotchange` 这个事件。你可以把它想象成插槽里的“保安”,专门盯着插槽门口进出了哪些直接孩子。 首先,什么时候会触发这个事件呢?很简单——当插槽里的,直接子元素,被添加或者移除的时候就会触发。注意啊,是“直接子元素”,也就是直接放在 `<slot></slot>` 标签之间的那些节点。比如你动态地往插槽里扔一个新的 `<div>`,或者把原本在里面的一个 `<p>` 删掉,这个事件就会立刻通知你。 但是,如果插槽里的某个子元素自己内部发生了改动——比方说它自己改了个文本、换了个页脚,但没有被从插槽里移除或新增——那 `slotchange` 就不会理睬。因为它只关心插槽的直接孩子变没变,不关心孙子辈的内容。 那这个事件怎么传播呢?它会在 DOM 里向上冒泡,但有一条重要的红线:它,不会穿过阴影边界,。也就是说,只有拥有这个插槽的组件自己才能监听到这个事件。外层的组件是收不到的。这听起来像限制,但实际正是我们想要的效果:只有父组件需要对插槽内容的变化做出反应,从而管理里面子组件的生命周期,比如初始化或者销毁一些东西。 所以总结一下,`slotchange` 就是我们用作管理插槽内容生命周期的主要工具。当你在父组件里监听到这个事件时,就可以安全地对新进来的子组件做处理,或者清理已经离开的子组件。记住这一点,处理动态插槽就顺手多了。

    查看详情
  • 28

    Compose Components — Slots vs Data

    第 91 页

    同学们,我们来看一下这一页的内容:Lightning Web Components 中,有两种根本不同的方法来组合你的组件。 第一种,我们叫它,声明性方法,,也就是用插槽。什么意思呢?就是父组件直接在它的模板里,把孩子组件写进去,就像搭积木一样。父组件写上标签,把孩子放进去,然后利用插槽的变更来管理孩子的生命周期。这特别适合做布局和分组,比如闪电按钮组`lightning-button-group`,你直接在内部写一堆按钮,它帮你排列好。消费者一看模板就知道,“哦,这里放的是什么组件”。 第二种,是,数据驱动的方法,。这种方法里,父组件给子组件传递的是配置数据,一个 JavaScript 对象。子组件自己不决定内容,它只负责对父组件传过来的数据变化做出反应。循环迭代、展示大量内容时特别好用。典型的例子是`lightning-datatable`,你给它一个很大的数据数组和列定义对象,它就把表格渲染出来了。 关键区别在哪呢?你要选哪种方法,就问自己一个问题:消费者是在决定,“哪些组件放在这里”,?那就要用声明性的插槽合成。还是消费者在描述,“哪些数据来描述这个东西”,?那就要用数据驱动的合成。前者关心标记和结构,后者关心配置和状态。用对方法,代码会更清晰,也更符合框架的设计思路。 拿小例子来说,你做工具栏,让用户自己放按钮,用插槽最直观。但你做一个信号灯,颜色和闪烁频率全是数据控制的,那就用数据驱动,传个配置对象进去就好。理解这个选择,你就能写出更好维护的 Lightning Web Components。

    查看详情
  • 29

    slotchange Event Pattern — button-group Example

    第 92 页

    同学们,今天我们来看一个声明性合成中特别经典的例子——按钮组模式。 想象一下,你有一个父组件,比如一个闪电按钮组,它里面有一个插槽,用来容纳子按钮。这些子按钮通过插槽投射进来,父组件需要知道它们的位置,然后自动给第一个按钮、最后一个按钮、中间的按钮,或者唯一一个按钮,加上不同的 CSS 类,从而让它们的样式变得统一又美观。 具体是怎么做到的呢? 父组件会监听插槽的 slotchange 事件。一旦插槽内容发生变化,事件处理函数就会被调用。这时,你要记住一个关键点:事件的目标(event.target)就是那个 slot 元素。在这个 slot 元素上调用 assignedElements 方法,才能拿到实际投射到插槽里的那些真实的 DOM 元素。注意,不是从事件对象上直接取,而是调用 slot.assignedElements()。 拿到这些子元素之后,父组件就会遍历它们,判断每一个是第一个、最后一个、中间的唯一一个,还是中间的某个按钮,然后给它们设置对应的样式类。 那问题来了,父组件怎么知道每一个子按钮的生命周期状态呢? 这就需要子组件配合了。每个闪电按钮在它自己的 linkedCallback 生命周期钩子里,会触发一个自定义的 privatebuttonregister 事件,并且把一个回调函数传给父组件。父组件用这个回调来记录这个子按钮,并管理它的顺序。 反过来,如果某个按钮从 DOM 中被移除了,子组件的 disconnectedCallback 就会被调用,这时它会主动调用之前注册好的取消注册回调,通知父组件“我走了”,这样父组件就能及时更新自己的记录和样式。 这样一来,通过插槽、slotchange 事件和子组件的注册回调,整个按钮组就能优雅地、声明式地管理好子元素的样式和生命周期。这种方式非常灵活,是 LWC 中组件合成的一个优秀实践。 好了,这个例子就讲到这里,大家可以动手试试,感受一下声明式合成的魅力。

    查看详情
  • 30

    Data-Driven Composition Approach

    第 93 页

    同学们,咱们接着聊Lightning Web Components里的一个重要模式——数据驱动的方法。你可以把它理解为“老虎机”插槽的一个替代方案。 先回忆一下,传统的老虎机模式,是由父组件直接把标记内容塞进子组件的插槽里。但数据驱动的方法不一样,它不是传标记,而是传数据。父组件手里握着一个数组,比如叫itemsData,里面每个对象都包含标签、Id、以及是否选中这类状态。然后,父组件的模板用for:each指令去循环这个数组,为每一个数据对象生成一个子组件,同时通过属性把那一项的数据传下去。这样一来,子组件就很纯粹了:它不需要去维护什么内部状态,它只负责忠实地渲染接收到的数据,当数据变的时候,它自然就跟着变。 那交互逻辑怎么办?注意,像点击事件的处理,我们是定义在父组件里的。子组件被点击,会触发父组件传进去的事件处理函数,父组件直接去修改自己数组里的对应项的选中状态。因为数据变了,框架会让子组件自动更新。所以整体是单向数据流,非常清晰。 为什么推荐这种方法呢?尤其是在像lightning-datatable这种复杂组件里,表格的列定义、行数据、排序状态等等,本质都是配置型的,你如果硬用标记去表达成百上千的单元格,那会非常麻烦。而用数据驱动,你只需要准备好数据,组件就会自动渲染出来,维护起来也简单。 总结一下,数据驱动的方法让父组件掌控全局状态,子组件变成无状态的“画板”,这样可以大大降低复杂度。在处理那些高度依赖于配置的场景时,它比老虎机模式更灵活、更易维护。好,这个概念大家理解了吧?

    查看详情
  • 31

    View Component Dependencies

    第 94 页

    同学们,今天我们来了解一个非常实用的小工具,叫做,组件依赖关系查看器,。在我们开发Lightning Web组件的时候,经常会碰到一个页面里嵌套了很多层的组件,想搞清楚它们之间的父子关系、调用链,如果一个个打开文件去看,会很花时间。而这个查看器就像一张自动画好的地图,能帮你一眼看清组件的层次结构,省时省力。 那它在哪里打开呢?很简单,进入“设置”(Setup),在快速搜索框里输入“Lightning Components”并选中它,就能看到你所有的自定义组件列表。每一个Lightning Web组件的行前面,都有一个像小三角一样的V形图标,点一下它,这行就会展开,显示出这个组件下面直接引用的子组件,还有这些子组件引用的孙组件,一直到曾孙组件。总体来说,它默认会展示三层深的树形结构,也就是组件自己、它的孩子和孙子、以及曾孙。 如果你发现嵌套比三层还要深,也别担心,直接在树上点击那个嵌套组件的名字,页面就会自动跳转到那个组件自己的依赖关系视图,你可以继续一层层往下看,非常方便。 这个树不仅显示你写的自定义LWC组件,还会把你组件里引用的Apex类也显示出来。这样一来,不管你是接手别人的代码,还是要修改一个复杂的功能,都能快速评估改动一个组件可能会影响到哪些地方,做好影响分析。这对于维护大型项目特别有帮助,能避免因为遗漏依赖而产生的bug。 好了,这就是组件依赖关系查看器的用法。建议大家课后动手去自己的环境里点一点,感受一下这个工具的便利。下一节我们继续讲别的调试和开发技巧。

    查看详情
  • 32

    Composition Best Practices Summary

    第 95 页

    同学们,咱们现在翻到这一页——这是关于Lightning Web Components里组件合成的最佳实践总结,特别重要,大家一定要记牢。 首先,记住一个大原则:,组合优于继承,。啥意思呢?就是当我们想复用代码的时候,尽量把一个小组件嵌到另一个组件里面去用,而不是搞那种父类子类的继承链。这样做更灵活,也不会因为跨命名空间产生各种奇怪的冲突,而且更符合现代组件设计的思路。 接着是数据流动的方向——记住,,数据只能从父组件流向子组件,,这是单向的。千万不要试图让子组件偷偷去改父组件的数据,那样会产生非常难查的副作用。我们得靠规矩来保持整个应用的稳定。 为了让组件的接口干净,给子组件暴露属性的时候,,尽量用原始类型,,比如字符串、数字、布尔值。因为这些是最标准、最清晰的。如果你传了一个对象或者数组过去,那这些非原始值会被框架用一个只读的代理包起来,你没法直接修改它们。如果你想在子组件里改,只能做一个浅拷贝,修改这个拷贝,而且这个修改不会影响父组件里的原数据。 那子组件如果真想告诉父组件“我这儿有变化了”,怎么办呢?用,事件,。子组件派发一个事件,父组件监听它,然后在父组件那边去改数据,改完再通过属性重新传给子组件。这样数据的所有权始终在父组件,传播方向就保持了自上而下。 在模板里,我们有几个好用的工具:想一口气传多个属性给一个子组件?用 `lwc:spread`,它能把一个对象里的所有属性都展开传过去。要动态绑定事件监听器?用 `lwc:on`。想做一些声明式的插槽组合?就用 slot 和 `slotchange` 事件。 如果遇到比较复杂的场景,别硬套,要选择,数据驱动,的思路——也就是根据数据来动态决定渲染什么、怎么组合。比如基于列表数据循环生成子组件,而不是靠写死一堆模板。 再说几个小细节:如果你要在 JavaScript 里访问插槽里的元素,务必用 `this.querySelector` 或者 `this.querySelectorAll`,,不要用 id 选择器,,因为组件可能在同一页被复用多次,用 id 会导致冲突。最后,如果你要根据条件决定是否渲染带插槽的内容,记得用 `lwc:if` 指令来条件性渲染 slot,这样更安全高效。 好啦,这些就是合成最佳实践的要点,大家把它们当成写组件时的“军规”,可以避免未来很多坑哦。

    查看详情