JavaScript — Salesforce LWC JavaScript 开发完全指南

LWC JavaScript 完整参考。涵盖 ES6 必备特性(Array/Arrow/Class/Module/Promise/async-await)、代码共享(同文件夹模式与 API 模块组件、默认/命名导出、编译器入口点、循环导入识别与解决)、第三方库集成(静态资源、platformResourceLoader、lwc:dom='manual'、D3 完整示例、Locker/LWS 合规)、API 调用(Salesforce LDS/Apex、Fetch API 与 CSP)、动态组件完整指南(lwc:component/lwc:is/async-await/属性传递/事件监听/性能/最佳实践),以及 TypeScript Developer Preview(设置/编译/Jest 测试/类型定义/限制)。...

📅 2026/7/19 ✍️ ponybai 🏷️ lwc, salesforce, javascript

JavaScript

JavaScript

每个 Lightning Web Component 必须有一个 JavaScript 文件,且必须是 ECMAScript 6 (ES6) 模块。本章覆盖代码共享、第三方库、API 调用、动态组件和 TypeScript 等完整的 JavaScript 参考。

LWC 开发必备 JavaScript 特性

JavaScript 必备特性

高效的 LWC 开发需要熟悉以下 ES6 特性:

  • 数组方法:Array.prototype.mapfilterreduce —— 数据转换的核心工具
  • 箭头函数:简洁语法 + 词法 this 绑定,模板和回调中特别有用
  • 类(Classes):ES6 class 语法,每个组件继承 LightningElement
  • 模块(Modules):import/export 用于代码组织和共享
  • 对象方法:Object.keysObject.values 操作数据结构
  • Promise:异步操作,现代更推荐 async/await
  • 变量声明:const(不可变)、let(可变),避免 var
async/await 优先:lwc-recipes 和本指南中的示例已广泛采用 async/await 替代原始 Promise。async/await 更直观、像同步代码、与 try/catch 天然兼容。

共享 JavaScript 代码 —— 概述

共享 JavaScript 代码

通过 ES6 模块共享代码,每个 JS 文件有 1 MB 大小限制。LWC 支持两种共享模式

模式 1 —— 同文件夹(内部组织):在同组件文件夹中创建 .js 文件,用相对路径导入("./utils")。只有该组件能导入——用于组件内部代码组织,不能跨组件共享。

模式 2 —— API 模块组件(跨组件共享库):创建不含 HTML 文件的组件文件夹作为库。其他组件用 c/componentName 语法导入。只能从主 JS 文件导入(与文件夹同名的文件)。要从补充文件暴露代码:先导出到补充文件 → 再从主文件重新导出。

// 同文件夹模式
import { getAmount, calculateCost } from "./utils";
import myFunction from "./myFunction";

// API 模块组件模式
import { getTermOptions, calculateMonthlyPayment } from "c/mortgageUtils";
// ✅ c/mortgageUtils(文件夹名)
// ✗ c/mortgageUtils.js(不能加扩展名)
// ✗ c/mortgageUtils/other(不能访问其他文件)

API 模块组件 —— mortgageUtils 完整示例

API 模块组件

API 模块组件是跨组件共享代码的推荐方式。核心要求:文件夹名和主 JS 文件名必须一致。

lwc/
  ├── mortgageUtils/           ← API 模块组件
  │   ├── mortgageUtils.js      ← 主文件(与文件夹同名)
  │   └── mortgageUtils.js-meta.xml
  └── myComponent/
      ├── myComponent.html
      ├── myComponent.js        ← 在此导入
      └── myComponent.js-meta.xml

// myComponent.js
import { getTermOptions, calculateMonthlyPayment } from "c/mortgageUtils";
关键规则:import 中指定文件夹名,不能加文件扩展名。不能从不同名的补充文件或嵌套文件夹中导入。需要暴露补充文件中的代码时,先重新导出到主 JS 文件。

导出默认函数和变量

导出默认

ES6 模块可以导出单个默认函数或变量。默认导出适用于模块只有一个主要用途的场景。

