React 第6周笔记 阶段项目 —— 语义色板生成器

路线图阶段:第一阶段(React 基础)第6周 —— 收官项目 项目:语义化配色生成器(主色 → 推导 5 个角色色) 本周不学新语法,而是综合前 5 周全部技能,做一个能上线、能进作品集的成品 部署:Vercel + GitHub 自动部署


项目定位:为什么这个项目”有价值”

不是随机配色器,而是语义色板生成器:把一个主色当”数据源(source of truth)“,用 HSL 数学推导出一整套有角色的颜色(Primary / Secondary / Accent / Background / Foreground)。

这正是 design engineer 真实工作的缩影(design tokens / 主题系统):底层是配色逻辑,上层是消费它的 UI。比随机配色器更能讲出故事。


1. 核心架构:逻辑层 / UI 层分离(本周最重要的工程思维)

颜色逻辑层(纯函数,零 React)        React 表现层(组件)
hexToRgb / rgbToHsl / hexToHsl      ColorPalette(管 state)
generatePalette                     ColorPicker / ColorInput
hslToCss / isValidHex / textColorFor  → 消费逻辑层的输出来渲染

纯函数(Pure Function):给定输入永远返回相同输出,不碰 React、不产生副作用。

类比设计:Figma 的变量/样式(token)是逻辑层,组件是表现层。定义一次 token,到处引用。纯函数就是代码里的 token 逻辑层。


2. 颜色模型:为什么用 HSL 不用 RGB

HSL 把颜色拆成符合人直觉的三个维度:

H (Hue 色相)        0–360°  红?蓝?绿?(色环角度)
S (Saturation 饱和度) 0–100% 鲜艳还是灰暗
L (Lightness 亮度)   0–100% 亮还是暗

有了 HSL,“推导配色”就是简单数学:

RGB 做不到这种直觉操作,所以必须 HEX → RGB → HSL 转换。


3. 推导规则:generatePalette

function generatePalette({ h, s, l }) {
  const background = { h: wrapHue(h),       s: 20,    l: 95 };  // 同色相,极浅,低饱和
  const foreground = { h: wrapHue(h),       s: 25,    l: 15 };  // 同色相,极深
  const primary    = { h: wrapHue(h),       s,        l };       // 主色本身
  const secondary  = { h: wrapHue(h + 30),  s: s - 2, l: l + 1 }; // 类比色(小角度)
  const accent     = { h: wrapHue(h - 150), s: s + 3, l: l - 4 }; // 强调色(大角度)
  return { primary, secondary, accent, background, foreground };
}

关键设计点

色环边界处理:wrapHue

色相是(0°=360°),加减后可能越界(负数或 >360),要”绕回来”:

function wrapHue(h) {
  return ((h % 360) + 360) % 360; // 经典写法:处理负数 + 超界,落回 0–359
}

S 和 L 是 0–100 的直线不是环,越界要用”夹紧(clamp)“而非 wrap。但微调幅度小时一般不越界,先不处理,别提前复杂化。


4. React 层:一个数据源 + 派生

const [primaryHex, setPrimaryHex] = useState("#C16952"); // 唯一数据源
const palette = generatePalette(hexToHsl(primaryHex));   // 派生,当场算,不存 state

主色一变 → 重渲染 → palette 重算 → 整套色卡自动更新。 五个角色色全是派生状态,一个都不存 state(呼应第5周相册的 filteredPhotos)。

⚠️ 初始值必须是合法 hex(如 “#C16952”),不能用空字符串 ""。 空字符串会让 hexToHsl("") 算出 NaN,配色崩溃/白屏。color picker 也必须有合法初始值。


5. 双输入同步 + 草稿状态(Draft State)

取色器 + 文字框两种输入主色,但文字框不能直接绑 primaryHex:用户逐字输入时,中间状态(“#A0B4”)是不完整 hex,会让 hexToHsl 算出 NaN 崩掉。

解法:文字框用独立”草稿”state,合法了才提交

const [primaryHex, setPrimaryHex] = useState("#C16952"); // 驱动配色
const [hexInput, setHexInput] = useState("#C16952");      // 文字框草稿

function handleHexInput(e) {
  const value = e.target.value;
  setHexInput(value);          // 草稿永远更新(用户能看到自己打的字)
  if (isValidHex(value)) {     // 仅当合法 hex 才提交
    setPrimaryHex(value);
  }
}

function handlePickerChange(e) {
  const value = e.target.value;
  setPrimaryHex(value);  // 取色器吐出的一定合法,直接提交
  setHexInput(value);    // 同时同步文字框,保持一致
}

这是”两个 state”的正当场景:草稿值 和 已确认主色 是两个不同的东西,不能互相派生。 (对比派生状态原则:能算出来的才不存;这里草稿是独立输入,必须存。)

isValidHex:正则验证

function isValidHex(hex) {
  return /^#[0-9A-Fa-f]{6}$/.test(hex); // # + 恰好 6 位十六进制字符
}

6. 用 Object.entries + 数组解构渲染色卡

palette 是对象,不能直接 .map()。用 Object.entries 转成数组:

Object.entries(palette)
// [["primary", {h,s,l}], ["secondary", {h,s,l}], ...]  每项是 [角色名, 颜色对象]

{colorsArray.map(([role, color]) => (   // 数组解构:方括号,按位置取
  <div key={role} style={{ backgroundColor: hslToCss(color) }}>
    {role}
  </div>
))}

7. 自动文字色(可读性)

深色块配白字、浅色块配黑字,用亮度 L 阈值自动决定:

function textColorFor({ l }) {
  return l > 55 ? "#1a1a1a" : "#ffffff";
}

设计时凭眼睛判断的事,用 L 阈值量化成代码。


8. 点击复制 HEX:navigator.clipboard

const [copied, setCopied] = useState(null); // 记录刚复制的角色

async function handleCopy(role, hex) {
  try {
    await navigator.clipboard.writeText(hex); // ⚠️ 必须 await,catch 才能抓到失败
    setCopied(role);
    setTimeout(() => setCopied(null), 1000);  // 1 秒后清除提示
  } catch (error) {
    console.log("复制失败:", error);
  }
}

9. cursor / hover / 事件 的区别(易混)


10. 部署:Vercel + GitHub 自动部署

本地 npm run build 验证  →  git push 到 GitHub
  →  Vercel 用 GitHub 登录 → Import 仓库
  →  自动识别 Vite(Build: npm run build, Output: dist)→ Deploy
  →  得到 https://xxx.vercel.app 链接

⚠️ 作品集仓库要独立、命名规范(color-palette-generator),别用 my-react-week1 这种学习文件夹推。 ⚠️ README 只写真实实现的功能,未完成的标成 roadmap。作品集造假是面试大忌。


第6周验收


第一阶段(第1-6周)总收尾

周次主题项目
1-2组件 / props / state / 事件名片 / 主题切换 / 计数器(自学)
3列表 / 表单 / 不可变更新Todo List
4useEffect / 异步 / API小相册
5组件拆分 / 状态提升带搜索的相册
6综合 / 部署语义色板生成器(作品集)

贯穿始终的核心心智模型

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


下一步:第二阶段(第7-10周)预告