typescript by epicenterhq/epicenter
npx skills add https://github.com/epicenterhq/epicenter --skill typescript相关技能:运行时类型验证模式请参阅
arktype。TypeBox 模式请参阅typebox。测试文件规范请参阅testing。
在以下情况下使用此模式:
根据当前工作内容按需加载:
types.ts 位置、共置规则、选项/ID 命名),请阅读 references/type-organization.mdlet 状态提取到子工厂中),请阅读 references/factory-patterns.md广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
在 TypeScript 中始终使用 type 而不是 interface。
readonly 仅用于数组和映射:切勿在基本属性或对象属性上使用 readonly。该修饰符是浅层的,对于非集合类型提供的保护很少。仅在确实可能发生意外修改的地方使用它:
// 良好 - 仅在数组上使用 readonly
type Config = { version: number; vendor: string; items: readonly string[]; };
// 不良 - 到处使用 readonly 是噪音 type Config = { readonly version: number; readonly vendor: string; readonly items: readonly string[]; };
例外:与上游库类型完全匹配(例如,标准模式接口)。理由请参阅 docs/articles/readonly-is-mostly-noise.md。
camelCase 中的首字母缩略词:将首字母缩略词视为单个单词,仅首字母大写:
// 正确 - 首字母缩略词视为单词
parseUrl(); defineKv(); readJson(); customerId; httpClient;
// 错误 - 全大写的首字母缩略词 parseURL(); defineKV(); readJSON(); customerID; HTTPClient;
例外:匹配现有的平台 API(例如,XMLHttpRequest)。理由请参阅 docs/articles/acronyms-in-camelcase.md。
TypeScript 5.5+ 会自动推断 .filter() 回调中的类型谓词。不要添加手动类型断言:
// 良好 - TypeScript 自动推断缩窄后的类型
const filtered = items.filter((x) => x !== undefined);
// 不良 - 不必要的类型谓词 const filtered = items.filter( (x): x is NonNullable<typeof x> => x !== undefined, );
将组件移动到新位置时,始终将相对导入更新为绝对导入(例如,将 import Component from '../Component.svelte' 更改为 import Component from '$lib/components/Component.svelte')
当函数仅在工厂/创建者函数的返回语句中使用时,使用对象方法简写语法,而不是单独定义它们。例如,不要这样写:
function myFunction() {
const helper = () => { /* ... */ }; return { helper }; }
请这样写:
function myFunction() {
return {
helper() {
/* ... */
},
};
}
function createX() { return { ... } } 而不是 class X { ... }。闭包提供了结构化的隐私性——返回语句之上的所有内容在位置上都是私有的,其内部的所有内容都是公共 API。类将 private/protected/public 成员以任意顺序混合,迫使你扫描每个成员并检查其修饰符。理由请参阅 docs/articles/closures-are-better-privacy-than-keywords.md。is/has/can 前缀布尔属性、变量和参数必须使用一个读起来像是否问题的谓词前缀:
is — 状态或身份:isEncrypted、isLoading、isVisible、isActive
has — 拥有或存在:hasToken、hasChildren、hasError
can — 能力或许可:canWrite、canDelete、canUndo
// 良好 — 读起来像一个问题
type Config = { isEncrypted: boolean; isReadOnly: boolean; hasCustomTheme: boolean; canExport: boolean; };
get isEncrypted() { return currentKey !== undefined; } const isVisible = element.offsetParent !== null; if (hasToken) { ... }
// 不良 — 含义模糊,读起来不像是否问题 type Config = { encrypted: boolean; // 没有 'is' 的形容词 readOnly: boolean; // 可能是一个名词 state: boolean; // 什么状态? mode: boolean; // 什么模式? };
这适用于:
isActive: boolean)get isEncrypted())const isValid = ...)function toggle(isEnabled: boolean))function isExpired(): boolean)例外:与上游库类型完全匹配(例如,来自外部定义类型的 API 的 tab.pinned、window.focused)。
当多个 if/else if 分支将同一变量与字符串字面量(或其他常量值)进行比较时,始终使用 switch 语句。这适用于操作类型、状态字段、文件类型、策略名称或任何可区分值。
// 不良 - 比较同一变量的 if/else 链
if (change.action === 'add') { handleAdd(change); } else if (change.action === 'update') { handleUpdate(change); } else if (change.action === 'delete') { handleDelete(change); }
// 良好 - switch 语句 switch (change.action) { case 'add': handleAdd(change); break; case 'update': handleUpdate(change); break; case 'delete': handleDelete(change); break; }
对于共享逻辑的情况,使用贯穿:
switch (change.action) {
case 'add':
case 'update': {
applyChange(change);
break;
}
case 'delete': {
removeChange(change);
break;
}
}
当 case 中使用 let 或 const 声明变量时,使用块作用域({ })。
何时不使用 switch:用于类型缩窄的提前返回可以保留为连续的 if 语句。如果每个分支都立即返回,并且检查是为了后续代码缩窄联合类型,则保留它们作为 if 守卫。
理由请参阅 docs/articles/switch-over-if-else-for-value-comparison.md。
当一个表达式将一组有限的已知值映射到输出时,使用 satisfies Record 查找而不是嵌套三元表达式。这是“值比较时优先使用 Switch 而非 If/Else”在表达式级别的对应物:switch 处理有副作用的语句,记录查找处理值映射。
// 不良 - 嵌套三元表达式
const tooltip = status === 'connected' ? 'Connected' : status === 'connecting' ? 'Connecting…' : 'Offline';
// 良好 - 具有详尽类型检查的记录查找 const tooltip = ({ connected: 'Connected', connecting: 'Connecting…', offline: 'Offline', } satisfies Record<SyncStatus, string>)[status];
satisfies Record<SyncStatus, string> 提供了编译时完备性检查:如果 SyncStatus 增加了第四个值,TypeScript 会报错,因为记录缺少一个键。嵌套三元表达式会静默地进入 else 分支。
这里不需要 as const。satisfies 已经验证了形状和值类型。as const 会将值缩窄为字面量类型('Connected' 而不是 string),当输出只是渲染或作为字符串传递时,这没有增加任何价值。
当记录只使用一次时,将其内联。当它被共享或有 5 个以上条目时,提取到一个命名常量。
理由请参阅 docs/articles/record-lookup-over-nested-ternaries.md。
每周安装量
76
代码仓库
GitHub 星标数
4.3K
首次出现
2026年1月20日
安全审计
安装于
opencode64
gemini-cli63
codex61
claude-code60
cursor59
github-copilot57
Related Skills : See
arktypefor runtime type validation patterns. Seetypeboxfor TypeBox schema patterns. Seetestingfor test file conventions.
Use this pattern when you need to:
Load these on demand based on what you're working on:
types.ts location, co-location rules, options/IDs naming), read references/type-organization.mdlet state into sub-factories), read references/factory-patterns.mdAlways use type instead of interface in TypeScript.
readonly only for arrays and maps: Never use readonly on primitive properties or object properties. The modifier is shallow and provides little protection for non-collection types. Use it only where mutation is a realistic footgun:
// Good - readonly only on the array
type Config = { version: number; vendor: string; items: readonly string[]; };
// Bad - readonly everywhere is noise type Config = { readonly version: number; readonly vendor: string; readonly items: readonly string[]; };
Exception: Match upstream library types exactly (e.g., standard-schema interfaces). See docs/articles/readonly-is-mostly-noise.md for rationale.
Acronyms in camelCase : Treat acronyms as single words, capitalizing only the first letter:
// Correct - acronyms as words
parseUrl(); defineKv(); readJson(); customerId; httpClient;
// Incorrect - all-caps acronyms parseURL(); defineKV(); readJSON(); customerID; HTTPClient;
Exception: Match existing platform APIs (e.g., XMLHttpRequest). See docs/articles/acronyms-in-camelcase.md for rationale.
TypeScript 5.5+ automatically infers type predicates in .filter() callbacks. Don't add manual type assertions:
// Good - TypeScript infers the narrowed type automatically
const filtered = items.filter((x) => x !== undefined);
// Bad - unnecessary type predicate const filtered = items.filter( (x): x is NonNullable<typeof x> => x !== undefined, );
When moving components to new locations, always update relative imports to absolute imports (e.g., change import Component from '../Component.svelte' to import Component from '$lib/components/Component.svelte')
When functions are only used in the return statement of a factory/creator function, use object method shorthand syntax instead of defining them separately. For example, instead of:
function myFunction() {
const helper = () => {
/* ... */
};
return { helper };
}
Use:
function myFunction() {
return {
helper() {
/* ... */
},
};
}
function createX() { return { ... } } instead of class X { ... }. Closures provide structural privacy—everything above the return statement is private by position, everything inside it is the public API. Classes mix private/protected/public members in arbitrary order, forcing you to scan every member and check its modifier. See docs/articles/closures-are-better-privacy-than-keywords.md for rationale.is/has/can PrefixBoolean properties, variables, and parameters MUST use a predicate prefix that reads as a yes/no question:
is — state or identity: isEncrypted, isLoading, isVisible, isActive
has — possession or presence: hasToken, hasChildren, hasError
can — capability or permission: canWrite, ,
This applies to:
isActive: boolean)get isEncrypted())const isValid = ...)function toggle(isEnabled: boolean))function isExpired(): boolean)Exception: Match upstream library types exactly (e.g., tab.pinned, window.focused from APIs where the type is externally defined).
When multiple if/else if branches compare the same variable against string literals (or other constant values), always use a switch statement instead. This applies to action types, status fields, file types, strategy names, or any discriminated value.
// Bad - if/else chain comparing the same variable
if (change.action === 'add') {
handleAdd(change);
} else if (change.action === 'update') {
handleUpdate(change);
} else if (change.action === 'delete') {
handleDelete(change);
}
// Good - switch statement
switch (change.action) {
case 'add':
handleAdd(change);
break;
case 'update':
handleUpdate(change);
break;
case 'delete':
handleDelete(change);
break;
}
Use fall-through for cases that share logic:
switch (change.action) {
case 'add':
case 'update': {
applyChange(change);
break;
}
case 'delete': {
removeChange(change);
break;
}
}
Use block scoping ({ }) when a case declares variables with let or const.
When NOT to use switch: early returns for type narrowing are fine as sequential if statements. If each branch returns immediately and the checks are narrowing a union type for subsequent code, keep them as if guards.
See docs/articles/switch-over-if-else-for-value-comparison.md for rationale.
When an expression maps a finite set of known values to outputs, use a satisfies Record lookup instead of nested ternaries. This is the expression-level counterpart to "Switch Over If/Else": switch handles statements with side effects, record lookup handles value mappings.
// Bad - nested ternary
const tooltip = status === 'connected'
? 'Connected'
: status === 'connecting'
? 'Connecting…'
: 'Offline';
// Good - record lookup with exhaustive type checking
const tooltip = ({
connected: 'Connected',
connecting: 'Connecting…',
offline: 'Offline',
} satisfies Record<SyncStatus, string>)[status];
satisfies Record<SyncStatus, string> gives you compile-time exhaustiveness: if SyncStatus gains a fourth value, TypeScript errors because the record is missing a key. Nested ternaries silently fall through to the else branch.
as const is unnecessary here. satisfies already validates the shape and value types. as const would narrow values to literal types ('Connected' instead of string), which adds no value when the output is just rendered or passed as a string.
When the record is used once, inline it. When it's shared or has 5+ entries, extract to a named constant.
See docs/articles/record-lookup-over-nested-ternaries.md for rationale.
Weekly Installs
76
Repository
GitHub Stars
4.3K
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode64
gemini-cli63
codex61
claude-code60
cursor59
github-copilot57
Android 整洁架构指南:模块化设计、依赖注入与数据层实现
1,400 周安装
canDeletecanUndo// Good — reads as a question type Config = { isEncrypted: boolean; isReadOnly: boolean; hasCustomTheme: boolean; canExport: boolean; };
get isEncrypted() { return currentKey !== undefined; } const isVisible = element.offsetParent !== null; if (hasToken) { ... }
// Bad — ambiguous, doesn't read as yes/no type Config = { encrypted: boolean; // adjective without 'is' readOnly: boolean; // could be a noun state: boolean; // what state? mode: boolean; // what mode? };