重构第13周笔记:项目结构化(逻辑/组件/组装三层分离)

路线图阶段:第三阶段(Design Engineer 工具箱)第13周 —— 正经重构 任务:把挤在 App.tsx 里的所有东西,拆成”类型层 / 逻辑层 / 组件层 / 组装层”的标准结构 核心收获:相对路径(../ vs ./)、import/export 判断法的主动运用、隔离环境的资源加载


0. 为什么重构:一个文件装了三类东西

重构前 App.tsx 把三类完全不同的东西塞在一起:

App.tsx(什么都有)
├── 类型定义        RGB / HSL / Palette
├── 颜色逻辑层      9 个纯函数(hexToRgb...generatePalette)
└── UI 组件层       ColorPicker / ColorInput / Swatch / PreviewCard / ColorPalette / App

能跑,但不是组件库该有的样子。week6 立的”逻辑/UI 分离”原则,之前只在概念上分离,这周在物理文件上真正兑现。


1. 目标结构:一个组件一个文件

src/
├── types.ts              类型层(RGB / HSL / Palette)
├── colors.ts             逻辑层(9 个纯函数)
├── components/
│   ├── ColorPicker.tsx   ┐
│   ├── ColorInput.tsx    │ UI 层:一个组件一个文件
│   ├── Swatch.tsx        │
│   └── PreviewCard.tsx   ┘
├── App.tsx               组装层(ColorPalette + App)
└── *.stories.tsx         story 文件

类比设计:像把 Figma 文件从”一个页面塞所有东西”整理成”Tokens 页 / Components 页 / 产品页”。

  • colors.ts = token 逻辑层
  • components/ = 组件库
  • App.tsx = 产品页(只组装,不定义)

2. ⭐ 重构铁律:从底层往上、小步走、每步都跑

最危险的是”一口气全改完然后跑不起来,不知道哪错了”。所以:

顺序:从”没有依赖的”开始,往上层走

第1步:types.ts         (类型,谁都不依赖,最底层)
第2步:colors.ts        (依赖 types)
第3步:components/*.tsx  (依赖 types + colors)
第4步:App.tsx 清理      (依赖上面全部)
第5步:story 归位

为什么从底层开始:底层不依赖别的,抽出来最干净;上层依赖底层,等底层就位了,上层 import 才有地方指。

每完成一步就 npm run dev —— 报错范围锁定在刚改的那一层


3. ⭐ import / export 判断法(本周反复主动用)

别的文件要写出这个名字 → 要 import → 源头要 export;没写出 → 不用管。 (值和类型同一套规则)

实战中的几次判断:

import type vs 普通 import


4. ⭐ 同文件内函数互调:不用 import

// colors.ts 内部
function hexToHsl(hex: string): HSL {
  return rgbToHsl(hexToRgb(hex));   // 调了同文件的两个函数,不用 import
}

import 只用于”跨文件”拿东西。同一个文件里的函数本来就互相看得见(同一作用域)。 所以九个颜色函数整体搬到 colors.ts,它们之间的调用关系原样保留,一行 import 都不用加。只有跨文件依赖(用 types.ts 的类型)才 import。


5. ⭐⭐ 相对路径:../ vs ./(搬进子文件夹最容易栽的坑)

组件搬进 src/components/ 后,路径前缀全变了:

src/
├── types.ts
├── colors.ts
├── App.tsx
└── components/
    └── Swatch.tsx
谁 import 谁路径解释
Swatch.tsxcolors.ts"../colors"从 components/ 出去到 src/ → ../
Swatch.tsxtypes.ts"../types"同上
App.tsxSwatch.tsx"./components/Swatch"从 src/ 进入 components/ → ./xxx/

记忆:

  • ./ = 当前目录(同层)
  • ../ = 上一层目录(出去)
  • ./子文件夹/ = 进入子文件夹

两个方向别搞反:子文件夹里的组件回头拿 colors 用 ../(出去);App 进子文件夹拿组件用 ./components/(进去)。


6. props interface 跟着组件走

SwatchProps 这种”某组件专属的 props 类型” → 搬到该组件文件里,放组件上面。

// Swatch.tsx
import type { HSL } from "../types";
import { hslToCss, hslToHex, textColorFor } from "../colors";

interface SwatchProps {        // 跟着 Swatch 搬过来,放组件上面
  role: string;
  color: HSL;
  isCopied: boolean;
  onCopy: (role: string, hex: string) => void;
}

export function Swatch({ role, color, isCopied, onCopy }: SwatchProps) { ... }

7. ⭐ 隔离环境的资源加载:Storybook 样式丢失

重构后 Storybook 样式全没了 —— week12 预告过、这次踩到的坑。

根因:Storybook 是独立环境,不走 App.tsx。

解法:在 Storybook 自己的入口 .storybook/preview.ts 单独喂一次:

import "../src/index.css";
//      ↑ preview.ts 在 .storybook/,index.css 在 src/ → 出去再进 src

改完重启 Storybook(preview.ts 改动热更新可能不生效)。

举一反三的排查直觉:凡是”app 正常、Storybook 不对”的问题,第一反应 —— 这个东西 Storybook 的入口(preview.ts)加载了吗?(CSS、字体、全局 context、主题 provider 都是这套路。)


8. 作品集卫生:收尾要清干净

重构收尾清理掉:

不是功能问题,是作品集卫生。面试官翻代码看到 console.log / 没用的 import / 死代码,会觉得”没收尾干净”。 和 week6 “README 不造假”、week12 “仓库卫生”是同一类:作品集项目的每个细节都在替你说话。


第13周验收


反复提醒过、需继续巩固的点


下一步(第14周预告)—— 仍在第三阶段