// myFunction.js
export default function () { /* 函数体 */ }
// 或:export { myFunction as default, ... };

// myComponent.js —— 导入时可以起任意名字
import myFunction from "./myFunction";  // 名字随意,不一定匹配原名

导入者自己选择导入名称——不一定要匹配原函数名或文件名(虽然通常这样约定)。

导出命名函数和变量

导出命名

ES6 模块可以导出多个命名函数或变量。导入时名字必须精确匹配

// mortgage.js
const getTermOptions = () => {
  return [
    { label: "20 years", value: 20 },
    { label: "25 years", value: 25 },
  ];
};
const calculateMonthlyPayment = (principal, years, rate) => { /* 计算逻辑 */ };
export { getTermOptions, calculateMonthlyPayment };

// myComponent.js —— 名字必须精确匹配
import { getTermOptions, calculateMonthlyPayment } from "c/mortgage";
// 可以只导入子集,可以用 as 重命名:
import { getTermOptions as options } from "c/mortgage";

LWC 编译器如何解析入口点

编译器入口点

LWC 模块 c/moduleName 只暴露一个文件作为入口点:

  1. moduleName.js 存在 → 它是入口点(JS 优先)
  2. 否则 moduleName.css 是入口点(纯 CSS 模块)
  3. 两者都不存在 → 编译失败
// ✅ 有效——从模块名导入
import { getSomething } from "c/utils";  // 解析为 utils.js

// ✗ 全部无效
import { getSomething } from "c/utils/utils.js";   // 不能加扩展名
import { getSomething } from "c/utils/other.js";   // 不能指定其他文件
import { getSomething } from "c/utils/utils";      // 不能显式指定文件名

访问补充 JavaScript 文件中的导出

补充文件导出

补充 JS 文件的文件名与模块名不同。通过 export-from 语法在主文件中重新导出:

// moreUtils.js(补充文件)
const someFunction = () => { /* 逻辑 */ };
export { someFunction, someOtherFunction };

// utils.js(主文件——重新导出)
export { someFunction, someOtherFunction } from "./moreUtils";
// 或用通配符:export * from "./moreUtils";

// someComponent.js——像在主文件中一样导入
import { someFunction } from "c/utils";
注意:重新导出只是让外部消费者能用——函数不会自动在 utils.js 内部可用,除非另外加 import。

避免循环导入

避免循环导入

LWC 不支持模块间的循环导入。循环导入发生时,一个模块在其依赖完成评估前加载,导入的名字解析为 undefined

常见症状:TypeError: X is not a functionCannot read properties of undefined、组件单独渲染正常但组合后失败、平台升级后突然出现错误。

// ❌ 直接循环:alpha → beta → alpha
// c/alpha/alpha.js
import { useBeta } from "c/beta";
// c/beta/beta.js
import { useAlpha } from "c/alpha";

识别与解决循环导入

解决循环导入

如何识别:从失败的模块开始,追踪其 import → 继续追踪每个被导入模块的 import → 直到回到起点(确认循环)或穷尽所有路径。

四种解决方案:

  1. 提取共享代码到第三个模块 —— 最常用、最干净的方案
  2. 合并模块 —— 紧密耦合的模块合并为一个
  3. 反转依赖 —— 以函数参数或组件属性传递依赖,而非 import
  4. 移除不必要的 import —— 重构后残留的过时 import

使用第三方 JavaScript 库 —— 概述

使用第三方库

前提:先检查 AppExchange基础组件是否有现成方案。

完整步骤

  1. 下载第三方库
  2. 上传为静态资源(Content Security Policy 要求)
  3. 在组件中导入import myLib from "@salesforce/resourceUrl/myLib"
  4. 导入加载方法:import { loadStyle, loadScript } from "lightning/platformResourceLoader"
  5. 异步加载loadScript(this, myLib + "/myLib.js").then(() => { /* 使用库 */ })

合规要求:LWS 未启用时库必须满足 Lightning Locker 要求;LWS 启用时大多数库无需修改即可工作,但显式设置 "use strict" 的库可能需要调整。

