@captain_z/zsk-skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +263 -0
- package/bundles.yaml +104 -0
- package/design-handoff/.gitkeep +0 -0
- package/design-handoff/figma-to-code/SKILL.md +265 -0
- package/design-handoff/ue-mcp/SKILL.md +184 -0
- package/frontend/.gitkeep +0 -0
- package/frontend/a11y-web/SKILL.md +169 -0
- package/frontend/api-contract-ts/SKILL.md +275 -0
- package/frontend/css-bem/SKILL.md +268 -0
- package/frontend/design-frontend/SKILL.md +163 -0
- package/frontend/dor-dod-frontend/SKILL.md +114 -0
- package/frontend/feature-tasks-frontend/SKILL.md +246 -0
- package/frontend/i18n/SKILL.md +296 -0
- package/frontend/nfr-web/SKILL.md +258 -0
- package/frontend/performance-web/SKILL.md +299 -0
- package/frontend/react-components/SKILL.md +211 -0
- package/frontend/react-naming/SKILL.md +224 -0
- package/frontend/review-frontend/SKILL.md +126 -0
- package/frontend/security-web/SKILL.md +286 -0
- package/frontend/spec-frontend/SKILL.md +141 -0
- package/frontend/testing-web/SKILL.md +252 -0
- package/frontend/typescript/SKILL.md +219 -0
- package/meta/.gitkeep +0 -0
- package/meta/philosophy/SKILL.md +221 -0
- package/package.json +24 -0
- package/quality/.gitkeep +0 -0
- package/quality/a11y-principles/SKILL.md +155 -0
- package/quality/code-hygiene/SKILL.md +126 -0
- package/quality/release/SKILL.md +209 -0
- package/quality/security-owasp/SKILL.md +157 -0
- package/quality/testing-pyramid/SKILL.md +173 -0
- package/sdlc/.gitkeep +0 -0
- package/sdlc/archive/SKILL.md +267 -0
- package/sdlc/bugfix/SKILL.md +181 -0
- package/sdlc/bugfix-tasks/SKILL.md +232 -0
- package/sdlc/coding/SKILL.md +177 -0
- package/sdlc/design/SKILL.md +299 -0
- package/sdlc/dor-dod/SKILL.md +120 -0
- package/sdlc/feature/SKILL.md +162 -0
- package/sdlc/proposal/SKILL.md +271 -0
- package/sdlc/refactor/SKILL.md +220 -0
- package/sdlc/refactor-tasks/SKILL.md +265 -0
- package/sdlc/reviewing/SKILL.md +197 -0
- package/sdlc/spec/SKILL.md +235 -0
- package/sdlc/task/SKILL.md +116 -0
- package/sdlc/task-evidence/SKILL.md +178 -0
- package/sdlc/task-structure/SKILL.md +153 -0
- package/sdlc/task-tracking/SKILL.md +192 -0
- package/sdlc/verify/SKILL.md +181 -0
- package/system/.gitkeep +0 -0
- package/system/adr/SKILL.md +169 -0
- package/system/architecture/SKILL.md +182 -0
- package/system/glossary/SKILL.md +141 -0
- package/system/nfr-baseline/SKILL.md +156 -0
- package/system/project-constraints-template/SKILL.md +241 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zsk:react-components
|
|
3
|
+
description: React component API design — layering (UI/logic separation via
|
|
4
|
+
hooks), composition vs configuration (compound components over flat-prop
|
|
5
|
+
explosion), granularity (5-question checklist), controlled vs uncontrolled
|
|
6
|
+
boundary, Props API design. React-specific implementation of generic component
|
|
7
|
+
discipline.
|
|
8
|
+
category: standard
|
|
9
|
+
domain: frontend
|
|
10
|
+
tier: optional
|
|
11
|
+
related:
|
|
12
|
+
- ../react-naming/SKILL.md
|
|
13
|
+
- ../typescript/SKILL.md
|
|
14
|
+
- ../../quality/code-hygiene/SKILL.md
|
|
15
|
+
triggers:
|
|
16
|
+
- React component design
|
|
17
|
+
- compound components
|
|
18
|
+
- component granularity
|
|
19
|
+
- controlled uncontrolled
|
|
20
|
+
- Props API
|
|
21
|
+
- hooks extraction
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# React Components · 组件设计纪律
|
|
25
|
+
|
|
26
|
+
> **范围**:React 组件的**设计层** — 分层 / 可扩展 / 组合 / 粒度 / 受控边界 / Props API
|
|
27
|
+
> **不含**:命名(见 [`react-naming`](../react-naming/SKILL.md))/ 类型(见 [`typescript`](../typescript/SKILL.md))/ 测试(见 [`testing-web`](../testing-web/SKILL.md))
|
|
28
|
+
> **跨栈原则**:Vue / Svelte / Solid 的组件设计原则相同,语法等价替换
|
|
29
|
+
|
|
30
|
+
## 1. 分层(UI / 逻辑分离)
|
|
31
|
+
|
|
32
|
+
组件内部严格分离**渲染层**与**逻辑层**。
|
|
33
|
+
|
|
34
|
+
### 硬规则
|
|
35
|
+
|
|
36
|
+
- **UI 层(`.tsx` 主文件)**:只负责 JSX 渲染 + 事件分发,不写 `if (user.role === 'admin')` 这类业务判定
|
|
37
|
+
- **逻辑层(hooks / services / utils)**:状态机、副作用、业务规则、API 调用
|
|
38
|
+
- **同一逻辑出现 ≥ 2 处 → 立即抽 hook**,不要等"第三次再抽"
|
|
39
|
+
|
|
40
|
+
### 推荐结构
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
// FeatureTree/index.tsx — UI 层
|
|
44
|
+
export function FeatureTree(props: FeatureTreeProps) {
|
|
45
|
+
const { state, handlers } = useFeatureTreeLogic(props);
|
|
46
|
+
return <FeatureTreeView {...state} {...handlers} />;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// FeatureTree/useFeatureTreeLogic.ts — 逻辑层
|
|
50
|
+
export function useFeatureTreeLogic(props: FeatureTreeProps) {
|
|
51
|
+
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
52
|
+
// ...
|
|
53
|
+
return { state: { expanded, ... }, handlers: { onToggle, ... } };
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Hook 设计红线
|
|
58
|
+
|
|
59
|
+
- Hook 只返回状态 + 方法,**不返回 JSX**
|
|
60
|
+
- Hook 内不做不相关副作用(一个 hook 只解决一类问题)
|
|
61
|
+
- Hook 命名以 `use` 开头(见 [`react-naming`](../react-naming/SKILL.md))
|
|
62
|
+
- Hook 的依赖遵守 `react-hooks/exhaustive-deps`,豁免必须在该行加 ADR 引用
|
|
63
|
+
|
|
64
|
+
## 2. 可扩展(Open / Closed)
|
|
65
|
+
|
|
66
|
+
**新增能力通过新增 Prop / 新增 slot 达成,不改已存在 Prop 的语义**。
|
|
67
|
+
|
|
68
|
+
### 禁用
|
|
69
|
+
|
|
70
|
+
- ❌ 发布后的 Prop 含义偷偷变化(`size: 'small' | 'large'` → `size: number`)
|
|
71
|
+
- ❌ 靠"传 `flag=true`"修改既有行为 → 表明 Prop 设计错了,应拆两个组件
|
|
72
|
+
- ❌ 改了 Prop 默认值而不开 major 版本
|
|
73
|
+
|
|
74
|
+
### 允许
|
|
75
|
+
|
|
76
|
+
- ✅ 新增可选 Prop,默认值不变更现状
|
|
77
|
+
- ✅ 新增具名 slot(`header` / `footer`)
|
|
78
|
+
- ✅ 新增事件回调(`onXxx`),老消费者无感
|
|
79
|
+
|
|
80
|
+
## 3. 组合 vs 配置
|
|
81
|
+
|
|
82
|
+
**组合(children / slot / compound)优于配置(扁平 Props)**。
|
|
83
|
+
|
|
84
|
+
### 判定:扁平 Props ≥ 8 个 or 出现 `renderXxx` 渲染回调时,改用组合。
|
|
85
|
+
|
|
86
|
+
### 组合模式清单
|
|
87
|
+
|
|
88
|
+
| 模式 | 何时用 | 形态 |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `children` | 单 slot | `<Card>{content}</Card>` |
|
|
91
|
+
| Compound Components | 多 slot 有语义关联 | `<Tabs><Tabs.List/><Tabs.Panel/></Tabs>` |
|
|
92
|
+
| Slot Props | 多 slot 不固定顺序 | `<Layout header={...} sidebar={...} />` |
|
|
93
|
+
| Render Props | 内部状态需暴露 | `<Autocomplete>{({items}) => ...}</Autocomplete>` |
|
|
94
|
+
| Hook 暴露 | 完全把控制权给外部 | `const combo = useCombobox(); <input {...combo.getInputProps()}/>` |
|
|
95
|
+
|
|
96
|
+
### Compound Components 示例
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<Select value={value} onChange={setValue}>
|
|
100
|
+
<Select.Trigger placeholder="选择..." />
|
|
101
|
+
<Select.Options>
|
|
102
|
+
<Select.Option value="a">A</Select.Option>
|
|
103
|
+
<Select.Option value="b">B</Select.Option>
|
|
104
|
+
</Select.Options>
|
|
105
|
+
</Select>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
父组件通过 Context 传状态,子组件只关心自己的渲染责任。
|
|
109
|
+
|
|
110
|
+
## 4. 组件粒度(5 问 checklist)
|
|
111
|
+
|
|
112
|
+
新建 / 拆分组件前,依次回答:
|
|
113
|
+
|
|
114
|
+
1. **复用**:这块 JSX 是否会在 ≥ 2 处出现?(否 → 先不拆)
|
|
115
|
+
2. **职责**:能否用一句话说明"它是什么"?(否 → 职责不清,先想清再拆)
|
|
116
|
+
3. **Props 数量**:拆完后每个组件的 Props ≤ 8?(否 → 再拆或改组合)
|
|
117
|
+
4. **独立状态**:子组件是否有独立状态?(是 → 值得拆)
|
|
118
|
+
5. **跨文件引用**:拆完后是否需要在多处 import?(否且只本文件用 → 考虑留在同文件内部组件)
|
|
119
|
+
|
|
120
|
+
**只要 1、2、4 其一是"强是",就应该拆**;反之留在父组件内。
|
|
121
|
+
|
|
122
|
+
## 5. 受控 / 非受控边界
|
|
123
|
+
|
|
124
|
+
### 判定
|
|
125
|
+
|
|
126
|
+
| 场景 | 选择 |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| 父组件需要读取 / 控制值 | **受控**(`value` + `onChange`) |
|
|
129
|
+
| 组件自管理、父不关心中间态 | **非受控**(`defaultValue` + `onCommit`) |
|
|
130
|
+
| 既要默认值又要外部控制 | **混合**(`defaultValue` + `value?` + `onChange?`) |
|
|
131
|
+
|
|
132
|
+
### 硬规则
|
|
133
|
+
|
|
134
|
+
- **不允许同时声明 `value` 和 `defaultValue`** 语义重复,Props 文档明确"二选一"
|
|
135
|
+
- **受控组件的 `value` 为 `undefined` 时视为"受控但当前无值"**,不要 fallback 到内部 state
|
|
136
|
+
- **受控 → 非受控切换(或反向)产生警告**:React 会报 `A component is changing an uncontrolled input to be controlled`,要求父组件在挂载前决定模式
|
|
137
|
+
|
|
138
|
+
### 示例
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
type InputProps = {
|
|
142
|
+
value?: string; // 受控值
|
|
143
|
+
defaultValue?: string; // 非受控初值
|
|
144
|
+
onChange?: (v: string) => void;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// 判断模式
|
|
148
|
+
const isControlled = value !== undefined;
|
|
149
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? '');
|
|
150
|
+
const currentValue = isControlled ? value : internalValue;
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## 6. Props API 设计
|
|
154
|
+
|
|
155
|
+
### Props 命名
|
|
156
|
+
|
|
157
|
+
- 数据类:名词(`items` / `user` / `selected`)
|
|
158
|
+
- 回调类:`on{Event}`(`onChange` / `onSelect` / `onError`)
|
|
159
|
+
- 布尔类:`is/has/can/should` 前缀(`isLoading` / `hasError` / `canSelect`)
|
|
160
|
+
- 详细规范见 [`react-naming`](../react-naming/SKILL.md)
|
|
161
|
+
|
|
162
|
+
### 默认值
|
|
163
|
+
|
|
164
|
+
- 默认值在**解构时给**,不在 `defaultProps`(RSC 弃用)
|
|
165
|
+
```tsx
|
|
166
|
+
function Btn({ size = 'medium', disabled = false }: BtnProps) { ... }
|
|
167
|
+
```
|
|
168
|
+
- 默认值应是"最常用"而非"最保守"
|
|
169
|
+
- 必填 Prop 不给默认值(用 TS 强制)
|
|
170
|
+
|
|
171
|
+
### 联合字面量 vs 布尔堆叠
|
|
172
|
+
|
|
173
|
+
- ❌ `<Btn primary danger large/>` — 多 boolean 堆叠组合爆炸
|
|
174
|
+
- ✅ `<Btn variant="danger" size="large"/>` — 联合字面量清晰可扩展
|
|
175
|
+
|
|
176
|
+
### Props 穿透
|
|
177
|
+
|
|
178
|
+
- **危险 Props 穿透**(`...rest` 到原生元素)需要类型约束:
|
|
179
|
+
```tsx
|
|
180
|
+
type BtnProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type'> & {
|
|
181
|
+
variant: 'primary' | 'danger';
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
- 不允许无类型 `...props` 穿透
|
|
185
|
+
|
|
186
|
+
## 7. 跨栈迁移(原则保留)
|
|
187
|
+
|
|
188
|
+
| 原则 | React | Vue 3 | Svelte 5 |
|
|
189
|
+
| --- | --- | --- | --- |
|
|
190
|
+
| UI / 逻辑分离 | Hook + View 组件 | Composables + `<script setup>` | Module + component |
|
|
191
|
+
| 组合 | children / compound | `<slot>` / 具名 slot | `<slot>` / snippets |
|
|
192
|
+
| 受控 / 非受控 | `value` + `onChange` | `v-model` / 单向 binding | `bind:value` 可选 |
|
|
193
|
+
| Props 字面量 | Union string | `PropType<'a'\|'b'>` | `$props<{variant: 'a'\|'b'}>` |
|
|
194
|
+
|
|
195
|
+
## 反模式(禁止)
|
|
196
|
+
|
|
197
|
+
- ❌ UI 层写业务规则(`if (permission.code === 'XYZ')` 出现在 `.tsx` 里)
|
|
198
|
+
- ❌ 为"未来可能扩展"预留参数(YAGNI — 见 [`quality/code-hygiene`](../../quality/code-hygiene/SKILL.md))
|
|
199
|
+
- ❌ 同一组件既受控又有内部 state 静默 fallback(产生"值不同步"bug)
|
|
200
|
+
- ❌ 扁平 Props ≥ 10 个 不改组合
|
|
201
|
+
- ❌ 同时声明 `value` 和 `defaultValue`
|
|
202
|
+
- ❌ 把不相关的能力塞进一个 hook
|
|
203
|
+
|
|
204
|
+
## 质量门禁
|
|
205
|
+
|
|
206
|
+
- [ ] UI / 逻辑分层清晰(JSX 文件无业务 `if`)
|
|
207
|
+
- [ ] 组件粒度通过 5 问 checklist
|
|
208
|
+
- [ ] 受控 / 非受控边界在 Props 文档声明
|
|
209
|
+
- [ ] Props API 遵循 [`react-naming`](../react-naming/SKILL.md)
|
|
210
|
+
- [ ] 无多 boolean 堆叠配置
|
|
211
|
+
- [ ] 新增能力通过加 Prop / slot 而非改语义
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zsk:react-naming
|
|
3
|
+
description: React + TypeScript naming conventions — components, files, folders,
|
|
4
|
+
variables, booleans, event handlers, event props, hooks, TS types/interfaces,
|
|
5
|
+
constants, and i18n keys. Principles transfer to Vue/Svelte with syntax
|
|
6
|
+
adjustments.
|
|
7
|
+
category: standard
|
|
8
|
+
domain: frontend
|
|
9
|
+
tier: optional
|
|
10
|
+
related:
|
|
11
|
+
- ../react-components/SKILL.md
|
|
12
|
+
- ../typescript/SKILL.md
|
|
13
|
+
- ../i18n/SKILL.md
|
|
14
|
+
triggers:
|
|
15
|
+
- React naming
|
|
16
|
+
- component file naming
|
|
17
|
+
- event handler naming
|
|
18
|
+
- boolean naming
|
|
19
|
+
- i18n key naming
|
|
20
|
+
- hook naming
|
|
21
|
+
- TypeScript type naming
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# React Naming · 命名约定(React + TS)
|
|
25
|
+
|
|
26
|
+
> **范围**:React + TS 项目的命名约定全集 — 组件 / 文件 / 变量 / 布尔 / 事件 / Hook / 类型 / 常量 / i18n key
|
|
27
|
+
> **前置检测**:若项目已有 `.eslintrc` / `tsconfig.json` 规定命名,以项目为准;本文件为缺省约定
|
|
28
|
+
|
|
29
|
+
## 命名总表
|
|
30
|
+
|
|
31
|
+
| 实体 | 约定 | 示例 | 反例 |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| 组件(函数 / 类) | PascalCase | `UserProfileCard` | `user_profile_card`, `userProfileCard` |
|
|
34
|
+
| 组件文件 | `PascalCase.tsx` 或 `index.tsx`(目录内) | `UserProfile.tsx` · `UserProfile/index.tsx` | `user-profile.tsx` |
|
|
35
|
+
| 组件目录 | PascalCase | `UserProfile/` | `user-profile/` |
|
|
36
|
+
| Hook 函数 | `use` + camelCase | `useUserProfile`, `useDebouncedValue` | `UserProfileHook`, `hookProfile` |
|
|
37
|
+
| Hook 文件 | 同 hook 名 `.ts` | `useUserProfile.ts` | `user-profile-hook.ts` |
|
|
38
|
+
| 工具函数 / utils | camelCase | `formatDate`, `parseISO` | `FormatDate`, `format_date` |
|
|
39
|
+
| utils 文件 | camelCase 或 kebab-case(保持一致) | `formatDate.ts` · `format-date.ts` | 两种混用 |
|
|
40
|
+
| 常量(模块级 / 全局) | SCREAMING_SNAKE_CASE | `MAX_RETRY`, `API_BASE_URL` | `maxRetry`, `ApiBaseUrl` |
|
|
41
|
+
| 常量(函数内局部) | camelCase | `const retryLimit = 3` | 局部也用 `RETRY_LIMIT` |
|
|
42
|
+
| TS 类型 / 接口 | PascalCase,单数名词 | `UserProfile`, `Response<T>` | `IUserProfile`, `UserProfileType` |
|
|
43
|
+
| Enum / 字面量联合 | PascalCase(Enum)/ 字面量小写 | `'loading' \| 'error'` | `'LOADING' \| 'ERROR'` |
|
|
44
|
+
| 变量 | camelCase | `currentUser` | `current_user` |
|
|
45
|
+
| 布尔变量 / Prop | `is/has/can/should/did` + 形容词 | `isLoading`, `hasError`, `canEdit` | `loading`, `error` 作布尔时含糊 |
|
|
46
|
+
| 事件 handler(组件内) | `handle` + 名词 + 动作(可选) | `handleSubmit`, `handleItemClick` | `onSubmit` 在组件内部定义(易与 Prop 冲突) |
|
|
47
|
+
| 事件 Prop(对外) | `on` + 名词 + 动作(可选) | `onChange`, `onItemClick` | `handleChange` 作 Prop |
|
|
48
|
+
| 回调 Prop(非 DOM 事件) | `on{Phase}{Subject}` | `onSelectionChange`, `onBeforeClose` | `callback`, `cb` |
|
|
49
|
+
| Ref | camelCase + `Ref` 后缀 | `inputRef`, `scrollContainerRef` | `ref1`, `myRef` |
|
|
50
|
+
| Context | `{Domain}Context` | `UserContext`, `ThemeContext` | `userCtx`, `themeContextValue` |
|
|
51
|
+
| Provider | `{Domain}Provider` | `UserProvider` | `UserContextProvider`(冗余) |
|
|
52
|
+
| HOC | `with{Capability}` | `withAuth`, `withErrorBoundary` | `authHOC` |
|
|
53
|
+
| 错误类 | PascalCase + `Error` 后缀 | `ValidationError`, `NetworkError` | `ErrValidation` |
|
|
54
|
+
|
|
55
|
+
## 组件命名深入
|
|
56
|
+
|
|
57
|
+
### 语义优先于类型
|
|
58
|
+
|
|
59
|
+
- ✅ `UserAvatar` / `PrimaryButton` / `EmptyState`
|
|
60
|
+
- ❌ `DivBox` / `Component1` / `MyComponent`
|
|
61
|
+
|
|
62
|
+
### 前缀用于区分用途
|
|
63
|
+
|
|
64
|
+
| 前缀 | 用途 | 例 |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| `App` | 应用外壳 | `AppHeader`, `AppShell` |
|
|
67
|
+
| `Page` | 路由级页面 | `PageUserProfile`(或目录 `pages/user-profile/`)|
|
|
68
|
+
| `Layout` | 布局容器 | `LayoutDashboard` |
|
|
69
|
+
| `Modal` / `Drawer` / `Popover` | 浮层类型 | `ConfirmModal`, `UserDrawer` |
|
|
70
|
+
| `Icon` | 图标组件 | `IconClose`, `IconCheck` |
|
|
71
|
+
|
|
72
|
+
### 禁用
|
|
73
|
+
|
|
74
|
+
- ❌ `I{Name}` 风格接口(Hungarian):用 `UserProfile` 不用 `IUserProfile`
|
|
75
|
+
- ❌ `{Name}Component` 后缀:`UserCard` 已经暗示是组件
|
|
76
|
+
- ❌ 无意义后缀 `Wrapper` / `Container` / `Box`(除非确实只是布局容器)
|
|
77
|
+
|
|
78
|
+
## 事件命名深入(handle vs on)
|
|
79
|
+
|
|
80
|
+
最常被搞混的一对。
|
|
81
|
+
|
|
82
|
+
| 场景 | 用 `handle` 还是 `on` | 例 |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| 组件内部定义的函数 | `handle` | `const handleClick = () => { ... }` |
|
|
85
|
+
| 作为 Prop 传给子组件 | `on` | `<Child onItemClick={handleClick}/>` |
|
|
86
|
+
| 声明 Props 类型 | `on` | `type Props = { onSelect: (id: string) => void }` |
|
|
87
|
+
|
|
88
|
+
**推断逻辑**:`on` = 对方告诉我"发生了什么",`handle` = 我决定"怎么响应"。
|
|
89
|
+
|
|
90
|
+
### 具体动作命名
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
// ✅ 清晰
|
|
94
|
+
const handleSubmit = () => { ... };
|
|
95
|
+
const handleItemClick = (id: string) => { ... };
|
|
96
|
+
const handleSearchChange = (value: string) => { ... };
|
|
97
|
+
|
|
98
|
+
// ❌ 模糊
|
|
99
|
+
const onClick = () => { ... }; // 函数还是 Prop?
|
|
100
|
+
const click = () => { ... }; // 动词作名词
|
|
101
|
+
const handleIt = () => { ... }; // "it" 指什么?
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 布尔命名深入
|
|
105
|
+
|
|
106
|
+
前缀传达语义:
|
|
107
|
+
|
|
108
|
+
| 前缀 | 语义 | 例 |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| `is` | 当前状态 | `isLoading`, `isOpen`, `isSelected` |
|
|
111
|
+
| `has` | 拥有 / 存在 | `hasError`, `hasItems`, `hasPermission` |
|
|
112
|
+
| `can` | 能力 / 权限 | `canEdit`, `canSelect` |
|
|
113
|
+
| `should` | 建议 / 预期 | `shouldRender`, `shouldAnimate` |
|
|
114
|
+
| `did` | 完成动作(过去) | `didLoad`, `didMount`(罕见)|
|
|
115
|
+
|
|
116
|
+
### 禁用
|
|
117
|
+
|
|
118
|
+
- ❌ `loading`(名词还是布尔?)
|
|
119
|
+
- ❌ `!disabled`(双重否定)— 换成 `isEnabled`
|
|
120
|
+
- ❌ `error` 作布尔(`error` 应是对象/消息,`hasError` 才是布尔)
|
|
121
|
+
|
|
122
|
+
## Hook 命名深入
|
|
123
|
+
|
|
124
|
+
| 类型 | 命名 | 例 |
|
|
125
|
+
| --- | --- | --- |
|
|
126
|
+
| 数据 fetch | `use{Resource}` | `useUser`, `useProducts` |
|
|
127
|
+
| mutation | `use{Action}{Resource}` | `useCreateUser`, `useUpdateOrder` |
|
|
128
|
+
| UI 状态 | `use{Feature}State` | `useModalState`, `useSelectionState` |
|
|
129
|
+
| 事件 | `use{Event}` | `useClickOutside`, `useKeyPress` |
|
|
130
|
+
| 派生值 | `use{Computed}` | `useFilteredItems`, `useSortedList` |
|
|
131
|
+
|
|
132
|
+
返回值约定:
|
|
133
|
+
|
|
134
|
+
- 单一值 → 直接返回(`const user = useUser(id)`)
|
|
135
|
+
- 多个值 → 返回对象(`const { data, error, isLoading } = useUser(id)`)
|
|
136
|
+
- 状态 + 更新器(对象 / 数组皆可,项目内保持一致)
|
|
137
|
+
|
|
138
|
+
## i18n key 命名
|
|
139
|
+
|
|
140
|
+
详见 [`i18n`](../i18n/SKILL.md),概要:
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
{namespace}.{feature}.{element}.{variant?}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
| key | 用途 |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `order.list.empty` | 订单列表空态 |
|
|
149
|
+
| `order.list.error.network` | 订单列表网络错误 |
|
|
150
|
+
| `common.button.confirm` | 通用确认按钮 |
|
|
151
|
+
| `user.profile.avatar.alt` | 用户头像 alt 文本 |
|
|
152
|
+
|
|
153
|
+
- 全小写 + `.` 分隔
|
|
154
|
+
- 不含中文、空格、下划线(除非项目 locale 工具强制)
|
|
155
|
+
- 语义优先于位置(不要 `page1.btn3`)
|
|
156
|
+
|
|
157
|
+
## TS 类型命名
|
|
158
|
+
|
|
159
|
+
### 常见模式
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
// 数据模型:名词单数
|
|
163
|
+
type User = { id: string; name: string };
|
|
164
|
+
|
|
165
|
+
// Props:组件名 + Props
|
|
166
|
+
type UserCardProps = { user: User; onSelect?: (id: string) => void };
|
|
167
|
+
|
|
168
|
+
// 回调签名:动词 + Handler / 参数 + Callback
|
|
169
|
+
type SelectHandler = (id: string) => void;
|
|
170
|
+
type ChangeCallback<T> = (value: T) => void;
|
|
171
|
+
|
|
172
|
+
// API 响应 / 请求:{Domain}{Action}Response
|
|
173
|
+
type UserListResponse = { items: User[]; total: number };
|
|
174
|
+
type UserCreateRequest = { name: string; email: string };
|
|
175
|
+
|
|
176
|
+
// 枚举(倾向字面量联合 > enum)
|
|
177
|
+
type Status = 'idle' | 'loading' | 'success' | 'error';
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### 禁用
|
|
181
|
+
|
|
182
|
+
- ❌ `IUser` 前缀(Hungarian)
|
|
183
|
+
- ❌ `UserType` / `UserInterface` 后缀(冗余)
|
|
184
|
+
- ❌ `TUser` 泛型式前缀(用于 generic 占位符是另一回事)
|
|
185
|
+
|
|
186
|
+
## 文件夹组织命名
|
|
187
|
+
|
|
188
|
+
```
|
|
189
|
+
src/
|
|
190
|
+
components/ kebab-case 目录 or PascalCase 目录,项目内保持一致
|
|
191
|
+
UserCard/
|
|
192
|
+
index.tsx
|
|
193
|
+
UserCard.test.tsx
|
|
194
|
+
UserCard.module.less
|
|
195
|
+
useUserCardLogic.ts
|
|
196
|
+
hooks/ 独立 hook 放这里
|
|
197
|
+
useDebouncedValue.ts
|
|
198
|
+
pages/ 路由页面
|
|
199
|
+
user-profile/ kebab-case(URL 一致)
|
|
200
|
+
index.tsx
|
|
201
|
+
utils/ 纯函数
|
|
202
|
+
formatDate.ts
|
|
203
|
+
types/ 全局类型
|
|
204
|
+
user.ts
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**选择 PascalCase 组件目录 vs kebab-case**:看项目 `eslint` / `import/no-unresolved` 配置和操作系统大小写敏感性。**一经确定,全项目统一**。
|
|
208
|
+
|
|
209
|
+
## 反模式(禁止)
|
|
210
|
+
|
|
211
|
+
- ❌ `data` / `item` / `value` 这种无信息量的变量名在作用域大时使用
|
|
212
|
+
- ❌ 缩写(除非行业常识:`id` / `url` / `api` / `ui`)— 用 `config` 不用 `cfg`
|
|
213
|
+
- ❌ 同一概念多个名字(`userId` / `user_id` / `uid` 混用)
|
|
214
|
+
- ❌ 中文拼音(`xuanze` / `dingdan`)
|
|
215
|
+
- ❌ 含数字后缀(`data1` / `data2`)— 改名或集合化
|
|
216
|
+
|
|
217
|
+
## 质量门禁
|
|
218
|
+
|
|
219
|
+
- [ ] 组件 / 文件 / hook / 类型符合命名总表
|
|
220
|
+
- [ ] 事件 handler 用 `handle`,事件 Prop 用 `on`
|
|
221
|
+
- [ ] 布尔带语义前缀(`is` / `has` / `can` / `should`)
|
|
222
|
+
- [ ] i18n key 按 `namespace.feature.element` 结构
|
|
223
|
+
- [ ] 无 Hungarian 前缀(`I...` / `T...` 例外)
|
|
224
|
+
- [ ] 无中文 / 拼音 / 无意义缩写
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: zsk:review-frontend
|
|
3
|
+
description: Frontend PR code review — frontend-specific self-checklist and
|
|
4
|
+
reviewer checklist covering TypeScript red lines (no any / @ts-ignore), React
|
|
5
|
+
hooks rules, dangerouslySetInnerHTML, large-list performance hazards, visual
|
|
6
|
+
regression, i18n three-language sync, jsx-a11y. Extends zsk:reviewing generic
|
|
7
|
+
checklist.
|
|
8
|
+
category: stage
|
|
9
|
+
domain: frontend
|
|
10
|
+
tier: optional
|
|
11
|
+
stage: 5
|
|
12
|
+
variants:
|
|
13
|
+
- feature
|
|
14
|
+
- bugfix
|
|
15
|
+
- refactor
|
|
16
|
+
related:
|
|
17
|
+
- ../../sdlc/reviewing/SKILL.md
|
|
18
|
+
- ../typescript/SKILL.md
|
|
19
|
+
- ../react-components/SKILL.md
|
|
20
|
+
- ../security-web/SKILL.md
|
|
21
|
+
- ../a11y-web/SKILL.md
|
|
22
|
+
- ../i18n/SKILL.md
|
|
23
|
+
- ../performance-web/SKILL.md
|
|
24
|
+
- ../testing-web/SKILL.md
|
|
25
|
+
triggers:
|
|
26
|
+
- frontend code review
|
|
27
|
+
- React review checklist
|
|
28
|
+
- TypeScript red lines
|
|
29
|
+
- hooks review
|
|
30
|
+
- visual regression review
|
|
31
|
+
- i18n three language check
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
# Stage 5: Reviewing · Frontend 补丁
|
|
35
|
+
|
|
36
|
+
> **用途**:在 [`zsk:reviewing`](../../sdlc/reviewing/SKILL.md) 通用 checklist 上追加的前端专属检查项
|
|
37
|
+
> **范围**:TS / React / dangerously / 视觉回归 / 三语 i18n / jsx-a11y / 性能 hazard
|
|
38
|
+
|
|
39
|
+
## 自评清单(前端补丁,Author 推前必过)
|
|
40
|
+
|
|
41
|
+
### TypeScript 红线(见 [`frontend/typescript`](../typescript/SKILL.md))
|
|
42
|
+
- [ ] 无新增 `any` / `as any` / `@ts-ignore` / `@ts-nocheck`
|
|
43
|
+
- [ ] 新增公共边界(Props / 导出函数 / 导出类型)显式声明类型
|
|
44
|
+
- [ ] API 类型引自 `{{config.paths.api_contracts}}/...`,未手写
|
|
45
|
+
- [ ] 联合类型用字面量(`'loading' | 'error'`),不用字符串魔法
|
|
46
|
+
|
|
47
|
+
### React hooks & 组件(见 [`frontend/react-components`](../react-components/SKILL.md))
|
|
48
|
+
- [ ] `useEffect` 依赖项正确(无 `react-hooks/exhaustive-deps` 违规,除非 ADR 豁免该行)
|
|
49
|
+
- [ ] Hooks 顶层调用(无条件 / 循环里调用)
|
|
50
|
+
- [ ] `useMemo` / `useCallback` 不过度使用也不缺位(有依赖或回调稳定性需求才用)
|
|
51
|
+
- [ ] 列表 `key` 稳定(不用数组下标)
|
|
52
|
+
- [ ] Context 不放频繁变动的值(避免大范围重渲染)
|
|
53
|
+
|
|
54
|
+
### 安全(见 [`frontend/security-web`](../security-web/SKILL.md))
|
|
55
|
+
- [ ] 无 `dangerouslySetInnerHTML`(或有 ADR 豁免 + DOMPurify 清洗)
|
|
56
|
+
- [ ] `href` / `src` 接受用户输入时已校验协议
|
|
57
|
+
- [ ] 敏感字段不写入 LocalStorage / SessionStorage
|
|
58
|
+
- [ ] 生产无 `console.log`(`console.warn` / `console.error` 允许且脱敏)
|
|
59
|
+
- [ ] 日志 / 上报经过脱敏过滤器
|
|
60
|
+
|
|
61
|
+
### 视觉 / 交互
|
|
62
|
+
- [ ] 视觉回归截图或差异记录已归档
|
|
63
|
+
- [ ] 新增 / 修改状态:loading / empty / error / hover / focus-visible / selected / disabled 全覆盖
|
|
64
|
+
- [ ] `focus-visible` 样式存在,未被 `outline: none` 移除
|
|
65
|
+
- [ ] `prefers-reduced-motion` 场景有降级
|
|
66
|
+
|
|
67
|
+
### i18n(见 [`frontend/i18n`](../i18n/SKILL.md))
|
|
68
|
+
- [ ] 所有可见文案用 `i18n.t(key, fallback, values?)`
|
|
69
|
+
- [ ] 三语(`{{config.i18n.languages}}`)**在同一 commit 同步**
|
|
70
|
+
- [ ] fallback 等于基线语言文案
|
|
71
|
+
- [ ] 复数 / 选择场景用 ICU 格式
|
|
72
|
+
- [ ] 日期 / 数字用 `Intl.*`,未硬编码格式
|
|
73
|
+
|
|
74
|
+
### a11y(见 [`frontend/a11y-web`](../a11y-web/SKILL.md))
|
|
75
|
+
- [ ] 新增可交互元素有键盘支持
|
|
76
|
+
- [ ] 图片有 `alt` 属性(装饰图 `alt=""`)
|
|
77
|
+
- [ ] 表单 `label` 与输入关联(`htmlFor` / 包裹)
|
|
78
|
+
- [ ] 未使用 `tabindex > 0`
|
|
79
|
+
- [ ] 对比度达标(文字 4.5:1 / 大字号 3:1)
|
|
80
|
+
- [ ] `axe-core` 零 critical / serious violation
|
|
81
|
+
|
|
82
|
+
### 性能 hazard(见 [`frontend/performance-web`](../performance-web/SKILL.md))
|
|
83
|
+
- [ ] 大列表 / 树(节点 ≥ 200)已用虚拟滚动
|
|
84
|
+
- [ ] 搜索 / 输入类 handler 有防抖(300ms 起步)
|
|
85
|
+
- [ ] 图片有 `width/height` 属性 + `loading="lazy"` 声明
|
|
86
|
+
- [ ] 新增依赖未导致首屏 JS 增长 > 10%(或有 ADR 豁免)
|
|
87
|
+
|
|
88
|
+
### Bugfix / Refactor 专用(前端补丁)
|
|
89
|
+
- [ ] Bugfix:复现测试 `git checkout HEAD^` 后仍失败(真的在测 bug)
|
|
90
|
+
- [ ] Refactor:视觉回归**零差异**(前端硬门禁)
|
|
91
|
+
- [ ] Refactor:包体积变化 ≤ ±2%(前端硬门禁)
|
|
92
|
+
|
|
93
|
+
## Reviewer 评审清单(前端补丁)
|
|
94
|
+
|
|
95
|
+
### 契约一致性
|
|
96
|
+
- [ ] Props / Events 与 `spec.md` 的 Component Public Contract 一致
|
|
97
|
+
- [ ] 类型引自 `{{config.paths.api_contracts}}` 而非手写
|
|
98
|
+
- [ ] 没有悄悄扩大公开接口(新增未声明的 Props / Event)
|
|
99
|
+
- [ ] 受控 / 非受控边界与 Spec 声明一致
|
|
100
|
+
|
|
101
|
+
### 边界与错误(前端)
|
|
102
|
+
- [ ] loading / empty / error 都有 UI
|
|
103
|
+
- [ ] 异步取消 / 竞态处理(`AbortController` 或框架等价)
|
|
104
|
+
- [ ] 网络失败 / 超时 / 权限不足 的 fallback UI
|
|
105
|
+
- [ ] ErrorBoundary 位置正确
|
|
106
|
+
|
|
107
|
+
### 测试覆盖(前端)
|
|
108
|
+
- [ ] 金字塔三层按 design.md 策略就位(unit + integration + e2e)
|
|
109
|
+
- [ ] a11y 自动化测试存在(axe-core / jsx-a11y lint)
|
|
110
|
+
- [ ] 视觉回归截图已归档
|
|
111
|
+
- [ ] 见 [`frontend/testing-web`](../testing-web/SKILL.md) 的完整工具链
|
|
112
|
+
|
|
113
|
+
### 代码风格(前端)
|
|
114
|
+
- [ ] 命名符合 [`frontend/react-naming`](../react-naming/SKILL.md)
|
|
115
|
+
- [ ] CSS 遵循 [`frontend/css-bem`](../css-bem/SKILL.md)(BEM / 无 `!important` / 无深度嵌套)
|
|
116
|
+
- [ ] 无过度注释 WHAT(见 [`quality/code-hygiene`](../../quality/code-hygiene/SKILL.md))
|
|
117
|
+
- [ ] 没有未被调用的新抽象(YAGNI)
|
|
118
|
+
|
|
119
|
+
## 质量门禁(前端补丁)
|
|
120
|
+
|
|
121
|
+
- [ ] 自评清单(前端补丁)全绿
|
|
122
|
+
- [ ] Reviewer 评审清单(前端补丁)全绿
|
|
123
|
+
- [ ] 视觉回归对照已归档
|
|
124
|
+
- [ ] axe-core 零 critical / serious
|
|
125
|
+
|
|
126
|
+
> 通用 reviewing 门禁(自评 / Conventional Comments / 分歧升级)见 [`zsk:reviewing`](../../sdlc/reviewing/SKILL.md)
|