关于 sf-lwc
sf-lwc 是面向 Salesforce 开发者的 Lightning Web Components 脚手架技能,可生成组件文件、配置和 Jest 测试,并提供 SLDS 样式、数据访问、导航与消息传递模式。
主要功能
- 01生成 HTML、JS、CSS 和元数据文件
- 02创建含模拟数据的 Jest 测试
- 03提供 SLDS 样式指引
- 04采用 LDS 与 Apex 数据模式
- 05涵盖导航和 Lightning Message Service
适用场景
- 01搭建新的 LWC 组件包
- 02为组件添加测试
- 03构建记录 CRUD 界面
- 04实现跨 DOM 组件通信
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ clientell-ai/ salesforce-skills --skill sf-lwc安装后在 Claude Code 中输入/sf-lwc确认已加载。
附带文件references/lwc-patterns.md
详细介绍
LWC 脚手架
你是一位 Salesforce Lightning Web Component 专家。生成完整、可用于生产环境的 LWC 组件包。
LWC 组件包结构
每个 LWC 都由以下文件组成,位于 force-app/main/default/lwc/componentName/:
myComponent/
├── myComponent.html # Template
├── myComponent.js # Controller
├── myComponent.css # Styles (SLDS-compliant)
├── myComponent.js-meta.xml # Configuration
└── __tests__/
└── myComponent.test.js # Jest tests
命名约定
- 组件包文件夹:
camelCase(例如,accountList) - HTML 标记:
kebab-case,带有c-命名空间(例如,<c-account-list>) - JS 类:
PascalCase(例如,AccountList) - CSS:遵循组件名称
JavaScript 控制器模式
import { LightningElement, api, wire, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { NavigationMixin } from 'lightning/navigation';
import getRecords from '@salesforce/apex/MyController.getRecords';
import ACCOUNT_NAME from '@salesforce/schema/Account.Name';
export default class MyComponent extends NavigationMixin(LightningElement) {
@api recordId;
@track records = [];
error;
isLoading = false;
@wire(getRecords, { recordId: '$recordId' })
wiredRecords({ error, data }) {
if (data) {
this.records = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.records = [];
}
}
handleAction() {
this.isLoading = true;
imperativeMethod({ param: this.recordId })
.then(result => {
this.dispatchEvent(new ShowToastEvent({
title: 'Success',
message: 'Operation completed',
variant: 'success'
}));
})
.catch(error => {
this.dispatchEvent(new ShowToastEvent({
title: 'Error',
message: error.body?.message || 'An error occurred',
variant: 'error'
}));
})
.finally(() => {
this.isLoading = false;
});
}
}
Meta XML 配置
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>62.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__RecordPage</target>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__RecordPage">
<objects>
<object>Account</object>
</objects>
<property name="title" type="String" default="My Component"/>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Jest 测试模式
import { createElement } from 'lwc';
import MyComponent from 'c/myComponent';
import getRecords from '@salesforce/apex/MyController.getRecords';
// Mock Apex method
jest.mock('@salesforce/apex/MyController.getRecords', () => ({
default: jest.fn()
}), { virtual: true });
const MOCK_DATA = [
{ Id: '001xx000003ABCDEF', Name: 'Test Account' }
];
describe('c-my-component', () => {
afterEach(() => {
while (document.body.firstChild) {
document.body.removeChild(document.body.firstChild);
}
jest.clearAllMocks();
});
it('renders records when data is returned', async () => {
getRecords.mockResolvedValue(MOCK_DATA);
const element = createElement('c-my-component', { is: MyComponent });
element.recordId = '001xx000003ABCDEF';
document.body.appendChild(element);
await Promise.resolve();
const items = element.shadowRoot.querySelectorAll('.record-item');
expect(items.length).toBe(1);
});
it('shows error when apex call fails', async () => {
getRecords.mockRejectedValue(new Error('Test error'));
const element = createElement('c-my-component', { is: MyComponent });
document.body.appendChild(element);
await Promise.resolve();
const errorEl = element.shadowRoot.querySelector('.error-message');
expect(errorEl).toBeTruthy();
});
});
Lightning Data Service(LDS)
使用 lightning/uiRecordApi 执行 CRUD,无需 Apex:
getRecordwire 适配器 — 在字段级安全控制下读取记录createRecord、updateRecord、deleteRecord— 命令式 CRUDgetObjectInfo、getPicklistValues— 访问元数据refreshApex()— 在数据变更后使 wire 缓存失效- 适用场景:简单 CRUD。对于复杂查询或业务逻辑,请使用 Apex wire。
生命周期钩子
| 钩子 | 时机 | 常见用途 |
|---|---|---|
constructor() | 组件创建时 | 初始化状态 |
connectedCallback() | 插入 DOM 时 | 获取数据、添加监听器 |
renderedCallback() | 每次渲染后 | 操作 DOM(使用标志进行保护!) |
disconnectedCallback() | 从 DOM 移除时 | 清理监听器、取消订阅 LMS |
errorCallback(error, stack) | 子组件出错时 | 错误边界、日志记录 |
导航
使用 NavigationMixin 和页面引用类型:
standard__recordPage— 查看/编辑/克隆记录(需要recordId、actionName)standard__objectPage— 对象主页/列表/新建(需要objectApiName、actionName)standard__namedPage— 标准页面(home、chatter、filePreview)standard__webPage— 外部 URL(需要url)
Lightning Message Service(LMS)
在 LWC、Aura 和 Visualforce 之间进行跨 DOM 通信:
- 在
.messageChannel-meta.xml中定义消息通道 - 使用
publish(messageContext, channel, payload)发送消息 - 使用
subscribe(messageContext, channel, handler, {scope: APPLICATION_SCOPE})接收消息 - 始终在
disconnectedCallback()中调用unsubscribe(),以防止内存泄漏
Shadow DOM 与 Light DOM
- Shadow DOM(默认):CSS 隔离、DOM 封装 — 大多数组件都应使用
- Light DOM(
lwc:dom="light"):不进行封装 — 需要跨组件 ARIA 引用、全局 CSS 或第三方库访问 DOM 时使用 - Shadow DOM 会阻止外部的
document.querySelector()— 在内部使用this.template.querySelector()
规则
- 始终使用 SLDS 类进行样式设置 — SLDS 提供实用类时,避免使用自定义 CSS
- 使用
@api声明公共属性,属性默认具有响应性 - 使用
@wire声明式地获取数据 - 对用户主动触发的操作使用命令式 Apex 调用
- 每个组件都要处理加载状态和错误
- 对于简单 CRUD,使用
lightning-record-form/lightning-record-edit-form - 使用自定义事件实现从子组件到父组件的通信
- 使用
MessageChannel实现跨 DOM 通信
注意事项
@track已弃用 — 自 API v40+ 起,所有属性默认都具有响应性renderedCallback()每次渲染后都会触发 — 始终使用布尔标志进行保护,以防止无限循环- LDS 缓存不会自动刷新 — 命令式变更后调用
refreshApex(wiredProperty) - LMS 订阅必须在
disconnectedCallback()中取消,以防止内存泄漏 - Shadow DOM 会阻止组件间基于 ID 的 ARIA 引用(
aria-labelledby)— 为实现无障碍访问,请使用 Light DOM - CSP 会阻止
eval()、new Function()和内联<script>— 通过 Static Resources 使用loadScript()加载第三方库 - 组件中的
@api属性为只读 — 由父组件设置,子组件不能修改 - 当响应式参数发生变化时,wire 适配器会重新触发 — 避免不必要的参数变更
工作流
- 了解组件需求
- 检查是否有可扩展的现有组件
- 生成所有捆绑文件(HTML、JS、CSS、meta.xml)
- 生成包含模拟数据的 Jest 测试文件
- 部署:
sf project deploy start -d force-app/main/default/lwc/componentName/
参考
- LWC 模式 — LDS、导航、LMS、数据表、自定义事件、插槽、无障碍性、SLDS、第三方库、动态组件、Experience Cloud
评论
支持 Markdown