DOM 操作 —— lwc:dom="manual"

lwc:dom=manual

不推荐用 JavaScript 直接操作 DOM——LWC 引擎效率更高。但 D3 等可视化库需要直接 DOM 控制。默认情况下 appendChild() 操作的元素不会应用样式

解决方案:在需要手动操作的空原生 HTML 元素上加 lwc:dom="manual"

<template>
  <div lwc:dom="manual"></div>
</template>

引擎看到此指令后保留该容器内的 CSS 封装,手动添加的元素会正确应用样式。注意:Salesforce 不提供对第三方库的官方支持。

D3 示例 —— 模板与设置

D3 示例 —— 模板与设置
<!-- libsD3.html -->
<template>
  <div class="slds-m-around_medium">
    <svg class="d3" width={svgWidth} height={svgHeight}
        lwc:dom="manual"></svg>
  </div>
</template>

<svg> 作为 D3 力导向图容器。lwc:dom="manual" 是关键——告诉 LWC 此元素 DOM 由 D3 手动管理。参考实现:lwc-recipes libsD3

D3 示例 —— JavaScript(一)加载库

D3 JS 加载
/* global d3 */
import D3 from "@salesforce/resourceUrl/d3";
import { loadScript, loadStyle } from "lightning/platformResourceLoader";

d3Initialized = false;  // 防止重渲染时重复初始化

async renderedCallback() {
  if (this.d3Initialized) return;
  this.d3Initialized = true;
  try {
    await Promise.all([
      loadScript(this, D3 + "/d3.v5.min.js"),
      loadStyle(this, D3 + "/style.css"),
    ]);
    this.initializeD3();
  } catch (error) {
    this.dispatchEvent(new ShowToastEvent({
      title: "Error loading D3", message: error.message, variant: "error",
    }));
  }
}

关键模式:/* global d3 */ 告诉 ESLint d3 是全局变量;② d3Initialized 标志防止重复初始化;③ Promise.all 并行加载脚本+样式;④ try/catch 优雅处理加载失败。

D3 示例 —— JavaScript(二)初始化可视化

D3 JS 初始化
initializeD3() {
  const svg = d3.select(this.template.querySelector("svg.d3"));
  // 使用 this.template.querySelector()——不能用 document.querySelector()!
  const simulation = d3.forceSimulation()
    .force("link", d3.forceLink().id(d => d.id))
    .force("charge", d3.forceManyBody())
    .force("center", d3.forceCenter(width / 2, height / 2));
  // ... 创建 links (lines) 和 nodes (circles),添加 drag 行为
}
LWC 关键模式:在 LWC 中不能使用 document 查询 DOM——必须用 this.template.querySelector() 将查询限制在组件自己的 Shadow DOM 内。

加载库的实用模式

实用加载模式
// 模式 1 —— 仅加载 JS(无 CSS)
loadScript(this, RESOURCE_NAME + "/lib.js").then(() => { /* callback */ });

// 模式 2 —— 并行加载多个 JS 文件
Promise.all([
  loadScript(this, RESOURCE_NAME + "/lib1.js"),
  loadScript(this, RESOURCE_NAME + "/lib2.js"),
]).then(() => { /* callback */ });

// 模式 3 —— 同时加载 JS + CSS(最常用)
Promise.all([
  loadScript(this, RESOURCE_NAME + "/lib.js"),
  loadStyle(this, RESOURCE_NAME + "/lib.css"),
]).then(() => { /* callback */ });

最佳实践:始终用 .catch()try/catch 处理错误;用标志防止重复加载;在 renderedCallback() 中加载(确保 DOM 就绪);在 disconnectedCallback() 中清理;始终使用 this.template.querySelector()

Locker 兼容的 JavaScript 库

Locker 兼容

适用场景:组织使用 Lightning Locker(非 LWS)。LWS 启用后大多数库无需修改。

Locker 三大要求:① 避免跨命名空间直接 DOM 操作;② 支持 JS ES5 严格模式;③ 避免 Locker 阻止的 API(查阅 Locker API Viewer)。

