📝 docs(code-reader): useState 状态卡片原型的解构表改成 第几项 | 名称 | 说明 | 操作

This commit is contained in:
cruldra committed 2026-10-16 21:20:29 +11:00
1 parent 719453a28b
commit 966d893f18
5 files changed
+2321 -1760

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 KiB

After

Width:  |  Height:  |  Size: 777 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

After

Width:  |  Height:  |  Size: 271 KiB

+7 -3
View File
@@ -645,13 +645,15 @@ function nameProp(ctx, name, color, tags, label) {
}
/**
* 表格:`cols` 是 `{name, color?, mono?}`,最后一列分剩下的;格子是着色的段、字符串(按列色)或带 `draw` 的对象。
* 列宽按整列的内容估,表头浅底,行和列之间一条分隔线。
* 表格:`cols` 是 `{name, color?, mono?, width?, fill?}`;格子是着色的段、字符串(按列色)或带 `draw` 的对象。
* 列宽按整列的内容估,给了 `width` 就用它;剩下的宽度归 `fill` 那一列,没有就归最后一列。表头浅底,行和列之间一条分隔线。
*/
function table(ctx, cols, rows) {
const tableW = ctx.w - 5;
const cellText = (cell) => typeof cell === "string" ? cell : Array.isArray(cell) ? cell.map((s) => s.text).join("") : (cell.est || "");
const natural = cols.map((c, i) => {
if (c.width)
return c.width;
let wMax = widthOf(c.name, 12, false);
for (const r of rows) wMax = Math.max(wMax, widthOf(cellText(r[i]), 12.5, c.mono !== false) + (r[i] && r[i].extra ? r[i].extra : 0));
return Math.ceil(wMax + 18);
@@ -659,7 +661,9 @@ function table(ctx, cols, rows) {
const last = cols.length - 1;
const avail = tableW - 2 - last;
const widths = natural.slice();
widths[last] = avail - widths.slice(0, last).reduce((a, b) => a + b, 0);
const fill = cols.findIndex((c) => c.fill);
const flex = fill >= 0 ? fill : last;
widths[flex] = avail - widths.reduce((sum, wd, i) => (i === flex ? sum : sum + wd), 0);
const outer = col(ctx.content, "Table wrap", { padding: [2, 0, 2, 5] });
const grid = col(outer, "Table", { radius: 4, stroke: C.borderStrong, clip: true });
const headBg = mix("#ffffff", ctx.bg, 0.05);
+45 -19
View File
@@ -1,6 +1,7 @@
// 代码阅读 React 状态卡片:useState 不再写成「钩子 useState」,而是一张「状态」卡片,讲清楚五件事:
// 它是普通变量也是 React 状态(左边变量、状态两个图标);它是数组解构(名称照源码写出解构,展开是每一项);
// 它来自 React 内置的 useState(悬停说明);状态类型;默认值。解构出的每一项各有一个调用树按钮:当前值查谁用了它,修改函数查谁改了它。
// 它来自 React 内置的 useState(悬停说明);状态类型;默认值。解构表的「操作」一列放每一项能做的操作,
// 现在是调用树:当前值查谁用了它,修改函数查谁改了它。
// 画板一照抄 luffy-agent-platform 的 frontend/client-nextjs/src/app/desktop/login/page.tsx 第 1–37 行,
// 画板二是手写的三行,讲没标类型、函数形式的默认值、只取当前值三种写法。
// 窗口、对齐、别的卡片的画法在同目录 react-shared.js,接在它后面跑:`cat react-shared.js react-state.js | op design - --script`。
@@ -85,35 +86,51 @@ const VARIANTS = sampleFile({
// 种类「状态」:描边、淡底、两个图标都用变量色;左边先是变量卡片的图标,再是状态图标,悬停才出名字
const STATE = { label: "状态", color: "#9cdcfe", icon: "variable" };
// 解构出的每一项:是什么、它那一行调用树按钮查什么(悬停文字)
// 解构出的每一项:说明、它那一行调用树按钮查什么(悬停文字)
const ITEMS = [
{ what: "当前值", tip: "谁用了这个状态" },
{ what: "修改函数", tip: "谁改了这个状态" },
];
/**
* 「是什么」一格:说法后面跟这一项自己的调用树按钮,`calls` 是它在样例里出现几次;
* `bubble` 时按钮右边画出它的悬停气泡(上方是上一行的按钮,放上面会挡住它)。
*/
function whatCell(item, calls, bubble) {
/** 一个调用树按钮的宽度,同 [callTreeButton]:有次数时图标加小圆标,0 次只有图标。 */
function callTreeWidth(calls) {
return calls === 0 ? 22 : 4 + 14 + 1 + 14 + 4;
}
/** 「操作」一格:这一项能做的操作从左往右排,现在只有调用树。 */
function opsCell(item, calls) {
return {
est: item.what,
extra: 6 + 37,
est: "",
draw: (frame) => {
const line = row(frame, "What", { gap: 6, height: 20, width: "fit_content" });
text(line, item.what, { size: 12.5, lh: 20 });
const line = row(frame, "Ops", { gap: 2, height: 20, width: "fit_content" });
callTreeButton(line, calls, item.tip);
if (bubble)
tooltip(line, item.tip);
},
};
}
/**
* 一处 useState:名称照源码写出解构(后面灰字「数组解构」,展开是「第几项 | 名称 | 是什么」,每一项一个调用树按钮)/
* 第 `n` 行调用树按钮的悬停气泡在卡片里的位置:在按钮正下方居中、隔 6px。
* 按钮上方是上一行的按钮,右边伸出卡片,只有下面空着(「来自」那一行的右半边)。
* 卡片里的位置照 [card]、[table] 的排法算:内容区左边在 84px(内边距 8、折叠箭头 14、两个图标 42、两处间距 10),
* 宽是卡片宽减 150;表从内容区上方 8 + 名称一行 20 + 间距 3 处开始,表格框留白 2、表头 24、每行前面 1px 分隔线、行高 24,
* 一行的各格从表格框左边 5px 排到内容区右边往回 2px,「操作」一格在最右,按钮在格子左边 8px、上边 2px。
*/
function opsBubble(w, n, opsW, calls, tip) {
const contentX = 8 + 14 + 10 + 42 + 10;
const rowEnd = contentX + (w - 150) - 2;
const rowTop = 8 + 20 + 3 + 2 + 24 + (n - 1) * 25 + 1;
const buttonX = rowEnd - opsW + 8;
const buttonBottom = rowTop + 2 + 20;
return { x: buttonX + callTreeWidth(calls) / 2 - tooltipWidth(tip) / 2, y: buttonBottom + 6 };
}
/**
* 一处 useState:名称照源码写出解构(后面灰字「数组解构」,展开是「第几项 | 名称 | 说明 | 操作」)/
* 来自(悬停说是 React 内置钩子)/ 状态类型 / 默认值。
* `names` 是解构出的名字,第二个是修改函数;`calls` 是各项在样例里出现几次。
* `type` 是状态类型(没写时「未标注」),`initial` 是默认值的着色段;`bubble` 画出修改函数那一行调用树按钮的悬停气泡。
* `names` 是解构出的名字,第二个是修改函数;`calls` 是各项在样例里出现几次,「操作」一格里调用树按钮上的数字。
* 「操作」一列按这张卡里按钮最宽的那一行定宽(不窄于表头),「说明」一列分剩下的。
* `type` 是状态类型(没写时「未标注」),`initial` 是默认值的着色段。
* `bubble` 画出修改函数那一行调用树按钮的悬停气泡:卡片和气泡放进一个自由摆放的框,气泡在前,才画在卡片上层。
*/
function stateCard(s) {
return (parent, w) => {
@@ -121,7 +138,16 @@ function stateCard(s) {
{ name: "variable", color: STATE.color, tip: "变量" },
{ name: "toggle-right", color: STATE.color, tip: "React 状态" },
];
const ctx = card(parent, STATE, w, { name: s.names[0], icons });
const opsW = Math.max(Math.ceil(widthOf("操作", 12, false) + 18), 8 + Math.max(...s.calls.map(callTreeWidth)) + 8);
let holder = parent;
let place = {};
if (s.bubble) {
holder = box(parent, "Hover / 谁改了这个状态", { layout: "none", width: w, height: "fit_content" });
const spot = opsBubble(w, 2, opsW, s.calls[1], ITEMS[1].tip);
tooltip(holder, ITEMS[1].tip, spot.x, spot.y);
place = { x: 0, y: 0 };
}
const ctx = card(holder, STATE, w, Object.assign({ name: s.names[0], icons }, place));
prop(ctx, "名称", (frame) => {
const line = row(frame, "Destructure", { gap: 8, height: 20, width: "fit_content" });
const parts = [seg("[", C.fg)];
@@ -134,8 +160,8 @@ function stateCard(s) {
text(line, parts, { mono: true });
text(line, "数组解构", { size: 12.5, color: C.desc });
}, { fold: "open" });
table(ctx, [{ name: "第几项", mono: false }, { name: "名称", color: C.variable }, { name: "是什么", mono: false }],
s.names.map((name, i) => [indexCell(i + 1), px(name, i === 0 ? C.variable : C.fn), whatCell(ITEMS[i], s.calls[i], s.bubble && i === 1)]));
table(ctx, [{ name: "第几项", mono: false }, { name: "名称", color: C.variable }, { name: "说明", mono: false, fill: true }, { name: "操作", width: opsW }],
s.names.map((name, i) => [indexCell(i + 1), px(name, i === 0 ? C.variable : C.fn), ITEMS[i].what, opsCell(ITEMS[i], s.calls[i])]));
prop(ctx, "来自", (frame) => text(frame, px("useState", C.fn), { mono: true, name: "useState(悬停:React 内置钩子)" }));
prop(ctx, "状态类型", s.type || px("未标注", C.desc), { mono: Boolean(s.type) });
prop(ctx, "默认值", s.initial);
File diff suppressed because it is too large. Load diff