📝 docs(code-reader): React 钩子卡片原型统一钩子图标、按来源分底色

This commit is contained in:
cruldra committed 2026-10-16 18:44:41 +11:00
1 parent 6fa06723e9
commit 5ec8588c70
4 files changed
+5744 -4204

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 469 KiB

After

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

+124 -83
View File
@@ -1,8 +1,8 @@
// 代码阅读 React 钩子卡片:一个 VS Code 窗口里左边是编辑器、右边是代码阅读预览,每张卡片和它那段源码的第一行对齐, // 代码阅读 React 钩子卡片:一个 VS Code 窗口里左边是编辑器、右边是代码阅读预览,每张卡片和它那段源码的第一行对齐,
// 卡片比源码高时,编辑器在那段源码下面留空,下一段源码再和下一张卡片对齐。 // 卡片比源码高时,编辑器在那段源码下面留空,下一段源码再和下一张卡片对齐。
// 每处钩子调用都是同一种「钩子」卡片:钩子 / 类型参数 / 参数 / 返回四行照钩子签名排,各类钩子只在后面追加自己的行, // 每处钩子调用都是同一种「钩子」卡片:钩子 / 类型参数 / 参数 / 返回四行照钩子签名排,各类钩子只在后面追加自己的行;
// 描边、图标按 React 类型定义里钩子的分组取。 // 图标、描边所有钩子一样,淡底按来源分,React 自带的钩子再按作用带一个图标小标签。
// 样例源码 `src/chat/Panel.tsx` 是手写的,两张画板共用:一张画第 1–20 行,一张画第 22–58 行。 // 样例源码 `src/chat/Panel.tsx` 是手写的,两张画板共用:一张画第 1–21 行,一张画第 23–60 行;钩子来自 React、第三方库、项目自己各有一处。
// 颜色、窗口和卡片画法取自同目录 react.js(VS Code Dark Modern,卡片淡底 7%、描边 35%),函数小框取自同目录 rust-chain.js; // 颜色、窗口和卡片画法取自同目录 react.js(VS Code Dark Modern,卡片淡底 7%、描边 35%),函数小框取自同目录 rust-chain.js;
// 卡片里的译文照 vscode/src/codeReader/translators/typescriptZh.ts 的中文直译,渲染大纲的说法照 vscode/src/codeReader/rules/react.ts。 // 卡片里的译文照 vscode/src/codeReader/translators/typescriptZh.ts 的中文直译,渲染大纲的说法照 vscode/src/codeReader/rules/react.ts。
// OpenPencil 自动折行量不准,长文字按估出的宽度手动断行,文字框都按内容长。 // OpenPencil 自动折行量不准,长文字按估出的宽度手动断行,文字框都按内容长。
@@ -77,6 +77,7 @@ const CRUMBS = ["src", "chat", FILE];
const SOURCE = [ const SOURCE = [
"import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, useTransition } from 'react'", "import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState, useTransition } from 'react'",
"import { useRouter } from 'next/navigation'",
"", "",
"const ThemeContext = createContext<Theme>('light')", "const ThemeContext = createContext<Theme>('light')",
"", "",
@@ -98,6 +99,7 @@ const SOURCE = [
"}", "}",
"", "",
"export function Panel({ id, onClose }: Props) {", "export function Panel({ id, onClose }: Props) {",
" const router = useRouter()",
" const theme = useContext(ThemeContext)", " const theme = useContext(ThemeContext)",
" const { messages } = useChatSession(id)", " const { messages } = useChatSession(id)",
" const [open, setOpen] = useState(() => readOpen(id))", " const [open, setOpen] = useState(() => readOpen(id))",
@@ -584,11 +586,12 @@ function tools(parent) {
/** /**
* 一张卡片:折叠箭头(有子块才有:`open` 展开、`closed` 收起)、种类图标、内容、右上角两个按钮。 * 一张卡片:折叠箭头(有子块才有:`open` 展开、`closed` 收起)、种类图标、内容、右上角两个按钮。
* 淡底默认用种类色,给了 `tint` 用它(钩子卡片按来源分底色)。
* `w` 是卡片外宽;返回内容区和内容区的宽度,属性行、表格按它断行。 * `w` 是卡片外宽;返回内容区和内容区的宽度,属性行、表格按它断行。
*/ */
function card(parent, kind, w, o) { function card(parent, kind, w, o) {
const opt = o || {}; const opt = o || {};
const bg = mix(kind.color, C.editor, 0.07); const bg = mix(opt.tint || kind.color, C.editor, 0.07);
const node = box(parent, kind.label + (opt.name ? " / " + opt.name : ""), { const node = box(parent, kind.label + (opt.name ? " / " + opt.name : ""), {
layout: "horizontal", layout: "horizontal",
gap: 10, gap: 10,
@@ -723,19 +726,21 @@ function outline(parent, items, bg) {
return list; return list;
} }
// ─── 卡片内容:第 1–20 行 ──────────────────────────────────────────────────── // ─── 卡片内容:第 1–21 行 ────────────────────────────────────────────────────
const EXPORTED = ["具名导出", C.keyword]; const EXPORTED = ["具名导出", C.keyword];
/** 导入收起时只剩一行:来源和导入项个数。 */ /** 导入收起时只剩一行:来源和导入项个数。 */
function importCard(parent, w) { function importCard(from, count) {
const ctx = card(parent, K.import, w, { name: "'react'", fold: "closed" }); return (parent, w) => {
const line = row(ctx.content, "Summary", { gap: 6, height: 20 }); const ctx = card(parent, K.import, w, { name: from, fold: "closed" });
text(line, "来源", { size: 12.5, color: C.desc }); const line = row(ctx.content, "Summary", { gap: 6, height: 20 });
text(line, "'react'", { mono: true, color: C.string }); text(line, "来源", { size: 12.5, color: C.desc });
text(line, "·", { color: C.dim }); text(line, from, { mono: true, color: C.string });
text(line, "导入项", { size: 12.5, color: C.desc }); text(line, "·", { color: C.dim });
text(line, "10 个", { size: 12.5, color: C.desc }); text(line, "导入项", { size: 12.5, color: C.desc });
text(line, count + " 个", { size: 12.5, color: C.desc });
};
} }
function contextCard(parent, w) { function contextCard(parent, w) {
@@ -765,20 +770,23 @@ function hookCard(parent, w) {
prop(ctx, "参数", px("1 个", C.desc), { mono: false, fold: "open" }); prop(ctx, "参数", px("1 个", C.desc), { mono: false, fold: "open" });
table(ctx, [{ name: "名称", color: C.variable }, { name: "类型", color: C.type }], [["id", cx("字符串")]]); table(ctx, [{ name: "名称", color: C.variable }, { name: "类型", color: C.type }], [["id", cx("字符串")]]);
prop(ctx, "返回", px("1 个", C.desc), { mono: false, fold: "open" }); prop(ctx, "返回", px("1 个", C.desc), { mono: false, fold: "open" });
table(ctx, [{ name: "名称", color: C.variable }, { name: "是什么", mono: false }], [["messages", [link("状态 messages", HOOK.state.color)]]]); table(ctx, [{ name: "名称", color: C.variable }, { name: "是什么", mono: false }], [["messages", [link("状态 messages", HOOK.color)]]]);
} }
// ─── 钩子调用:基础卡片加扩展行 ────────────────────────────────────────────── // ─── 钩子调用:基础卡片加扩展行 ──────────────────────────────────────────────
// 钩子的分类,同 React 类型定义里钩子的分组:卡片描边、图标和钩子名后面的分类小标签按它取 // 所有钩子卡片同一个图标、同一个描边色;淡底按钩子从哪来分:React 自带、第三方库、项目里自己写的
const HOOK = { const HOOK = { label: "钩子", color: "#b180d7", icon: "fishing-hook" };
state: { label: "状态", color: "#9cdcfe", icon: "toggle-right" }, const ORIGIN = { react: "#61dafb", library: "#e5c07b", own: "#89d185" };
effect: { label: "副作用", color: "#e5c07b", icon: "zap" },
ref: { label: "引用", color: "#9cdcfe", icon: "anchor" }, // React 自带钩子的作用,同 React 类型定义里钩子的分组:钩子名后面的小标签带这个图标
perf: { label: "性能", color: "#9cdcfe", icon: "sigma" }, const ROLE = {
context: { label: "上下文", color: "#4ec9b0", icon: "share-2" }, state: { label: "状态", icon: "toggle-right" },
concurrent: { label: "并发", color: "#9cdcfe", icon: "hourglass" }, effect: { label: "副作用", icon: "zap" },
custom: { label: "自定义钩子", color: "#61dafb", icon: "plug" }, ref: { label: "引用", icon: "anchor" },
perf: { label: "性能", icon: "sigma" },
context: { label: "上下文", icon: "share-2" },
concurrent: { label: "并发", icon: "hourglass" },
}; };
// 所有钩子卡片同一个标签列宽,最长的标签是上下文的「没有外层提供时」 // 所有钩子卡片同一个标签列宽,最长的标签是上下文的「没有外层提供时」
@@ -789,6 +797,23 @@ 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)];
/** 中性色的小标签,可以带一个图标:作用、库名、「浏览器绘制前」。 */
function plainTag(parent, label, bg, iconName) {
const pill = row(parent, "Tag / " + label, {
width: "fit_content",
height: 18,
padding: [0, 5, 0, iconName ? 4 : 5],
gap: 3,
radius: 4,
fill: mix("#ffffff", bg, 0.06),
stroke: mix("#ffffff", bg, 0.2),
});
if (iconName)
icon(pill, iconName, C.desc, 11);
text(pill, label, { size: 11, color: C.desc, lh: 16, weight: 500 });
return pill;
}
/** 函数参数收成的小框:朝右的折叠箭头,写「函数 (参数) · N 句」,没参数时不写括号;同调用链原型的闭包小框。 */ /** 函数参数收成的小框:朝右的折叠箭头,写「函数 (参数) · N 句」,没参数时不写括号;同调用链原型的闭包小框。 */
function fnBox(params, count) { function fnBox(params, count) {
const segs = [seg("函数", C.desc)] const segs = [seg("函数", C.desc)]
@@ -814,18 +839,23 @@ function fnBox(params, count) {
/** /**
* 钩子调用的卡片,所有钩子共用一种:钩子 / 类型参数 / 参数 / 返回四行照钩子签名排,没有的行不显示, * 钩子调用的卡片,所有钩子共用一种:钩子 / 类型参数 / 参数 / 返回四行照钩子签名排,没有的行不显示,
* `extend` 在后面追加这类钩子自己的行。描边、图标、分类小标签按 `cat` 取;`fold` 表示有函数参数,函数体子块收起。 * `extend` 在后面追加这类钩子自己的行。图标、描边所有钩子一样,淡底按来源取:
* `params` 每项是「参数 | 值」,`returns` 每项是「名称 | 是什么」,格子同 [table]。 * React 自带的(给 `role`)钩子名后面跟作用小标签,第三方库的(给 `lib`)跟库名小标签,自己写的(`own`)不跟小标签、钩子名能点到定义。
* `fold` 表示有函数参数,函数体子块收起。`params` 每项是「参数 | 值」,`returns` 每项是「名称 | 是什么」,格子同 [table]。
*/ */
function hookCall(parent, w, h) { function hookCall(parent, w, h) {
const kind = { label: "钩子", color: h.cat.color, icon: h.cat.icon }; const tint = h.own ? ORIGIN.own : h.lib ? ORIGIN.library : ORIGIN.react;
const ctx = card(parent, kind, w, { name: h.name + " · " + h.target, fold: h.fold ? "closed" : null, labelW: HOOK_LABEL_W }); const ctx = card(parent, HOOK, w, { name: h.name + " · " + h.target, fold: h.fold ? "closed" : null, labelW: HOOK_LABEL_W, tint });
const line = row(ctx.content, "Prop / 钩子", { gap: 12, align: "start" }); const line = row(ctx.content, "Prop / 钩子", { gap: 12, align: "start" });
const key = row(line, "Key", { width: ctx.labelW, height: 20 }); const key = row(line, "Key", { width: ctx.labelW, height: 20 });
text(key, "钩子", { size: 12.5, color: C.desc, lh: 20 }); text(key, "钩子", { size: 12.5, color: C.desc, lh: 20 });
const value = row(line, "Value", { gap: 6, height: 20 }); const value = row(line, "Value", { gap: 6, height: 20 });
text(value, [seg(h.name, C.fn, h.link ? { w: 600, u: true } : { w: 600 })], { mono: true }); text(value, [seg(h.name, C.fn, h.own ? { w: 600, u: true } : { w: 600 })], { mono: true });
for (const [label, color] of [[h.cat.label, h.cat.color]].concat(h.tags || [])) tag(value, label, color, ctx.bg); if (h.role)
plainTag(value, h.role.label, ctx.bg, h.role.icon);
if (h.lib)
plainTag(value, h.lib, ctx.bg);
for (const label of h.tags || []) plainTag(value, label, ctx.bg);
if (h.typeArg) if (h.typeArg)
prop(ctx, "类型参数", cx(h.typeArg)); prop(ctx, "类型参数", cx(h.typeArg));
if (h.params) { if (h.params) {
@@ -852,38 +882,48 @@ function effectRows(o) {
// ─── 钩子调用:样例里的每一处 ──────────────────────────────────────────────── // ─── 钩子调用:样例里的每一处 ────────────────────────────────────────────────
// 别的卡片点到副作用时用的名字;链接色是它要跳去的钩子卡片的颜色
const SESSION_EFFECT = "副作用「id 变化时」"; const SESSION_EFFECT = "副作用「id 变化时」";
const OPEN_EFFECT = "副作用「open 变化时」"; const OPEN_EFFECT = "副作用「open 变化时」";
const MOUNT_EFFECT = "副作用「仅挂载时」"; const MOUNT_EFFECT = "副作用「仅挂载时」";
/** 第 13 行:自定义钩子里的 `useState`。 */ /** 第 14 行:自定义钩子里的 `useState`。 */
function messagesStateHook(parent, w) { function messagesStateHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.state, role: ROLE.state,
name: "useState", name: "useState",
target: "messages", target: "messages",
typeArg: "Message[]", typeArg: "Message[]",
params: [["initialState", cx("空数组")]], params: [["initialState", cx("空数组")]],
returns: [["messages", "当前值"], [px("setMessages", C.fn), "修改函数"]], returns: [["messages", "当前值"], [px("setMessages", C.fn), "修改函数"]],
extend: (ctx) => prop(ctx, "在哪改", [link(SESSION_EFFECT, HOOK.effect.color), seg(" 里的 ", C.fg)].concat(cx("ws.onmessage"))), extend: (ctx) => prop(ctx, "在哪改", [link(SESSION_EFFECT, HOOK.color), seg(" 里的 ", C.fg)].concat(cx("ws.onmessage"))),
}); });
} }
/** 第 14 行:自定义钩子里的 `useEffect`。 */ /** 第 15 行:自定义钩子里的 `useEffect`。 */
function sessionEffectHook(parent, w) { function sessionEffectHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.effect, role: ROLE.effect,
name: "useEffect", name: "useEffect",
target: "id 变化时", target: "id 变化时",
fold: true, fold: true,
params: [["setup", fnBox(null, 3)], ["deps", changed("id")]], params: [["effect", fnBox(null, 3)], ["deps", changed("id")]],
extend: effectRows({ uses: px("id", C.variable), changes: [seg("状态 ", C.fg), seg("messages", C.variable)], cleanup: "ws.close()" }), extend: effectRows({ uses: px("id", C.variable), changes: [seg("状态 ", C.fg), seg("messages", C.variable)], cleanup: "ws.close()" }),
}); });
} }
function routerHook(parent, w) {
hookCall(parent, w, {
lib: "next/navigation",
name: "useRouter",
target: "router",
returns: [["router", "路由器,用于页面跳转"]],
});
}
function themeHook(parent, w) { function themeHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.context, role: ROLE.context,
name: "useContext", name: "useContext",
target: "theme", target: "theme",
params: [["context", [link("ThemeContext", K.context.color)]]], params: [["context", [link("ThemeContext", K.context.color)]]],
@@ -894,32 +934,31 @@ function themeHook(parent, w) {
function sessionHook(parent, w) { function sessionHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.custom, own: true,
name: "useChatSession", name: "useChatSession",
link: true,
target: "messages", target: "messages",
params: [["id", cx("id")]], params: [["id", cx("id")]],
returns: [["messages", [link("状态 messages", HOOK.state.color)]]], returns: [["messages", [link("状态 messages", HOOK.color)]]],
}); });
} }
function openStateHook(parent, w) { function openStateHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.state, role: ROLE.state,
name: "useState", name: "useState",
target: "open", target: "open",
params: [["initialState", [seg("首次渲染时调用 ", C.fg)].concat(cx("readOpen(id)"))]], params: [["initialState", [seg("首次渲染时调用 ", C.fg)].concat(cx("readOpen(id)"))]],
returns: [["open", "当前值"], [px("setOpen", C.fn), "修改函数"]], returns: [["open", "当前值"], [px("setOpen", C.fn), "修改函数"]],
extend: (ctx) => prop(ctx, "在哪改", [link("toggle", C.fn), seg("、", C.fg), link(OPEN_EFFECT, HOOK.effect.color)]), extend: (ctx) => prop(ctx, "在哪改", [link("toggle", C.fn), seg("、", C.fg), link(OPEN_EFFECT, HOOK.color)]),
}); });
} }
function reducerHook(parent, w) { function reducerHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.state, role: ROLE.state,
name: "useReducer", name: "useReducer",
target: "state", target: "state",
params: [["reducer", [link("reducer", C.fn)]], ["initialArg", cx("{ count: 0 }")]], params: [["reducer", [link("reducer", C.fn)]], ["initialState", cx("{ count: 0 }")]],
returns: [["state", "当前值"], [px("dispatch", C.fn), "派发函数"]], returns: [["state", "当前值"], [px("dispatch", C.fn), "派发函数"]],
extend: (ctx) => { extend: (ctx) => {
prop(ctx, "动作", "", { fold: "open" }); prop(ctx, "动作", "", { fold: "open" });
@@ -934,7 +973,7 @@ function reducerHook(parent, w) {
function transitionHook(parent, w) { function transitionHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.concurrent, role: ROLE.concurrent,
name: "useTransition", name: "useTransition",
target: "isPending", target: "isPending",
returns: [["isPending", "是否正在过渡"], [px("startTransition", C.fn), "启动过渡的函数"]], returns: [["isPending", "是否正在过渡"], [px("startTransition", C.fn), "启动过渡的函数"]],
@@ -943,7 +982,7 @@ function transitionHook(parent, w) {
function boxRefHook(parent, w) { function boxRefHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.ref, role: ROLE.ref,
name: "useRef", name: "useRef",
target: "boxRef", target: "boxRef",
typeArg: "HTMLDivElement", typeArg: "HTMLDivElement",
@@ -951,29 +990,29 @@ function boxRefHook(parent, w) {
returns: [["boxRef", "引用对象"]], returns: [["boxRef", "引用对象"]],
extend: (ctx) => { extend: (ctx) => {
prop(ctx, "指向", [seg("<div>", C.nativeTag), seg(" 元素", C.fg)]); prop(ctx, "指向", [seg("<div>", C.nativeTag), seg(" 元素", C.fg)]);
prop(ctx, "用在", [seg("<div>", C.nativeTag), seg(" 的 ref 属性、", C.fg), link(MOUNT_EFFECT, HOOK.effect.color)]); prop(ctx, "用在", [seg("<div>", C.nativeTag), seg(" 的 ref 属性、", C.fg), link(MOUNT_EFFECT, HOOK.color)]);
}, },
}); });
} }
function timerHook(parent, w) { function timerHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.ref, role: ROLE.ref,
name: "useRef", name: "useRef",
target: "timer", target: "timer",
typeArg: "数值", typeArg: "数值",
params: [["initialValue", [seg("没写,", C.desc), seg("未定义", C.number)]]], params: [["initialValue", [seg("没写,", C.desc), seg("未定义", C.number)]]],
returns: [["timer", "引用对象"]], returns: [["timer", "引用对象"]],
extend: (ctx) => { extend: (ctx) => {
prop(ctx, "写入", [link(OPEN_EFFECT, HOOK.effect.color)]); prop(ctx, "写入", [link(OPEN_EFFECT, HOOK.color)]);
prop(ctx, "读取", [link(OPEN_EFFECT, HOOK.effect.color), seg(" 的清理函数", C.fg)]); prop(ctx, "读取", [link(OPEN_EFFECT, HOOK.color), seg(" 的清理函数", C.fg)]);
}, },
}); });
} }
function totalHook(parent, w) { function totalHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.perf, role: ROLE.perf,
name: "useMemo", name: "useMemo",
target: "total", target: "total",
fold: true, fold: true,
@@ -988,7 +1027,7 @@ function totalHook(parent, w) {
function toggleHook(parent, w) { function toggleHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.perf, role: ROLE.perf,
name: "useCallback", name: "useCallback",
target: "toggle", target: "toggle",
fold: true, fold: true,
@@ -1000,11 +1039,11 @@ function toggleHook(parent, w) {
function openEffectHook(parent, w) { function openEffectHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.effect, role: ROLE.effect,
name: "useEffect", name: "useEffect",
target: "open 变化时", target: "open 变化时",
fold: true, fold: true,
params: [["setup", fnBox(null, 2)], ["deps", changed("open")]], params: [["effect", fnBox(null, 2)], ["deps", changed("open")]],
extend: effectRows({ extend: effectRows({
uses: [seg("open", C.variable), seg("、", C.fg), seg("timer", C.variable)], uses: [seg("open", C.variable), seg("、", C.fg), seg("timer", C.variable)],
changes: [ changes: [
@@ -1022,28 +1061,28 @@ function openEffectHook(parent, w) {
function renderEffectHook(parent, w) { function renderEffectHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.effect, role: ROLE.effect,
name: "useEffect", name: "useEffect",
target: "每次渲染后", target: "每次渲染后",
fold: true, fold: true,
params: [["setup", fnBox(null, 1)], ["deps", NO_DEPS]], params: [["effect", fnBox(null, 1)], ["deps", NO_DEPS]],
extend: effectRows({ uses: px("total", C.variable), changes: cx("document.title") }), extend: effectRows({ uses: px("total", C.variable), changes: cx("document.title") }),
}); });
} }
function layoutEffectHook(parent, w) { function layoutEffectHook(parent, w) {
hookCall(parent, w, { hookCall(parent, w, {
cat: HOOK.effect, role: ROLE.effect,
name: "useLayoutEffect", name: "useLayoutEffect",
target: "仅挂载时", target: "仅挂载时",
tags: [["浏览器绘制前", HOOK.effect.color]], tags: ["浏览器绘制前"],
fold: true, fold: true,
params: [["setup", fnBox(null, 1)], ["deps", ON_MOUNT]], params: [["effect", fnBox(null, 1)], ["deps", ON_MOUNT]],
extend: effectRows({ uses: [link("boxRef", C.variable)], changes: px("无", C.desc), changesMono: false }), extend: effectRows({ uses: [link("boxRef", C.variable)], changes: px("无", C.desc), changesMono: false }),
}); });
} }
// ─── 卡片内容:第 22–58 行 ─────────────────────────────────────────────────── // ─── 卡片内容:第 23–60 行 ───────────────────────────────────────────────────
const CALLBACK = "回调"; const CALLBACK = "回调";
const PROP_COLS = [ const PROP_COLS = [
@@ -1231,7 +1270,7 @@ function groupHeads(parent) {
const right = col(line, "Editor group 2", {}); const right = col(line, "Editor group 2", {});
editorTitle(right, true); editorTitle(right, true);
crumb(right); crumb(right);
filters(right, ["种类", "访问级别"], 5); filters(right, ["种类", "访问级别"], 6);
} }
function status(parent) { function status(parent) {
@@ -1268,30 +1307,32 @@ function windowOf(name, x, bands) {
// ─── 画板 ──────────────────────────────────────────────────────────────────── // ─── 画板 ────────────────────────────────────────────────────────────────────
windowOf("Prototype / React 钩子 · 自定义钩子与上下文", 0, [ windowOf("Prototype / React 钩子 · 自定义钩子与上下文", 0, [
{ from: 1, to: 2, draw: importCard }, { from: 1, to: 1, draw: importCard("'react'", 10) },
{ from: 3, to: 4, draw: contextCard }, { from: 2, to: 3, draw: importCard("'next/navigation'", 1) },
{ from: 5, to: 11, draw: reducerCard }, { from: 4, to: 5, draw: contextCard },
{ from: 12, to: 12, draw: hookCard }, { from: 6, to: 12, draw: reducerCard },
{ from: 13, to: 13, depth: 1, draw: messagesStateHook }, { from: 13, to: 13, draw: hookCard },
{ from: 14, to: 18, depth: 1, last: true, draw: sessionEffectHook }, { from: 14, to: 14, depth: 1, draw: messagesStateHook },
{ from: 19, to: 20 }, { from: 15, to: 19, depth: 1, last: true, draw: sessionEffectHook },
{ from: 20, to: 21 },
]); ]);
windowOf("Prototype / React 钩子 · 组件 Panel", WIN_W + BOARD_GAP, [ windowOf("Prototype / React 钩子 · 组件 Panel", WIN_W + BOARD_GAP, [
{ from: 22, to: 22, draw: panelCard }, { from: 23, to: 23, draw: panelCard },
{ from: 23, to: 23, depth: 1, draw: themeHook }, { from: 24, to: 24, depth: 1, draw: routerHook },
{ from: 24, to: 24, depth: 1, draw: sessionHook }, { from: 25, to: 25, depth: 1, draw: themeHook },
{ from: 25, to: 25, depth: 1, draw: openStateHook }, { from: 26, to: 26, depth: 1, draw: sessionHook },
{ from: 26, to: 26, depth: 1, draw: reducerHook }, { from: 27, to: 27, depth: 1, draw: openStateHook },
{ from: 27, to: 27, depth: 1, draw: transitionHook }, { from: 28, to: 28, depth: 1, draw: reducerHook },
{ from: 28, to: 28, depth: 1, draw: boxRefHook }, { from: 29, to: 29, depth: 1, draw: transitionHook },
{ from: 29, to: 29, depth: 1, draw: timerHook }, { from: 30, to: 30, depth: 1, draw: boxRefHook },
{ from: 30, to: 33, depth: 1, draw: totalHook }, { from: 31, to: 31, depth: 1, draw: timerHook },
{ from: 34, to: 37, depth: 1, draw: toggleHook }, { from: 32, to: 35, depth: 1, draw: totalHook },
{ from: 38, to: 41, depth: 1, draw: openEffectHook }, { from: 36, to: 39, depth: 1, draw: toggleHook },
{ from: 42, to: 44, depth: 1, draw: renderEffectHook }, { from: 40, to: 43, depth: 1, draw: openEffectHook },
{ from: 45, to: 47, depth: 1, draw: layoutEffectHook }, { from: 44, to: 46, depth: 1, draw: renderEffectHook },
{ from: 48, to: 50, depth: 1, draw: handleClickCard }, { from: 47, to: 49, depth: 1, draw: layoutEffectHook },
{ from: 51, to: 57, depth: 1, last: true, draw: renderCard }, { from: 50, to: 52, depth: 1, draw: handleClickCard },
{ from: 58, to: 58 }, { from: 53, to: 59, depth: 1, last: true, draw: renderCard },
{ from: 60, to: 60 },
]); ]);
File diff suppressed because it is too large. Load diff