测试步骤:创建小型示例应用 → 验证加载和基本功能 → 在 Locker Console 中测试合规性。

Locker 合规 —— 常见违规与修复

常见违规
违规问题修复
意外全局变量严格模式禁止未声明变量的全局泄露window.myLib = (function() { ... })();
CSP 违规eval()new Function()<script>移除或用 CSP 合规替代
DOM 访问违规库扫描整个 DOM 而非仅操作传入的元素使用 lwc:dom="manual"
不支持的 DOM API使用 Locker 阻止或未实现的 API查阅 Locker API Viewer 替换

不合规时的选项:联系库维护者更新、开源项目自行贡献修复、fork 仓库自行维护。

LWS 兼容的 JavaScript 库

LWS 兼容

LWS 使用虚拟沙箱方式,比 Locker 更宽松——大多数现代第三方库无需修改即能运行。DOM API 表面基本等同于标准浏览器 API。但显式设置 "use strict" 的库可能需要调整。如果组织仍在用 Locker,迁移到 LWS 应优先考虑——它极大简化了第三方库的兼容性。

从 JavaScript 调用 API —— 概述

调用 API 概述

Lightning 组件框架使用 Content Security Policy (CSP) 控制可加载内容的来源。默认 CSP 不允许从 JavaScript 发起 API 调用和 WebSocket 连接。需要在 Setup 中将第三方 URL 添加为受信任 URL(Trusted URL)来修改 CSP 策略。

两类 API:Salesforce API(通过 Lightning Data Service 或 Apex)、第三方 API(通过 Fetch API + 受信任 URL,认证头信息必须用 Apex HttpRequest 代理)。

Salesforce API —— Lightning Data Service 与 Apex

Salesforce API

LDS 优先:Lightning Data Service 基于 User Interface API,自动处理缓存、共享规则和字段级安全。大多数 CRUD 操作通过 @wire 或命令式调用即可满足需求。

当 LDS 不够时:写 Apex 类,用 @AuraEnabled 注解方法。通过 @salesforce/apex 导入,用 @wire(响应式)或直接调用(命令式)。Apex 在服务器端执行——安全、支持事务、复杂业务逻辑、完全访问所有 Salesforce API。

第三方 API —— Fetch API 使用

Fetch API
// 基本 Fetch
async getItems() {
  const response = await fetch("http://example.com/items.json");
  const items = await response.json();
}

// 正确处理 HTTP 错误
async getItems() {
  try {
    const response = await fetch("http://example.com/items.json");
    if (!response.ok) throw Error(response);    // ⚠️ .catch() 不捕获 4XX/5XX!
    const myItems = await response.json();
  } catch (error) {
    console.error("Fetch failed:", error);      // 网络失败才到这里
  } finally { /* 总是执行 */ }
}
关键陷阱:.catch() 只在网络失败(离线、超时)时触发——不捕获 HTTP 4XX/5XX 错误!必须检查 response.okresponse.status。认证头信息绝不放在客户端 JS 中——用 Apex HttpRequest 代理。参考 lwc-recipes miscRestApiCall

动态实例化组件 —— 概述与设置

动态组件概述

动态组件延迟加载代码到需要时再获取——减少初始包大小。但每次动态导入增加网络往返开销

环境设置

  1. 启用 LWS:动态导入的硬性要求
  2. 关闭持久缓存(仅开发环境):Setup → Session Settings → 取消勾选 "Enable secure and persistent browser caching"(生产环境重新启用)
  3. 配置 .js-meta.xml:添加 lightning__dynamicComponent capability,apiVersion 需 ≥ 55.0

支持托管包,不支持非托管包。

动态组件语法 —— lwc:component 与 lwc:is

动态组件语法
<template>
  <div class="container">
    <lwc:component lwc:is={componentConstructor}></lwc:component>
  </div>
</template>

