Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
或本地 tsc 跑一遍再抄 —— 全书反复要求读者去验证,自己更不能想当然;
- 术语第一次出现时中文+英文双写(如「泛型(generics)」),之后可只用惯用形;
- 别怕句子短。短句有力。
- **中文正文在 JSX 里不要折行。** JSX 把跨行文本的换行连接成一个空格,中文字之间、全角标点前后就会多出空格。中文段落在源码里写成一行;中文与英文单词、数字、行内代码之间照常空一格,「——」与运算符两侧也保留空格,其余地方不留。
`test/zh-copy-spacing.test.ts` 扫描全部 TSX 文件,发现多余的空格就报错。

## 课程结构(12 页,由易到难)

Expand Down
166 changes: 55 additions & 111 deletions app/classes/page.tsx

Large diffs are not rendered by default.

29 changes: 11 additions & 18 deletions app/classes/viz.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ export function AccessGate() {
key={gm.key}
type="button"
className={`seg-btn${member === i ? " on" : ""}`}
aria-pressed={member === i}
onClick={() => setMember(i)}
>
{gm.ico} {gm.mod} {gm.key}
Expand All @@ -248,6 +249,7 @@ export function AccessGate() {
key={i}
type="button"
className={`seg-btn${place === i ? " on" : ""}`}
aria-pressed={place === i}
onClick={() => setPlace(i)}
>
{L(gp.label)}
Expand Down Expand Up @@ -302,8 +304,7 @@ export function AccessGate() {
<>
九种组合都点一遍。<b>public 三处全通,protected 两通一拦,private
一通两拦。</b>两条报错值得读细:protected 那条末尾多了{" "}
<code>and its subclasses</code>,private 那条没有。
两种检查都只发生在编译期。
<code>and its subclasses</code>,private 那条没有。两种检查都只发生在编译期。
</>
}
/>
Expand Down Expand Up @@ -353,10 +354,8 @@ const ERASE_TABS: EraseTab[] = [
}
zh={
<>
<b>private 这个词被整个擦掉了。</b>编译产物里就是一个普通属性。
连源码里的 <code>shop[&quot;vaultCode&quot;]</code> TypeScript
都放行 —— 用方括号访问私有成员是它有意保留的后门,连断言都不用写。
运行时,<code>JSON.stringify(shop)</code>、DevTools
<b>private 这个词被整个擦掉了。</b>编译产物里就是一个普通属性。连源码里的 <code>shop[&quot;vaultCode&quot;]</code> TypeScript
都放行 —— 用方括号访问私有成员是它有意保留的后门,连断言都不用写。运行时,<code>JSON.stringify(shop)</code>、DevTools
和任何一个普通 JavaScript 文件都看得见这个值。
</>
}
Expand Down Expand Up @@ -395,8 +394,7 @@ const ERASE_TABS: EraseTab[] = [
<b>#vaultCode 是 JavaScript 的语法,不是 TypeScript 的。</b>
在较新的 target 下它原样输出,运行时类外确实拿不到。类外写{" "}
<code>shop.#vaultCode</code> 是语法错误,
<code>JSON.stringify(shop)</code> 打印出来是 <code>{"{}"}</code>。
target 较老时,编译器会改用 <code>WeakMap</code> 实现,行为一样。
<code>JSON.stringify(shop)</code> 打印出来是 <code>{"{}"}</code>。target 较老时,编译器会改用 <code>WeakMap</code> 实现,行为一样。
</>
}
/>
Expand All @@ -418,6 +416,7 @@ export function ErasureViz() {
key={et.label}
type="button"
className={`seg-btn${tab === i ? " on" : ""}`}
aria-pressed={tab === i}
onClick={() => setTab(i)}
>
{et.label}
Expand Down Expand Up @@ -557,8 +556,7 @@ const CUP_FRAMES: CupFrame[] = [
zh={
<>
第 04 章的规则在 class 上照常生效:<b>编译器比较的是形状,不是名字</b>
。这两个类声明的成员完全一样,所以要 PaperCup 的地方,
给一个 PlasticCup 也接受。
。这两个类声明的成员完全一样,所以要 PaperCup 的地方,给一个 PlasticCup 也接受。
</>
}
/>
Expand All @@ -582,8 +580,7 @@ const CUP_FRAMES: CupFrame[] = [
}
zh={
<>
现在两个类各自声明了一个 <code>private stock</code>。名字一样、
类型一样,赋值依然失败:
现在两个类各自声明了一个 <code>private stock</code>。名字一样、类型一样,赋值依然失败:
<b>
Types have separate declarations of a private property
&apos;stock&apos;.
Expand Down Expand Up @@ -615,9 +612,7 @@ const CUP_FRAMES: CupFrame[] = [
zh={
<>
为什么?对 private 和 protected 成员,TypeScript 比较的是
<b>它们在哪里声明</b>,而不是名字和类型。两处声明就是两个不同的成员。
private 的用意是「这是某一个类的内部细节」,如果别的类能顶替,
这个用意就落空了。这是 class 唯一一处按「出身」而不是按形状比较的地方。
<b>它们在哪里声明</b>,而不是名字和类型。两处声明就是两个不同的成员。private 的用意是「这是某一个类的内部细节」,如果别的类能顶替,这个用意就落空了。这是 class 唯一一处按「出身」而不是按形状比较的地方。
</>
}
/>
Expand All @@ -644,9 +639,7 @@ const CUP_FRAMES: CupFrame[] = [
}
zh={
<>
解法顺着规则就出来了:让两个类 <b>extends 同一个基类</b>。
这时 <code>private stock</code> 只有一处声明,两边都继承自它,
于是重新兼容。
解法顺着规则就出来了:让两个类 <b>extends 同一个基类</b>。这时 <code>private stock</code> 只有一处声明,两边都继承自它,于是重新兼容。
</>
}
/>
Expand Down
Loading
Loading