📝 docs(code-reader): React 钩子原型的函数参数一律收成 fold,函数体展开成子块

This commit is contained in:
cruldra committed 2026-10-16 21:35:43 +11:00
1 parent 67e8a77f5d
commit a139341132
5 files changed
+3262 -2767

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 KiB

After

Width:  |  Height:  |  Size: 271 KiB

+7 -3
View File
@@ -53,6 +53,7 @@ const K = {
component: { label: "React 组件", color: "#61dafb", icon: "atom" }, component: { label: "React 组件", color: "#61dafb", icon: "atom" },
handler: { label: "事件处理函数", color: "#dcdcaa", icon: "mouse-pointer-click" }, handler: { label: "事件处理函数", color: "#dcdcaa", icon: "mouse-pointer-click" },
render: { label: "渲染输出", color: "#89d185", icon: "layout-template" }, render: { label: "渲染输出", color: "#89d185", icon: "layout-template" },
return: { label: "返回", color: "#89d185", icon: "corner-down-left" },
}; };
const MONO = "Menlo"; const MONO = "Menlo";
@@ -842,8 +843,11 @@ const changed = (name) => [seg(name, C.variable), seg(" 变化时", C.keyword)];
const ON_MOUNT = [seg("仅挂载时", C.keyword)]; const ON_MOUNT = [seg("仅挂载时", C.keyword)];
const NO_DEPS = [seg("没写,", C.desc), seg("每次渲染后", C.keyword)]; const NO_DEPS = [seg("没写,", C.desc), seg("每次渲染后", C.keyword)];
/** 函数参数收成的小框:朝右的折叠箭头,写「函数 (参数) · N 句」,没参数时不写括号;同调用链原型的闭包小框。 */ /**
function fnBox(params, count) { * 函数参数收成的小框:折叠箭头,写「函数 (参数) · N 句」,没参数时不写括号;同调用链原型的闭包小框。
* 默认收起(箭头朝右),`open` 时展开(箭头朝下),函数体是卡片下面的子块。
*/
function fnBox(params, count, open) {
const segs = [seg("函数", C.desc)] const segs = [seg("函数", C.desc)]
.concat(params ? [seg(" (", C.fg)].concat(params, [seg(")", C.fg)]) : []) .concat(params ? [seg(" (", C.fg)].concat(params, [seg(")", C.fg)]) : [])
.concat([seg(" · " + count + " 句", C.desc)]); .concat([seg(" · " + count + " 句", C.desc)]);
@@ -859,7 +863,7 @@ function fnBox(params, count) {
fill: C.input, fill: C.input,
stroke: C.borderStrong, stroke: C.borderStrong,
}); });
icon(pill, "chevron-right", C.desc, 13); icon(pill, open ? "chevron-down" : "chevron-right", C.desc, 13);
text(pill, segs, { mono: true, size: 12.5, lh: 18 }); text(pill, segs, { mono: true, size: 12.5, lh: 18 });
}, },
}; };
+45 -6
View File
@@ -5,6 +5,7 @@
// 画板一照抄 luffy-agent-platform 的 frontend/client-nextjs/src/app/desktop/login/page.tsx 第 1–37 行, // 画板一照抄 luffy-agent-platform 的 frontend/client-nextjs/src/app/desktop/login/page.tsx 第 1–37 行,
// 画板二是手写的三行,讲没标类型、函数形式的默认值、只取当前值三种写法; // 画板二是手写的三行,讲没标类型、函数形式的默认值、只取当前值三种写法;
// 画板三把其它常用钩子照同一个思路画:卡片讲这个变量(或这条语句)是什么,钩子退到「来自」一行,样例是手写的搜索面板。 // 画板三把其它常用钩子照同一个思路画:卡片讲这个变量(或这条语句)是什么,钩子退到「来自」一行,样例是手写的搜索面板。
// 钩子收的函数参数一律只画小框「函数 · N 句」,函数体是卡片下面的子块(画板三的 visible 画成展开的样子)。
// 窗口、对齐、别的卡片的画法在同目录 react-shared.js,接在它后面跑:`cat react-shared.js react-state.js | op design - --script`。 // 窗口、对齐、别的卡片的画法在同目录 react-shared.js,接在它后面跑:`cat react-shared.js react-state.js | op design - --script`。
// ─── 样例源码 ──────────────────────────────────────────────────────────────── // ─── 样例源码 ────────────────────────────────────────────────────────────────
@@ -243,8 +244,32 @@ const line = (label, value, o) => (ctx) => prop(ctx, label, value, o);
const fromRow = (hook, tip, linked) => (ctx) => prop(ctx, "来自", (frame) => const fromRow = (hook, tip, linked) => (ctx) => prop(ctx, "来自", (frame) =>
text(frame, [seg(hook, C.fn, linked ? { u: true } : {})], { mono: true, name: `${hook}(悬停:${tip})` })); text(frame, [seg(hook, C.fn, linked ? { u: true } : {})], { mono: true, name: `${hook}(悬停:${tip})` }));
/** 收起的函数体一行:小框「函数 (参数) · N 句」,同 [fnBox]。 */ /** 钩子收的函数参数一行:只画小框「函数 (参数) · N 句」,同 [fnBox],函数体是卡片的子块;`open` 时小框是展开的样子。 */
const fnRow = (label, params, count) => (ctx) => prop(ctx, label, (frame) => fnBox(params, count).draw(frame)); const fnRow = (label, params, count, open) => (ctx) => prop(ctx, label, (frame) => fnBox(params, count, open).draw(frame));
/**
* 一张卡片连同它展开的子块:卡片下面隔 8px 是下一层,左边一条竖线标出这一层(同编辑器、预览对齐里的子块竖线),
* `children` 在竖线右边,宽度少 20px。
*/
function withChildren(drawCard, children) {
return (parent, w) => {
const block = col(parent, "Block", { gap: 8 });
drawCard(block, w);
const level = row(block, "Level", { gap: 8, align: "start" });
const guide = col(level, "Guide", { width: 12, height: "fill_container", align: "center" });
I(guide, { type: "rectangle", name: "Line", width: 1, height: "fill_container", fill: solid(C.guide) });
const list = col(level, "Blocks", { gap: 8 });
children(list, w - 20);
};
}
/** 一行译文的「返回」卡片,照现有返回卡片:返回图标、绿色描边,译文按词性着色。 */
function returnCard(src) {
return (parent, w) => {
const ctx = card(parent, K.return, w);
text(ctx.content, cx(src), { mono: true, maxW: ctx.w });
};
}
/** 带「N 个」和展开表的一行(参数)。 */ /** 带「N 个」和展开表的一行(参数)。 */
const tableRow = (label, cols, rows) => (ctx) => { const tableRow = (label, cols, rows) => (ctx) => {
@@ -258,11 +283,13 @@ const changedAll = (names) => names.flatMap((name, i) => (i > 0 ? [seg("、", C.
/** /**
* 一处 useState:名称照源码写出解构(展开是「第几项 | 名称 | 说明 | 操作」,当前值查谁用了它,修改函数查谁改了它)/ * 一处 useState:名称照源码写出解构(展开是「第几项 | 名称 | 说明 | 操作」,当前值查谁用了它,修改函数查谁改了它)/
* 来自 / 状态类型(没写时「未标注」)/ 默认值。`names` 是解构出的名字,`calls` 是各项在样例里出现几次。 * 来自 / 状态类型(没写时「未标注」)/ 默认值。`names` 是解构出的名字,`calls` 是各项在样例里出现几次。
* 默认值是函数时 `fold` 写「closed」:函数体是卡片的子块,收起。
*/ */
function stateCard(s) { function stateCard(s) {
return valueCard({ return valueCard({
kind: STATE, kind: STATE,
lang: VAR, lang: VAR,
fold: s.fold,
title: s.names[0], title: s.names[0],
name: { kind: "array", items: s.names.map((name, i) => ({ name, color: i === 0 ? C.variable : C.fn, what: ITEMS[i].what, tip: ITEMS[i].tip, calls: s.calls[i] })) }, name: { kind: "array", items: s.names.map((name, i) => ({ name, color: i === 0 ? C.variable : C.fn, what: ITEMS[i].what, tip: ITEMS[i].tip, calls: s.calls[i] })) },
bubble: s.bubble, bubble: s.bubble,
@@ -369,7 +396,17 @@ windowOf("Prototype / React 状态 · 其他写法", WIN_W + BOARD_GAP, VARIANTS
{ {
from: 2, from: 2,
to: 2, to: 2,
draw: stateCard({ names: ["items", "setItems"], calls: [0, 0], type: [link("Item", C.type), seg("[]", C.fg)], initial: [seg("首次渲染时调用 ", C.fg)].concat(cx("loadItems()")) }), draw: stateCard({
names: ["items", "setItems"],
calls: [0, 0],
fold: "closed",
type: [link("Item", C.type), seg("[]", C.fg)],
initial: (frame) => {
const holder = row(frame, "Initial", { gap: 6, height: 20, width: "fit_content" });
text(holder, "首次渲染时调用", { mono: true });
fnBox(null, 1).draw(holder);
},
}),
}, },
{ {
from: 3, from: 3,
@@ -552,19 +589,21 @@ const INPUT_REF = valueCard({
], ],
}); });
const VISIBLE = valueCard({ // useMemo 的计算函数展开着:函数体只有一个表达式,算 1 句,子块是一张返回卡片
const VISIBLE = withChildren(valueCard({
kind: MEMO_VALUE, kind: MEMO_VALUE,
lang: VAR, lang: VAR,
fold: "open",
title: "visible", title: "visible",
name: "visible", name: "visible",
calls: 1, calls: 1,
tip: "谁用了", tip: "谁用了",
rows: [ rows: [
fromRow("useMemo", BUILTIN_TIP), fromRow("useMemo", BUILTIN_TIP),
line("计算", cx("results.filter((r) => r.hidden 不成立)")), fnRow("计算", null, 1, true),
line("重算时机", changedAll(["results"])), line("重算时机", changedAll(["results"])),
], ],
}); }), returnCard("返回 results.filter((r) => r.hidden 不成立)"));
const SUBMIT = valueCard({ const SUBMIT = valueCard({
kind: MEMO_FN, kind: MEMO_FN,
File diff suppressed because it is too large. Load diff