// JS
componentConstructor;
connectedCallback() {
  import("c/concreteComponent")
    .then(({ default: ctor }) => (this.componentConstructor = ctor))
    .catch((err) => console.log("Error importing component"));
}

<lwc:component> 是 DOM 中的占位符lwc:is 绑定构造函数。构造函数为 falsy → 不渲染;定义但非 LightningElement → 抛出错误。import() 返回 Promise,解析为 { default: constructor }。组件名用 c/componentName 格式。

动态组件 —— async/await 模式

async/await 模式
connectedCallback() {
  this.loadComponent();  // 调用 async helper,保持自身同步
}

async loadComponent() {
  try {
    const { default: ctor } = await import("c/myComponent");
    this.componentConstructor = ctor;
  } catch (err) {
    console.error("Error importing component", err);
  }
}
为什么 connectedCallback 不标记 async?生命周期钩子应保持同步——标记 async 会导致时序问题。改为调用 async helper 方法。标准模式:sync hook → async helper。

选择与识别动态组件

选择动态组件
<lwc:component lwc:is={componentConstructor} lwc:ref="myCmp"></lwc:component>

renderedCallback() {
  if (this.refs.myCmp) {  // 构造函数设置后的下一个渲染周期才可用
    console.log(this.refs.myCmp);
  }
}

两种判断就绪的方法:① 动态组件自身的 connectedCallback;② 父组件的 renderedCallback 配合 this.refs 守卫检查。Jest 测试注意:动态组件的标签名是内部默认值——用 data-* 属性代替标签名选择器。

动态组件 —— 属性、子元素和属性传递

属性、子元素

支持所有标准 HTML 全局属性、data-* 属性和事件监听器。子元素在动态组件之后渲染——构造函数变化时整个子树(组件+子元素)一起替换。

// 直接传属性
<lwc:component lwc:is={componentConstructor} text="I love dynamic components!">

// lwc:spread 传属性(运行时动态决定)
childProps = { city: "San Francisco", state: "CA" };
<lwc:component lwc:is={componentConstructor} lwc:spread={childProps}>

动态组件 —— 事件监听与组件切换

事件监听与切换

组合使用四大指令的完整示例——lwc:islwc:spreadlwc:ononclick

get childProps() {
  return this.dynamicCtor === ChildA
    ? { name: "Child A Name", age: 30 }
    : { name: "Child B Name", age: 5 };
}
get eventHandlers() {
  return this.dynamicCtor === ChildA
    ? { customEventA: this.handleCustomEventA }
    : { customEventB: this.handleCustomEventB };
}
switchComponent() {
  this.dynamicCtor = this.dynamicCtor === ChildA ? ChildB : ChildA;
}

关键洞察:getter 根据当前加载的组件返回不同的属性和事件处理器

动态组件 —— 传递 Record ID 与事件监听器

Record ID 与事件
<!-- 传 record-id -->
<lwc:component record-id={recordId} lwc:is={componentConstructor}></lwc:component>

<!-- lwc:on 附加事件监听 -->
<lwc:component lwc:is={childComponent} lwc:on={eventHandlers}></lwc:component>

eventHandlers = { customEvent: this.handleCustomEvent };
handleCustomEvent(event) { console.log("Received:", event.detail); }

动态组件 —— 性能考量

性能考量

静态导入:框架将组件+所有依赖打包到单个 JS 文件——一次网络请求全部加载。动态导入:框架不预取——每次需要额外的网络往返(除非已缓存)。

建议 1:让动态导入可静态分析——用字符串字面量 import("c/componentName"),而非 import("c/" + variable)。未来框架优化可预打包可静态分析的动态导入。
建议 2:不要过度使用动态导入——从静态导入开始,只在包大小成为可测量的性能问题时切换。

动态组件 —— 最佳实践与反模式

最佳实践与反模式

