diff --git a/docs/superpowers/specs/code-reader/spec.md b/docs/superpowers/specs/code-reader/spec.md index 4690014..c092546 100644 --- a/docs/superpowers/specs/code-reader/spec.md +++ b/docs/superpowers/specs/code-reader/spec.md @@ -142,7 +142,7 @@ 库规则的种类用规则给的图标(`KindBadge.icon`):SQLAlchemy 数据表 `database`、数据类 `package`、Celery 应用 `server-cog`、定时任务 `calendar-clock`、FastAPI 接口 `globe`、FastAPI 路由器 `network`。 - 这组图标由插件自己定义,是一个封闭的集合:契约里的 `KIND_ICON_NAMES`(上表 26 个——命令、管道、执行共用一个,解释器、解析指令共用一个,选项、设置共用一个,字段、标签共用一个——加库规则用的 6 个,lucide 的 kebab-case 名字),类型 `KindIconName` 是它们的联合,`KindBadge.icon` 只能写其中一个,写别的是类型错误。webview 给每个名字配一个逐个 import 的 lucide 图标组件(`kindIcons.tsx` 的 `KIND_ICON_COMPONENTS`,打包只带这些,预览脚本约 157 kB);webview 的 `KindIconName` 照契约手抄,`kindIconNames.test.ts` 核对两边一样。规则要新图标就把名字加进这组、在 webview 配上图标,没有按任意名字找图标的路。 + 这组图标由插件自己定义,是一个封闭的集合:契约里的 `KIND_ICON_NAMES`(上表 26 个——命令、管道、执行共用一个,解释器、解析指令共用一个,选项、设置共用一个,字段、标签共用一个——加库规则用的 9 个(React 组件的原子、副作用的闪电、事件处理的点击手势也在里面),lucide 的 kebab-case 名字),类型 `KindIconName` 是它们的联合,`KindBadge.icon` 只能写其中一个,写别的是类型错误。webview 给每个名字配一个逐个 import 的 lucide 图标组件(`kindIcons.tsx` 的 `KIND_ICON_COMPONENTS`,打包只带这些,预览脚本约 157 kB);webview 的 `KindIconName` 照契约手抄,`kindIconNames.test.ts` 核对两边一样。规则要新图标就把名字加进这组、在 webview 配上图标,没有按任意名字找图标的路。 - 右上角的图标按钮(原型组件 `Reader / BlockTools`,普通块、属性表块、库规则的卡片共用):只有图标、不带字,名字写在悬停提示和 `aria-label` 里;整排是 `role="group"`(`aria-label="块操作"`)。先是「问 AI」(sparkles 图标,点开 / 收起块下面的问答区,展开时带按下的底色、`aria-expanded`;文件不在工作区文件夹里时没有,见「问 AI」),再是「编辑 / 预览」切换:预览时铅笔「编辑」,编辑时眼睛「预览」、带按下的底色(`--vscode-toolbar-activeBackground`)。按钮宽 24px、高 20px(和种类图标同高),图标 14px、`descriptionForeground` 色;悬停 `--vscode-toolbar-hoverBackground`,键盘聚焦描 1px `--vscode-focusBorder`;Tab 能走到,回车 / 空格切换。 - 悬停提示(整个插件的网页只走这一条路,预览和看板共用同一个组件):带提示的元素只写 `data-tooltip="提示"`(名字照旧写在 `aria-label` 里,只有图标的按钮必须有 `aria-label`),页面根上挂一个 `TooltipHost`(`webview-ui/src/components/ui/tooltip.tsx`)统一弹,不用原生 `title`;提示里的换行照样换行(看板摘修复弹框里「迁移」列出的几个文件一行一个);注释、回答里 Markdown 链接的标题(`[文字](网址 "标题")`)也写成 `data-tooltip`。网页的 TSX 里写原生 `title` 属性是 lint 错误(`vscode/eslint.config.mjs`),只有 `iframe` 这种必须带标题的元素例外,组件自己叫 `title` 的属性不算。原因:macOS 上 VS Code 带的 Electron(1.139 是 Electron 43)只在鼠标压着带 `title` 的元素自身边缘时才弹原生提示,停在它里面的图标、文字上不弹(electron/electron#49843,Electron 38 起),种类图标 20px 的框几乎被 16px 的图形占满,基本弹不出来;样子也是系统的。样子照 VS Code 的悬停框:`--vscode-editorHoverWidget-background` 底、`--vscode-editorHoverWidget-border` 边框、`--vscode-editorHoverWidget-foreground` 字、`0 2px 8px --vscode-widget-shadow` 阴影,11px 字,圆角 4px,最宽 22rem;优先在元素上方,上方放不下翻到下方,水平对着元素中间,贴边留 8px,不接鼠标。鼠标停 500ms 才弹,在同一个元素里移动不重新计时,已经弹着时移到另一个带提示的元素上立刻换;移开、移出页面、按下鼠标、按任意键(含 Esc)、滚动就收,收了之后鼠标不离开这个元素不再弹。键盘把焦点移到带提示的元素上(`:focus-visible`)立刻弹,焦点离开就收;提示不是元素自己的名字(`aria-label` 或它的字)时用 `aria-describedby` 挂到元素上,读屏当描述念。 - 种类图标和底色按种类区分(颜色见「颜色」);注释块虚线框。 @@ -814,11 +814,19 @@ export type TokenRole = */ export interface Segment { text: string, role: TokenRole, offset?: number, reference?: RuleReference, color?: ThemeColor, refers?: SourceRange } -/** 框的样式,决定框头图标和框的颜色:'iteration' 是写在表达式里的遍历,图标、颜色同「循环」块。 */ -export type GroupStyle = 'iteration' +/** + * 框的样式,决定框头图标和框的颜色:iteration 写在表达式里的遍历(渲染大纲的「对 … 的每一项」也是),图标、颜色同「循环」块; + * condition 按条件(「当 … 时」「如果 …」),图标、颜色同「条件」块;branch 条件的一支(「是」「否」「否则」),同「条件」块的颜色、不带图标; + * skip 这个条件下不渲染,图标、颜色同「返回」块;tag 小标签(「具名导出」「回调」),只有框头的字、颜色是 color; + * classes 收起来的一串类名(「样式 N 个类」),中性色,点开、悬停看 detail。 + */ +export type GroupStyle = 'iteration' | 'condition' | 'branch' | 'skip' | 'tag' | 'classes' -/** 框起来的一组词:预览画成嵌在前后文字里的小框,框头是图标和 label(翻译插件给);框里的词照样带下标,还能再套框。 */ -export interface SegmentGroup { style: GroupStyle, label: string, segments: Inline[] } +/** + * 框起来的一组词:预览画成嵌在前后文字里的小框,框头是图标和 label(翻译插件、库规则给;只有图标时是空的);框里的词照样带下标,还能再套框。 + * color 盖过样式的颜色(tag 按标签的意思上色),detail 是点开、悬停时看的全文(classes 的全部类名,一行一条)。 + */ +export interface SegmentGroup { style: GroupStyle, label: string, segments: Inline[], color?: ThemeColor, detail?: string } /** 译文里的一段:着色的词,或框起来的一组词。 */ export type Inline = Segment | SegmentGroup @@ -943,14 +951,17 @@ export interface Translator { export interface ThemeColor { dark: string, light: string } /** 种类图标:插件自己定义的一组 lucide 图标(kebab-case 名字);前 22 个给内置种类,后面的给库规则。规则要新图标就加进这组,没有按任意名字找图标的路。 */ -export const KIND_ICON_NAMES = ['import', 'square-function', /* …22 个内置种类的… */ 'database', 'package', 'server-cog', 'calendar-clock', 'globe', 'network'] as const +export const KIND_ICON_NAMES = ['import', 'square-function', /* …22 个内置种类的… */ 'database', 'package', 'server-cog', 'calendar-clock', 'globe', 'network', 'atom', 'zap', 'mouse-pointer-click'] as const export type KindIconName = (typeof KIND_ICON_NAMES)[number] /** 库规则的种类:块左边只画图标(种类色),label 是悬停提示和读屏念的名字,筛选栏里也叫它;icon 只能从 KindIconName 里挑。 */ export interface KindBadge { label: string, color: ThemeColor, icon: KindIconName } -/** 库规则认领一块后给出的替身,下面挂着原块留下的子块:规则自己的卡片,或认成的现成属性表块。 */ -export type RuleBlock = RuleCard | RuleFacts +/** 库规则认领一块后给出的替身,下面挂着原块留下的子块:规则自己的卡片,或认成的现成属性表块;这块讲在了别的卡片里时是略去它。 */ +export type RuleBlock = RuleCard | RuleFacts | RuleOmit + +/** 这块讲在了别的卡片里,不再单独成块(React 的 "use client" 讲成组件卡片的「客户端组件」标签);不能有子块。 */ +export interface RuleOmit { omit: true } /** 规则自己的卡片:一张属性表,种类的图标、颜色、名字和每一行都由规则给。 */ export interface RuleCard { @@ -960,8 +971,15 @@ export interface RuleCard { defines?: SourceRange // 块自己的名字(类名):跳到的定义是它时闪整块 attributes?: BlockAttributes // 规则自己的筛选属性,叠在宿主从原块推出的上面;kind 填了不算;名字由 LibraryRule.attributeNames 给 absorbs: CodeBlock[] // 并进来的子块(原块 children 里的对象本身),不再单独成块;没并的照源码顺序留作子块,编辑范围同别的块,是原块整条语句 + slots?: RuleSlot[] // 画在卡片某一行下面的子块,一块只能放一处 } +/** + * 卡片里的一处子块:block(没并进来的子块)不排在卡片后面,画在第 row 行下面当子卡片,照常转成块(翻译、库规则、编辑、问 AI、差异都照常)。 + * item 是这一行的子行里讲它的那一个:这时它默认收着,点那个子行的第一格才展开、一次只展开一个(React 组件的事件处理表点名称)。 + */ +export interface RuleSlot { block: CodeBlock, row: number, item?: number } + /** 把这块认成契约里现成的一种属性表块(enum 库的类 → 枚举块):宿主当语言插件报的这种块处理,翻译插件排属性表;头部是原块整条语句。 */ export interface RuleFacts { as: BlockFacts @@ -1008,7 +1026,7 @@ export interface LibraryRule { id: string // 'sqlalchemy' label: string // 'SQLAlchemy 数据表' languageIds: string[] - libraries: string[] // 关心的库:导入来源等于它或是它的子模块才启用 + libraries: string[] // 关心的库:导入来源等于它或是它的子模块才启用;空数组是不看导入,languageIds 里这几种语言都启用(React) claim: (block: CodeBlock, context: RuleContext) => RuleBlock | undefined resolve?: (key: string, workspace: RuleWorkspace) => Promise // 查自己标的引用;没给就不能标引用 attributeNames?: (displayLanguage: string | undefined) => AttributeNames // 自己报的筛选属性和值叫什么(同 Translator.attributeNames,用词归规则);翻译插件给了名字的以翻译插件为准 @@ -1072,20 +1090,22 @@ export interface CommandRule { - 注册:`CodeReaderApi.registerLibraryRule(rule)`,返回 `Disposable`;内置规则走同一个 API。 - 必须给:`id`、`label`、`languageIds`、`libraries`(关心的库)、`claim`(看一块,认领就返回替身 `RuleBlock`,不认返回 `undefined`)。 -- 替身(`RuleBlock`)两种,按有没有 `as` 分: +- 替身(`RuleBlock`)三种,按有没有 `omit`、`as` 分: + - 略去(`RuleOmit`,`{ omit: true }`):这块讲在了别的卡片里(React 文件开头的 `"use client"` 讲成组件卡片的「客户端组件」标签,组件上面没并进块头部的 JSDoc 讲成「说明」),预览不画它、不占块的位置(后面的块 id 接着往下数)。它不能有子块,有子块时宿主当替身不合约定。 - 认成现成的属性表块(`RuleFacts`):`as` 是种类和 facts(`BlockFacts`,例如 `{ kind: 'enum', facts: EnumFacts }`),`absorbs` 同卡片。宿主把原块换成这种块(节点不变,头部是原块整条语句,子块是留下的),此后和语言插件报的这种块完全一样:翻译插件的 `describe` 排属性表、种类名取翻译插件的、颜色取这个种类的、筛选属性从 facts 推、块自己的名字从 facts 取。规则不给用词,也不给标签和颜色。只要检查并进来的是不是这块的子块。 - 规则自己的卡片(`RuleCard`),下面几条都说的是它。 -- 卡片必须给:`kind`(写成「规则 id.名字」,宿主核对前缀;筛选记住的就是它,要稳定)、`badge`(种类的名字、暗 / 亮两种颜色和 lucide 图标名)、`rows`(用现成的 `PropertyRow`:属性名 + 着色的值或 Markdown,表格就是带 `columns` 的子行)、`absorbs`(并进来的子块)。可以给:`defines`(块自己的名字,跳到定义是它时闪整块)、`attributes`(自己的筛选属性);表格子行填 `PropertyItem.defines`(字段名),跳到这个定义时只闪那一行。 +- 卡片必须给:`kind`(写成「规则 id.名字」,宿主核对前缀;筛选记住的就是它,要稳定)、`badge`(种类的名字、暗 / 亮两种颜色和 lucide 图标名)、`rows`(用现成的 `PropertyRow`:属性名 + 着色的值或 Markdown,表格就是带 `columns` 的子行)、`absorbs`(并进来的子块)。可以给:`defines`(块自己的名字,跳到定义是它时闪整块)、`attributes`(自己的筛选属性)、`slots`(放进卡片某一行下面的子块,见下);表格子行填 `PropertyItem.defines`(字段名),跳到这个定义时只闪那一行。 +- 放进卡片里的子块(`RuleSlot`):留下的子块(没并进来的)可以不排在卡片后面,画在卡片第 `row` 行下面当子卡片(React 组件的副作用画在「副作用」那一行下面);它照常转成块,照样问各规则(副作用那句也被 React 规则认成自己的卡片),编辑、问 AI、差异、筛选都照常。给了 `item` 的是那一行的子行里讲它的那一个:默认收着,点那个子行的第一格才展开、同一行里别的收起(事件处理表点名称)。一块只能放一处,不能同时并进来;`row` 要有这一行、`item` 要有这个子行。 - 只经契约:规则拿到的是块(kind、node、header、children、facts)和 `RuleContext`;源码里的名字照抄时自己带下标(`Segment.offset`),表达式、类型交给 `context.translate`(当前翻译插件的 `expression`,名字带下标,⌘ + 点击、高亮各处出现照常),说明文字交给 `context.doc`(当前翻译插件的 `doc`,和别的「说明」一样按 Markdown 显示),别的文件里定义的名字交给 `context.lookup`(见「规则认领时查别的文件里的定义」);库自己的配色(HTTP 方法)写在段的 `color` 上。规则可以读自己那门语言的语法树(规则声明了 `languageIds`),但不 import 语言插件、翻译插件和宿主模块(`rules/contractOnly.test.ts` 检查 `rules/` 下非测试文件只 import `../contract` 和同目录的文件)。 - 用词归规则:属性名、表头、种类名、类型译名都在规则自己的词表里,按 `context.displayLanguage`(翻译插件的 `displayLanguage`)挑,没有这种语言的词表就用规则默认的那份;加一种显示语言就是给规则加一份词表。webview 只画结构,不认识「数据表」这些字。 ### 启用、认领、优先 -- 启用:宿主收集这份源码里各层导入块的 `ImportFacts.sources`(去掉引号),有一个等于规则的某个库、或以「库.」「库/」开头(子模块),规则才对这份源码启用。规则不用自己读导入语句。 +- 启用:宿主收集这份源码里各层导入块的 `ImportFacts.sources`(去掉引号),有一个等于规则的某个库、或以「库.」「库/」开头(子模块),规则才对这份源码启用。规则不用自己读导入语句。`libraries` 是空数组的规则不看导入,`languageIds` 里这几种语言的源码都启用(React:新的 JSX 写法不用导入 react)。 - 认领:宿主从顶层往下每块问一遍启用的规则,按注册顺序,第一个认领的算(先注册的优先);被认领的块留下的子块照样问。 -- 出错:规则抛错、或替身不合约定(种类 id 没带规则 id 前缀、并进来的不是这块的子块、属性表里的引用标的不是自己 / 规则没给 `resolve` / 引用的段没带下标)时,宿主记一条警告日志(输出面板「Superpowers」),当它没认、接着问下一个规则;都没认这块照常显示。预览不会因为规则出错而失败。 +- 出错:规则抛错、或替身不合约定(种类 id 没带规则 id 前缀、并进来的不是这块的子块、属性表里的引用标的不是自己 / 规则没给 `resolve` / 引用的段没带下标、放进卡片里的块不是留下的子块 / 放了几处 / 没有那一行或子行、要略去的块还有子块)时,宿主记一条警告日志(输出面板「Superpowers」),当它没认、接着问下一个规则;都没认这块照常显示。预览不会因为规则出错而失败。 - 并进来的子块:不再单独成块,由规则挑(`absorbs`),宿主不替规则多并。没并进来的子块(方法、规则不讲的注释)不管写在哪(字段中间也算),都照源码顺序是替身的子块,预览里照常看得见。替身的编辑范围同别的块,是原块整条语句(从头部开始,到原块连同各子块的最后一行,类就是整个类),盖住留下的子块的行:替身编辑时预览不画这些子块,同一行不会同时在两处能改(见「预览(代码块树)」)。内置规则并成员时连同紧挨在它前面的注释块一起并(讲的是这个成员),讲方法的注释、最后面的注释照常留下。 -- 卡片在预览里:种类图标、颜色取 `badge`(`icon`;颜色暗色、亮色主题各取一个),悬停提示是 `badge.label`,内容是 `rows`;筛选的 `kind` 是替身的种类 id,其余属性从原块推出(原来的类是公共的,数据表也是公共的),叠上规则给的(规则自己的属性叫 `LibraryRule.attributeNames` 给的名字,规则起名时抛错记日志、照写 id);筛选栏里这个种类叫 `badge.label`;和类块一样默认收起子块。 +- 卡片在预览里:种类图标、颜色取 `badge`(`icon`;颜色暗色、亮色主题各取一个),悬停提示是 `badge.label`,内容是 `rows`,放进卡片的子块画在它那一行下面(这一行有折叠箭头,收起时它们跟着收起;画成一层块,同块树),不算卡片的子块(卡片的折叠箭头只管后面留下的子块);筛选的 `kind` 是替身的种类 id,其余属性从原块推出(原来的类是公共的,数据表也是公共的),叠上规则给的(规则自己的属性叫 `LibraryRule.attributeNames` 给的名字,规则起名时抛错记日志、照写 id);筛选栏里这个种类叫 `badge.label`;和类块一样默认收起子块。 ### 规则自己查定义 @@ -1438,6 +1458,79 @@ export interface CommandRule { 配色:名称 function、标签 string、依赖名 function、「(参数名)」hint、表格第一列(项)hint、URL 同上、参数名 variable、类型按翻译插件、异常类名 type、消息和说明 comment、自动带的 422 hint。 +### 内置:React 组件(TypeScript / JavaScript) + +`vscode/src/codeReader/rules/react.ts`(读语法树的办法在 `rules/reactTree.ts`,都是纯函数),`languageIds: ['typescriptreact', 'javascriptreact']`,`libraries: []`(新的 JSX 写法不用导入 react,TSX / JSX 文件都启用)。原型:`code.js` 的「React 组件」一节(`TITLEBAR_CARD`、`LEGAL_CARD`、`reactCard`),插件菜单「React 组件」那一页的四张画板;样例是 luffy 的 `components/desktop/DesktopTitlebar.tsx`(`fixtures/desktopTitlebar.tsx.txt`,简单)、`components/LegalDocPage.tsx`(`fixtures/legalDocPage.tsx.txt`,复杂),说明板的例子 `DeleteAppDialog`、`DesktopSettingsDialog`、`AppVaultSummary`、`ShareButton`、`PosterDialog` 也复制进了 `fixtures/`。三种卡片: + +- 组件:种类 `react.component`,名字「React 组件」,图标 `atom`,颜色暗 #61dafb / 亮 #0a7ea4(组件色,渲染大纲里的组件、「客户端组件」标签同色),和函数块一样默认收起留下的子块。 +- 副作用:种类 `react.effect`,名字「副作用」,图标 `zap`,颜色暗 #e5c07b / 亮 #9a6a00。 +- 事件处理:种类 `react.handler`,名字「事件处理」,图标 `mouse-pointer-click`,颜色暗 #dcdcaa / 亮 #795e26。 + +认哪些是组件:名字大写开头;是函数声明、值是函数的变量(箭头函数、函数表达式,直接返回 JSX 的也算),或被 `memo(…)` / `forwardRef(…)`(`React.memo` 也算,可以套几层)包着的函数;函数自己的 return(嵌套函数里的不算)里有一处返回 JSX(元素、片段,三元、`&&` 的某一支是 JSX 也算;和 `null` 混着返回也算)。小写开头的工具函数、不返回 JSX 的大写函数照常是函数块。组件里又定义的组件自己也是一张组件卡片,不算处理函数。 + +整块换成组件卡片,不再是普通函数卡片的输入参数 / 输出参数。文件开头的 `"use client"`(文件里有组件时)和组件上面没并进块头部的 JSDoc(值是 `memo(…)`、直接返回 JSX 的常量块)略去(`RuleOmit`),讲成卡片上的「客户端组件」标签和「说明」。属性表(有才显示,没有就不画): + +| 行 | 内容 | +| --- | --- | +| 名称 | 组件名(组件色,带下标);后面跟小标签(`tag` 框):「具名导出」「默认导出」(同 import / export 关键字的颜色;`export default X`、`export { X }` 写在文件别处也算)、「客户端组件」(组件色)、「记忆化」「转发 ref」(同 const 关键字的颜色,memo / forwardRef 包着) | +| 说明 | 组件上面的 JSDoc,Markdown | +| 属性 | `N 个`(hint);属性类型写的是一个名字(接口、类型别名、导入的类型)时后面写「 · 来自 XxxProps」,名字带下标(⌘ + 点击跳到类型)。下面一张表格「名称 \| 类型 \| 默认值 \| 必填」,有属性带说明时多一列「说明」。没写参数时写「无」 | +| 状态 | useState / useReducer:「名称 \| 初始值 \| 改它的」 | +| 引用 | useRef:「名称 \| 初始值 \| 用在」 | +| 读取的数据 | 别的钩子(`useXxx(…)` 赋给变量):「变量 \| 来自 \| 意思」 | +| 派生值 | 组件体里别的常量,和 useMemo / useCallback:「名称 \| 怎么算 \| 什么时候算」 | +| 副作用 | `N 个`(hint),下面每个 useEffect / useLayoutEffect / useInsertionEffect 一张副作用子卡片 | +| 事件处理 | 组件体里定义的函数(函数声明、值是函数的常量):「名称 \| 绑定在 \| 做什么」,点名称在表下面展开它的子卡片 | +| 渲染 | `N 处返回`(hint),下面是返回的 JSX 画成的结构大纲 | + +属性表:属性类型的字段照类型里的顺序一行一个(类型字面量、同文件的接口 / 类型别名,交叉类型各部分接起来,`Readonly<…>`、`PropsWithChildren<…>` 看里面;没标注参数类型时 `const X: FC` 的泛型实参、`forwardRef` 的第二个泛型实参),解构里有、类型里没有的接在后面,`...rest` 最后(类型写「其余的属性」)。名称是属性名(定义处带下标:解构出来的是解构那处,没解构的是字段;跳到定义只闪这一行),`children` 后面标「子元素」、函数类型的属性(`(x) => R`、方法签名)标「回调」(组件色的小标签),解构时改了名写「(组件里叫 x)」;类型照翻译插件翻,方法签名写成函数签名;默认值取解构的默认值,没有写「—」;必填看类型的 `?`(是 / 否,storage 色),没类型可看时写了默认值的「否」、别的「—」;说明是字段上面的 JSDoc / 注释、这一行行尾的注释(comment 色,几行接成一行)。类型是导入的名字时经工作区索引查它的字段(索引 `react.propTypes`:看工作区里所有 `**/*.{ts,tsx}`,预筛有 `interface X` / `type X` 才解析,按 typescriptreact 解析;从文件顶层认出接口、类型别名,键是名字、值是各字段的名字、类型写法、可省略、回调、说明的 JSON),查法同「规则认领时查别的文件里的定义」:先照解构的出行、类型「未标注」,查到了按字段重排、重发;别的文件里的类型照写文字(type 色)。 + +状态、引用、读取的数据、派生值: + +- 名称、变量带下标(解构模式照常翻),跳到定义只闪这一行。 +- 状态的初始值:useState 的实参、useReducer 的第二个实参(后面括号里写 reducer);改它的:改状态的函数名(function 色,带下标),后面括号里写在哪儿改,「;」隔开:「副作用」(几个时「副作用 2」)、处理函数名、派生值名、「 的 onChange(传 e.target.value)」(绑在 JSX 属性上;在列表里一项项渲染的前面加「每个」)、「渲染时,当 … 时」(渲染时按条件重置状态的 `if (…) { setX(…) }`:主体都是在调改状态的函数,这句并进卡片)。 +- 引用的用在:「挂在
的 ref 上」(元素写成 ``:组件用组件色、能跳到定义(`refers` 到标签名),原生标签灰色,带 `aria-label` / `title` 字符串时后面写「(返回)」),别处用到的「副作用 1、handleSectionClick 里用它」。 +- 读取的数据的来自照常翻;意思:库自带的钩子照词表(next/navigation 的 `usePathname` 当前路径、`useRouter` 路由器,跳转用、`useSearchParams`、`useParams`、`useSelectedLayoutSegment(s)`,react 的 `useId`、`useTransition`、`useSyncExternalStore`、`useOptimistic`、`useActionState`、`useDeferredValue`,react-dom 的 `useFormStatus`);`useContext(X)` 是「从 X 上下文读」;传选择器的 `useXxx((s) => s.a.b)` 是「从 xxx store 读 a.b」(store 名:导入路径最后一节是 store 时取上一节,`@/lib/desktop/store` 是 desktop;否则去掉 store、use 字样,再不行取钩子名),选择器不是直接取属性的写「从 xxx store 算 …」;自己项目里的(相对路径、`@/` 这类别名、同文件)是「自定义钩子」,别的库的是「xxx 的钩子」(hint)。 +- 派生值的怎么算照常翻:useMemo 是算值的那个表达式,useCallback 写「函数 」加函数;主体是语句块时写「N 句」,那句照常是块、放进这一行对应的子行上,点名称展开(useCallback 的用事件处理子卡片,useMemo 的照常是变量块);什么时候算:普通常量「每次渲染」,useMemo / useCallback 按依赖读(同副作用的时机)、后面写「(useMemo)」。 + +副作用子卡片(组件体里那句 `useEffect(…)` 照常是块,被规则认成这张卡片,放进卡片「副作用」那一行下面):时机按依赖数组读,`[a, b]` 是「a、b 变了时」,`[]` 是「只在挂载时」,没写是「每次渲染后」(keyword 色),useLayoutEffect 后面加「,浏览器绘制前」;做什么是紧贴在上面的注释(几行接成一行,hint 色;这段注释并进组件卡片),没写注释时写「N 句」;副作用函数里的语句照常是块,放进「做什么」那一行下面;最后返回清理函数时那句 return 并进来,多一行「清理」:`() => …` 翻它的主体,语句块一句句翻、「;」隔开。 + +事件处理:表里名称是函数名(function 色,带下标);绑定在从 JSX 属性反查:「