第10周笔记-把配色生成器改写成 TypeScript(实战迁移)

s

路线图阶段:第二阶段(TypeScript)第10周 —— 第二阶段毕业项目 任务:把第一阶段的配色生成器从 JS 完整迁移到 TS 核心收获:亲身体会 TS 如何在”跑代码之前”抓出名实不符的 bug


0. 迁移的两个准备动作

1. 文件后缀:.jsx.tsx React + TS 用 .tsx,才能既写 JSX 又写类型。

2. 顺序:先逻辑层(纯函数),再 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)

rgbToHslr /= 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周验收


第二阶段(第7-10周)总收尾

周次主题性质
7数组方法 map/filter/reduce/find/some/every/includes补 JS 地基
8解构 / 展开运算符 / async/await + fetch补 JS 地基
9TypeScript 入门:类型/推断/interface/props/useState学 TS
10把配色生成器迁移成 TypeScriptTS 实战

贯穿的收获


下一步:第三阶段(第11-18周)预告 —— Design Engineer 工具箱