第10周笔记-把配色生成器改写成 TypeScript(实战迁移)
s
路线图阶段:第二阶段(TypeScript)第10周 —— 第二阶段毕业项目 任务:把第一阶段的配色生成器从 JS 完整迁移到 TS 核心收获:亲身体会 TS 如何在”跑代码之前”抓出名实不符的 bug
0. 迁移的两个准备动作
1. 文件后缀:.jsx → .tsx
React + TS 用 .tsx,才能既写 JSX 又写类型。
2. 顺序:先逻辑层(纯函数),再 UI 层(组件)
- 逻辑层不涉及 React,简单,适合练手起点。
- UI 层会引用逻辑层的类型,先把底层定义好,上层才好引用。
- (这一步能这么顺,正是第6周”逻辑/UI 分离”架构的回报。)
1. 先定义复用的类型(全局地基)
项目里两种颜色对象反复出现,先定义成 interface,全项目复用:
interface RGB {
r: number;
g: number;
b: number;
}
interface HSL {
h: number;
s: number;
l: number;
}
// 五个角色色组成的调色板 —— interface 可以嵌套引用 interface
interface Palette {
primary: HSL;
secondary: HSL;
accent: HSL;
background: HSL;
foreground: HSL;
}
Palette里每个字段都是HSL—— 这是”对象里装对象”的描述方式:interface 嵌套引用 interface。
2. 逻辑层:给纯函数加类型
模式统一:参数标类型、返回值标类型(回忆判断法:TS 看不到值的地方才标)。
function hexToRgb(hex: string): RGB { ... } // 字符串进,RGB 出
function rgbToHsl({ r, g, b }: RGB): HSL { ... } // 解构参数标在 } 后面
function hexToHsl(hex: string): HSL { ... }
function wrapHue(h: number): number { ... } // 数字进,数字出
function hslToCss({ h, s, l }: HSL): string { ... } // HSL 进,CSS 字符串出
function isValidHex(hex: string): boolean { ... } // 返回 true/false → boolean
function textColorFor({ l }: HSL): string { ... } // 只用 l,但类型仍是完整 HSL
function generatePalette({ h, s, l }: HSL): Palette { ... }
⚠️ 关键认知1:类型描述”传进来什么”,不是”用了几个字段”
function textColorFor({ l }: HSL): string { ... }
// ↑ 只解构出 l 来用
// 但传进来的是完整 HSL 对象 → 类型标完整 HSL
函数内部只挑 l 用是它自己的事;参数类型要如实描述”调用时传进来的对象长什么样”(完整 HSL)。
标成 { l: number } 反而会让传完整 HSL 时被 TS 挑剔”多了 h、s”。
⚠️ 关键认知2:TS 当场抓”名实不符”(本周最大价值)
迁移中踩的两次错,方向一致 —— 习惯性想标成颜色对象类型:
function hexToRgb(hex: string): HSL { return { r, g, b }; }
// ❌ 函数返回 {r,g,b}(RGB),却标了 HSL → TS 立刻报错
interface ColorPickerProps { value: HSL; }
// ❌ value 实际收到 primaryHex("#C16952" 字符串),却标 HSL → TS 立刻报错
教训:改类型前先问”这个位置实际流过的值,长什么样?” hex 是字符串(string);只有
{h,s,l}/{r,g,b}这种对象才是 HSL/RGB。 JS 会默默放过这种名实不符,TS 在跑代码前就拦住 —— 这就是它的核心价值。
备注:参数重新赋值(no-param-reassign)
rgbToHsl 里 r /= 255 修改了解构出的参数。功能安全(数字按值复制,不影响原对象),TS 也不报错。
但部分团队 ESLint 规则(no-param-reassign)会建议别重新赋值参数。现在不用改,知道有这回事即可。
3. UI 层:给 React 组件 props 加类型
React 事件处理函数的类型
input 的 change 事件,类型是固定的一长串:
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
// ↑ React 的变化事件 ↑ 哪个元素的事件(input)
这类”长得吓人”的类型不用背,要认识。遇到 input 事件就用这个模式,需要时查得到、复制得来,完全符合”不求精通”。
组件 props interface
interface ColorPickerProps {
value: string; // hex 字符串,不是 HSL!
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
}
function ColorPicker({ value, onChange }: ColorPickerProps) { ... }
ColorInput 的 props 和 ColorPicker 完全一样(value: string + onChange 事件)。
长得一样的 interface 以后可合并复用,但现在分开写更清楚,别提前优化。
4. 主组件 ColorPalette:state + 函数类型综合
三个 useState
const [primaryHex, setPrimaryHex] = useState("#C16952"); // 推断 string,不用标
const [hexInput, setHexInput] = useState("#C16952"); // 推断 string,不用标
const [copied, setCopied] = useState<string | null>(null); // null 初始 → 手动标联合类型
判断法:初始值能代表类型 → 推断;初始值是 null/将来会变 → 用 <Type | null> 手动标。
事件处理 + 自定义函数
function handleHexInput(e: React.ChangeEvent<HTMLInputElement>) { ... }
function handlePickerChange(e: React.ChangeEvent<HTMLInputElement>) { ... }
async function handleCopy(role: string, hex: string) { ... }
// ↑ 自己定义的函数,参数都是字符串
Object.entries 的小坑(知道即可)
const colorsArray = Object.entries(palette);
// TS 推断大致是 [string, HSL][]
// 后面 .map(([role, color]) => ...) 解构时,TS 能自动推断 role/color 类型
大多数情况不用额外标。哪天 TS 推断不够准再处理,现在能跑就行(不求精通)。
第10周验收
- 会把 .jsx 项目迁移成 .tsx
- 先定义复用 interface(RGB/HSL/Palette),含嵌套引用
- 给纯函数标参数 + 返回值类型
- 懂”类型描述传进来什么,不是用了几个字段”
- 会标 React 事件类型 React.ChangeEvent
- 给组件 props / state / 函数完整加类型,零类型错误且功能不变
- 体会 TS 在编译期抓”名实不符”的价值
第二阶段(第7-10周)总收尾
| 周次 | 主题 | 性质 |
|---|---|---|
| 7 | 数组方法 map/filter/reduce/find/some/every/includes | 补 JS 地基 |
| 8 | 解构 / 展开运算符 / async/await + fetch | 补 JS 地基 |
| 9 | TypeScript 入门:类型/推断/interface/props/useState | 学 TS |
| 10 | 把配色生成器迁移成 TypeScript | TS 实战 |
贯穿的收获
- 第7-8周把第一阶段”用过但没吃透”的 JS 正式补牢。
- 第9-10周达成路线图目标:能舒服地读写带类型的 React(不求精通)。
- TS 的核心价值:在跑代码前,抓出 JS 会默默放过的名实不符 bug。
下一步:第三阶段(第11-18周)预告 —— Design Engineer 工具箱
- Tailwind CSS:回头把配色生成器 UI 重做成精致版 v2(终于能追求那两张参考图的质感了)。
- Storybook:隔离环境开发组件 + 写文档。
- Design tokens、基础无障碍(a11y)、Framer Motion。
- 阶段大作业:小而精致的组件库(按钮/输入框/卡片/弹窗)。
- ⚠️ 进入你设计背景的主场。但仍按路线图节奏,别贪多。