LWC in 30 seconds
Standards-based Web Components with reactive rendering, shadow DOM, and a compiler that ships to modern browsers. If you know ES modules + class syntax, you know 80% of LWC.
Anatomy
force-app/main/default/lwc/dealCard/
├─ dealCard.js # controller
├─ dealCard.html # template
├─ dealCard.css # scoped styles
├─ dealCard.js-meta.xml # visibility, targets, design attributes
Reactive data with @wire
import { LightningElement, api, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import NAME from '@salesforce/schema/Opportunity.Name';
import AMOUNT from '@salesforce/schema/Opportunity.Amount';
export default class DealCard extends LightningElement {
@api recordId;
@wire(getRecord, { recordId: '$recordId', fields: [NAME, AMOUNT] })
opp;
get name() { return getFieldValue(this.opp.data, NAME); }
get amount() { return getFieldValue(this.opp.data, AMOUNT); }
}
Reactive property = leading $. When recordId changes, the wire re-invokes.
Calling Apex
public with sharing class DealCardController {
@AuraEnabled(cacheable=true)
public static List<Opportunity> topDeals(Id accountId) {
return [SELECT Id, Name, Amount FROM Opportunity
WHERE AccountId = :accountId AND IsClosed = false
ORDER BY Amount DESC LIMIT 5 WITH USER_MODE];
}
}
import topDeals from '@salesforce/apex/DealCardController.topDeals';
@wire(topDeals, { accountId: '$recordId' })
deals;
cacheable=true lets Lightning Data Service cache the response — huge for list components.
Composition, not inheritance
Slots > subclasses. Build a <c-panel> with named slots:
<template>
<div class="panel">
<header><slot name="title"></slot></header>
<div class="body"><slot></slot></div>
<footer><slot name="actions"></slot></footer>
</div>
</template>
Consume:
<c-panel>
<h2 slot="title">Deal Health</h2>
<c-health-score record-id={recordId}></c-health-score>
<lightning-button slot="actions" label="Refresh"></lightning-button>
</c-panel>
Events between components
- Parent → child:
@apipublic properties + methods - Child → parent:
this.dispatchEvent(new CustomEvent('select', { detail: id })) - Sibling ↔ sibling: Lightning Message Service (LMS)
import { publish, MessageContext } from 'lightning/messageService';
import DEAL_SELECTED from '@salesforce/messageChannel/DealSelected__c';
@wire(MessageContext) messageContext;
handleSelect(id) { publish(this.messageContext, DEAL_SELECTED, { id }); }
Testing with Jest
import { createElement } from 'lwc';
import DealCard from 'c/dealCard';
import { getRecord } from 'lightning/uiRecordApi';
const mockRecord = require('./data/getRecord.json');
test('renders opportunity name', async () => {
const el = createElement('c-deal-card', { is: DealCard });
el.recordId = '006xx0000000001';
document.body.appendChild(el);
getRecord.emit(mockRecord);
await Promise.resolve();
expect(el.shadowRoot.querySelector('h2').textContent).toBe('Acme Renewal');
});
Aim for 80% branch coverage with real assertions, not snapshot-only tests.
Performance rules
@wirewith cacheable Apex — never imperativefetchin a getter- Use
@trackonly on non-reactive fields you mutate (arrays/objects) - Chunk long lists with Lightning Datatable + Infinite Loading
- Lighthouse audit each community-facing LWC — LCP < 2.5s