反模式 1 —— 不可分析动态导入:避免 import(\`c/\${val}Chart\`)。优先用静态映射(小包)或可分析动态导入(大包)。

反模式 2 —— 组件名作为属性:避免接受组件名字符串后再动态导入。更好的设计:让父组件处理导入,直接传构造函数作为属性。

字符串插值:只有在组件名来自 Custom Metadata 或 Apex 时才用 import(\`c/\${this.metaDataValue}\`)——无法在构建时评估,增加运行时开销。

动态组件 —— 包与调试

包与调试

包支持:仅限托管包。从托管包导入时用包的命名空间(不是 c/)。

调试清单:① LWS 启用?② .js-meta.xml 含 lightning__dynamicComponent?③ apiVersion ≥ 55.0?④ 关闭持久缓存?⑤ import 路径正确?⑥ 构造函数有效?⑦ 浏览器控制台错误?⑧ 网络标签页确认动态导入请求。最可靠确认方式:检查 DOM,标签应物理替换

TypeScript for LWC —— 概述(Developer Preview)

TypeScript 概述
⚠️ Developer Preview:非 GA、可能随时变更或废弃、不要用于生产功能。

Winter '25 起可用 TypeScript v5.4.5+ 开发 LWC。收益:构建时类型检查、IDE 自动补全、静态类型分析、更大类型安全、内联文档。前提:VS Code + Salesforce Extension Pack(推荐),或手动安装 @salesforce/lightning-types + lwc npm 包。

启用 TypeScript 支持

启用 TypeScript
  1. 设置 feature flag:settings.json"salesforcedx-vscode-lwc.preview.typeScriptSupport": true
  2. 配置 tsconfig.json:关键设置 "experimentalDecorators": false(必须为 false!)
  3. 安装 TypeScript:npm install typescript --save-dev(最低 v5.4.5)

TypeScript 版本自行管理——平台不提供。保存 feature flag 设置后自动生成 tsconfig.json 到 lwc 文件夹;未出现则重启 VS Code。

编译 TypeScript 为 JavaScript 并部署

编译与部署

LWC 编译器不编译 TypeScript——你必须在部署前手动转换:

npx tsc --project ./path/to/lwc

每个 .ts 文件旁生成对应的 .js 文件。Salesforce 平台不存储 TypeScript 源码——只接收编译后的 JS。你必须通过 Git 等管理 .ts 源文件。

转换现有组件:重命名 .js.ts → 运行编译器 → 解决类型错误。

TypeScript 组件的 Jest 测试

Jest 测试

sfdx-lwc-jest 开箱即支持 TypeScript 编译——无需修改 jest.config.js

// 安装 Jest 类型定义
npm install --save-dev @types/jest
// tsconfig.json: "compilerOptions": { "types": ["jest"] }
// 测试文件:.test.ts,结构同 JS 测试
npm run test:unit  // JS 和 TS 测试通用命令

非空断言注意:this.template! 仅在 shadow DOM 模式下安全——light DOM 下始终为 null,断言会导致运行时错误。

Salesforce Lightning 类型与 Ambient Modules

Lightning 类型

VS Code Extension Pack 自动提供 lwc@salesforce/apex@salesforce/schemalightning/messageService 的类型定义(临时——将迁移到 @salesforce/lightning-types)。

手动安装:安装 @salesforce/lightning-types → 创建 types/salesforce.d.ts → 在 tsconfig.json 中配置 paths每个模块都要映射)。基础组件类型:import LightningButton from "lightning/button"

Ambient Modules:为无类型定义的组件声明类型形状——定义类、方法签名和可选属性。

TypeScript —— 注意事项与限制

注意事项
  1. 自行管理 TypeScript 版本 —— 平台不提供
  2. 必须使用源码控制 —— .ts 文件不部署到平台,只部署 .js
  3. 小心非空断言 —— this.template! 仅在 shadow DOM 安全
  4. 装饰器错误抑制:experimentalDecorators: false 导致装饰器报错——每行前加 // @ts-ignore(GA 后应移除)
  5. 当前不支持:Salesforce CLI 集成、TypeScript 源码映射调试、非 Salesforce 类型、自定义 Salesforce 对象/字段的类型定义

感谢阅读本指南。如需继续学习,请参阅下一章:操作 DOM。