Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

前言

为什么会有这本书?

2013年,我写下了人生中第一行代码。和很多人一样,我学编程的方式是看视频教程、跟着敲代码、复制粘贴。这样做确实能做出东西——一个ToDo List应用、一个天气预报页面、一个博客系统。教程说做什么,我就做什么。教程说怎么改,我就怎么改。

但有一个问题始终困扰着我:离开教程之后,我什么都不会。

我不知道为什么要用这个库而不是那个库。我不知道为什么文件要这样组织而不是那样组织。我不知道当代码报错时,除了重新看一遍教程、或者在CSDN上复制一段代码碰运气之外,还能做什么。我不知道怎么把一个想法——一个完完全全属于自己的想法——从零变成现实。

这种感觉,就像一个学做菜的人,把菜谱背得滚瓜烂熟,但一旦给他一堆陌生的食材,他就愣住了在厨房里手足无措。他缺的不是菜谱,是对食材本身的理解,是刀工火候的直觉,是“这个菜为什么这样做”的第一性原理。

这本书,就是为那些不再满足于“跟着做”、而是想知道“为什么这样做”的人写的。

这本书在讲什么?

本书围绕一个真实完整的开源项目——THE118™ · 3D 交互式化学元素周期表——展开。项目源码托管在 GitHub,基于 MIT 协议开源。你可以在线体验最终效果:the118.pro

在这本书里,我们将从零开始,一步一步把这个项目完整地搭建出来。你会亲手写一个矩阵运算引擎,亲眼看到一个元素周期表从无到有地在你面前诞生。但这本书不只是教你写代码——它会告诉你每一步“为什么这样做”,以及“还有哪些做法,我们为什么选了这一种”。

学完这本书后,你不只会做这个项目,你还会获得一种通用的能力:面对一个未知的技术难题,知道从哪里开始思考、如何拆解问题、怎样做出决策。

这本书适合谁?

如果你符合以下任何一种情况,这本书应该会对你很有帮助:

你是一个前端初学者,已经学过HTML、CSS和JavaScript的基础语法,能写出简单的页面,但不知道怎么把“一堆零散的代码”组织成一个“完整的项目”。你不知道什么是工程化,不知道Git除了 commit 和 push 还能干什么,不知道为什么团队开发需要那么多配置文件。

你是一个自学了一段时间前端的开发者,跟着教程做过几个项目,但总觉得“离开了教程就什么都不会”。你隐约感觉到,自己和“专业前端工程师”之间隔着一层看不见的墙,你想打破它。

你对计算机图形学或者线性代数在实际项目中的应用感到好奇,想看看矩阵到底是怎么让元素在屏幕上转起来的,而不是只在教科书上算行列式。

你想学习“如何设计软件架构”——不只是怎么写一个组件,而是怎么把几十个组件、几十个状态、几十个数据服务组织成一个清晰的、可维护的整体。

你正在准备自己的第一个“拿得出手的作品”——不是教学示例的ToDo List,不是仿写的小米商城,而是一个真正属于你自己的、从零设计并实现的项目,可以自豪地放在简历上。

这本书不适合谁?

坦诚地说,这本书并不适合所有人。如果你属于以下情况,这本书可能会让你感到挫败,我建议你先打好基础再回来看。

你对HTML、CSS、JavaScript完全零基础。 本书不会从“什么是变量”、“什么是DOM”开始讲。如果你还不会写基本的前端三件套代码,请先去学习基础教程,做完几个简单的静态页面,再来读这本书。这不是因为本书的门槛有多高,而是因为我不想浪费你的时间——对着看不懂的术语死磕,不如先去补齐基础。

你想在三天内速成一个项目,应付面试或作业。 这本书不教“最快的做法”,它教“最透彻的做法”。我们会在第6章和第7章停下来,花整整两章的篇幅去写数学工具库——不是因为不会调CSS的 rotateX,而是因为我们要理解底层。如果你追求速成,这本书会让你不耐烦。

你对“为什么要这样做”完全不感兴趣。 如果你的核心诉求是“给我一段能跑的代码就行”,那这本书可能不是最好的选择。这本书最大的篇幅不是用在展示“最终代码”上,而是用在解释我们为什么走到了这里上。每一个技术选型、每一处架构设计,都会展开讨论它的背景、优劣和取舍。

你只想学React,不想碰数学。 本书涉及大量的线性代数——4x4矩阵运算、齐次坐标、变换组合、三角函数。如果你对这些内容完全没有兴趣,只想学React的Hook和组件通信,那我推荐你去看React官方文档或者专门的React教程。本书的数学部分不是点缀,而是核心。

这本书的技术深度

本书的技术覆盖面比较广,但并不追求每个方向的“高级”深度。我们的目标是打通一个完整项目从头到尾的完整链路,让你看到每一个环节的真实样貌。具体来说:

工程化基础:Git、ESLint、Commitlint、Husky、Vite配置、路径别名、环境变量——这些是专业前端开发者的日常工具,本书会在项目初始阶段一次性配好,并解释每个工具的意义。

TypeScript:本书所有代码都用TypeScript编写,不会用 any 来糊弄。但TypeScript本身不是本书的主角——我们不会深入类型体操或高级泛型,我们只用TypeScript做好类型安全这件最基本的事。

线性代数与矩阵运算:这是本书最有特色的部分。我们会从零实现一个4x4矩阵运算库,包括平移、旋转、缩放矩阵的构造,展开循环的矩阵乘法优化,以及复合变换的组合逻辑。不需要你有线性代数的基础——我会用最直观的方式解释每个公式的含义。

React与状态管理:本书使用React 19 + Context + Hooks来构建UI和状态管理。我们会设计四个独立的Context来管理不同的状态域,讨论Provider的嵌套顺序、惰性初始化和性能优化策略。

架构设计:这是本书的另一个核心。我们会先搭建一个传统的分层架构,然后在书的第五部分,主动审视它的缺陷,引入FSD(Feature-Sliced Design)进行重构。你会看到:架构不是一开始就设计完美的,它是演化出来的。

测试与部署:本书会为数学工具和布局算法编写单元测试,并把测试挂到Git Hooks上。最后,我们会用GitHub Pages把项目部署上线,绑定自定义域名。

如何使用本书?

这本书不是一本“地铁读物”——你不会在通勤路上翻两页就能学到东西。它需要你坐在电脑前,打开编辑器,跟着每一章的步骤亲手写代码。

每一章都依赖前面章节的成果,所以建议你按顺序阅读,不要跳章。如果你在某一章卡住了,可以先去GitHub仓库查看对应阶段的源码,对照着找问题。

每一章末尾都有一个“扩展思考”或者“调试彩蛋”——这是留给你的练习题。它们不是选做的,而是帮助你巩固理解的必经之路。

书中的代码并不是“最终版本”。在第16章到第19章的重构部分,我们会主动修改甚至推翻前面的代码。这可能会让你觉得“之前的努力白费了”,但请相信——这不是浪费,这是演化。真实世界的软件就是这样,没有一蹴而就的完美设计。

善用Git。 在每一章结束后,commit你的代码。这样,当你读到重构章节、需要大幅改动代码时,你随时可以回退到之前的版本,或者查看“重构前”和“重构后”的差异。Git不只是存档工具,它是你的学习笔记。

致谢

这本书的诞生,离不开很多人的帮助。

感谢凯尔·辛普森的《你不知道的JavaScript》——这套书教会我,技术写作可以像侦探小说一样引人入胜。你会在本书的叙述风格中,看到它深深的影子。感谢查尔斯·佩措尔德的《编码》——它证明了一件事:再复杂的技术,只要从第一性原理出发,都能被清晰地解释。本书的“不教怎么用、教为什么这样设计”的理念,就来源于此。

感谢你——本书的读者。你选择了一条更难的路:不满足于“能跑就行”,而是追问“为什么能跑”。在这个追求速成的时代,这是一种稀缺而珍贵的品质。

获取更多帮助与反馈

本书的配套源码在 GitHub 上开源,你可以在线体验最终效果:the118.pro。如果你在阅读过程中发现了错误——无论是技术错误、拼写错误还是逻辑错误——欢迎提交Issue或Pull Request。

如果你在使用本书或THE118项目的过程中遇到任何问题,或者有任何建议、合作意向,欢迎通过以下方式与我联系:

我无法保证立刻回复每封邮件,但我一定会认真阅读每一封来信。


由于作者水平和学识有限,书中难免存在疏漏、错误或表述不够准确之处。本书所涉及的技术栈——React、TypeScript、Vite、FSD架构等——均处于快速迭代之中,书中内容虽力求与当前最新版本保持一致,但技术的演进速度往往快于书籍的出版周期。因此,部分细节在您阅读时可能已发生变化,敬请留意相关技术的官方文档以获取最新信息。

敬请广大读者不吝批评指正,并提出宝贵意见。你们的反馈,将是本书持续改进的最大动力。


好了,让我们开始吧。翻开下一页,第1章见。

emmakyitt
2026年7月

第一章:嘿,抬头看!欢迎来到3D元素世界

1.1 一个可能你也曾有过的想法

你可能在化学课上,对着化学课本最后一页的那张密密麻麻的元素周期表发过呆;也可能在网上看到过一些很酷炫的、可以旋转拖拽的3D元素周期表网站。那些红绿蓝紫的小卡片,在屏幕上转来转去,看起来充满了“科技感”。

我也一样。

有一天我盯着屏幕上那个漂亮的3D化学元素周期表,脑子里突然冒出了一个想法,不是“哇,好厉害”,而是:

“如果让我自己从零开始写一个,不依赖任何所谓的‘3D图形库’,我能做到吗?”

这个想法一直在我的脑海中挥之不去。最终,我决定试一试。而这个项目,就是你即将要亲手构建的 THE118™ · 3D 交互式化学元素周期表

它不仅仅是一个Web 3D 项目,更是一个完美的学习沙盒:它有你足够熟悉的业务逻辑(毕竟我们都背过“氢氦锂铍硼”),但它的实现方式,却足以挑战和重塑你对Web前端能力的认知。

通过这本书,你将会:

  • 亲手搭建一个完整的前端工程化项目,而不是用工程模版一键生成。
  • 理解3D图形在浏览器中绘制的数学本质,而不是只会调用库的API。
  • 掌握如何管理一个复杂应用的状态,让代码不再是一团乱麻。
  • 学会用“架构师”的视角去审视代码,并亲手重构它,让它变得更加强壮。
  • 发布你的项目代码并将它部署到互联网上去,让全世界的人都能看到它。
  • 明确你的习路径与职业成长,对它们有清晰的认知和规划能力。

但最重要的是,我希望带给你一种信念:那些看起来无比复杂、透着神秘光芒的软件,在拆开它们外壳之后,都是由最基础、最朴实的技术构成的。你完全可以理解它们,也可以创造它们。

1.2 我们到底要做一个什么东西?

在开始写第一行代码前,我们得先搞清楚我们的目标。用软件工程的话来说,这叫“需求”。

核心功能(MVP,最小可行产品):

  1. 在浏览器中展示118个化学元素卡片,每个卡片至少显示原子序数和元素符号。
  2. 提供5种3D布局方式:标准的表格、一个大球、一段螺旋、一个魔方似的网格、以及一个看起来完全随机的元素分布。
  3. 然后能流畅的切换这五种布局,同时还应该有过渡动画。

交互体验:

  1. 点击一个卡片,它能“到屏幕中央,让你看个清楚。
  2. 双击一个卡片,弹出一个元素详情面板,告诉你它的名字、用途等信息。
  3. 拖拽整个3D场景,它能转起来。且在松手的那一刻,不是立刻停住,而是像一颗打出去的冰球——缓慢停止。

其他一些锦上添花的事:

  • 项目应该支持亮色和暗色主题。
  • 整个项目要严格遵守代码规范

是不是感觉要做的还蛮多的?别怕。我们不会试图一口吃成个胖子。回顾一下我们是抱着什么心态来做这个项目的:学习。甚至我们现在可以非常自信的断定,代码在未来一定是会被我们重写的,因为随着我们能力的增长,我们会越来越看不惯自己刚开始时写的那些“幼稚”的代码。这很正常,甚至可以说是一件值得高兴的事情。

所以,放轻松,享受这个创造的过程。

1.3 为什么我们要选择一条“更难”的路?

现在,我们要做出一个贯穿全书的最重要的决定。这个决定,将会定义我们整个项目的技术灵魂。

如果你去网上搜 “Web 3D 元素周期表”,99%的项目都会用到一个叫做 Three.js 的图形库。Three.js 是一个非常伟大的库,它把复杂的 WebGL 指令包装成简单易懂的 JavaScript 对象,让你能轻松地创建图形、灯光和摄像机等。

那为什么我们不用?

你想象一下,你是想成为一个只会驾驶“自动档汽车”的司机,还是一个能在发动机出问题时,打开引擎盖就知道该拧哪个螺丝的机修师?

Three.js 就是那个“自动档汽车”。它把我们和底层那些迷人的数学原理隔离开了。我们使用它,能做出东西,但我们不知道它为什么能工作。

我们这本书的目标,就是打开那个“引擎盖”。深入到底层,去触摸那些构成了整个3D世界的、看似枯燥却威力无穷的线性代数数学矩阵

这,就是我们选择这条“更难”的路的原因。因为我们追求的不仅仅是“做到”,更是要“知道”。我们要的,是那种“一切尽在掌握”的自由。

1.4 你的学习旅程地图

下面是我们将要一起走过的路。我会把它分成三个大阶段,就像玩游戏升级一样。

第一阶段:新手村 · 磨刀不误砍柴工 (第2章 - 第5章)
在这个阶段,我们不写一行业务代码。我们会专注于搭建一个“专业”的开发环境。这听起来可能有点无聊,但它能让我们从一开始就养成受益终身的职业习惯。

  • 环境与工具: 从零配置 Git, Node.js, Vite, TypeScript。
  • 代码规范: 学会用 ESLint, Commitlint, Husky 来让你的项目保持整洁。

第二阶段:打怪升级 · 从原理到实现 (第6章 - 第15章)
这是本书最核心、最有挑战,也是最有成就感的阶段。我们将从最底层的数学矩阵开始,一步步构建出我们的3D元素舞台。

  • 数学引擎: 亲手写一个4x4矩阵计算工具包。
  • 领域核心: 实现那五种酷炫的元素卡片布局算法。
  • 前端交互: 用React把我们的数据变成看得见、拖得动的3D元素。

第三阶段:觉醒时刻 · 重构与进化 (第16章 - 第25章)
等到整个项目跑起来之后,我们不会就此止步。我们会站在一个更高的角度,审视我们之前写过的代码。

  • 代码评审: 一起找出项目代码中的“坏味道”。
  • 架构重构: 学习更先进的FSD架构,并动手改造我们的项目。
  • 放眼未来: 总结一路走来的收获,并聊聊如何成为一名更专业的软件工程师。

好了,旅行地图已经展开。我们旅程的第一步,将不是令人兴奋的3D画面,而是每一个专业项目开始的地方:需求分析与项目规划。不要小看它,正是这一步,让我们不至于在复杂的代码海洋中迷航。

现在,让我们深吸一口气,翻到下一页。

第二章:看不见的蓝图 —— 需求分析与项目规划

2.1 一个价值百万的好习惯

想象一下,你如果要决定亲手建造一座房子。你会热血上涌,立刻冲到建材市场去买水泥和砖头吗?

当然不会。

你一定会先坐下来,然后找张白纸,画一张草图。标注哪里是客厅,哪里是卧室,门朝哪开,窗户要多大。有了这张图,你才知道该买多少砖,水管怎么铺,电线往哪走。没这张图就开工,建出来的可能不是房子,而是一堆昂贵的废墟。

软件开发和盖房子一模一样。

那张“草图”,在软件开发的世界里,就叫做需求分析项目规划。它可能是你整个项目中最不“性感”的部分——没有代码,没有动画,没有炫酷的效果。

但请相信我,它绝对是你整个项目中最有价值的部分。

为什么?因为这个习惯能把你的思维模式从 “我能做什么” 转换成 “我应该做什么” 。它能帮你在被代码细节吞没之前,先看清楚整片森林的模样。

这是我们给自己的第一个专业训练。

2.2 第一步:把“我想要”变成“用户想要”

千万别被“需求分析”这四个字吓到。对于我们这个独立开发的项目,它其实很简单,就是回答三个问题:

  1. 谁会用它? —— 可能是化学爱好者、学生、或者只是想看点酷炫东西的普通人。
  2. 他们能用它做什么? —— 这是我们定义功能的地方。
  3. 我们如何衡量成功? —— 功能做到什么程度才算“完成了”?

为了更好地回答这些问题,我们引入一个工具,叫做用户想要(User wants)。它的格式长这样:

作为一个 [用户角色],我希望 [做某件事],以便 [达到某个目的]。

这个句式强迫我们站在用户的角度思考问题,把我们自身带入到用户的视角中去,而不是站在开发者的角度去炫技。我们来尝试写几条:

  • 作为一个对化学元素很感兴趣的人,我希望在页面上看到所有118个元素,以便对元素周期表有一个整体的认识。
  • 作为一个好奇心较强的人,我希望能够用鼠标拖拽旋转整个元素集合,以便从不同角度观察它们。
  • 作为一个对视觉感受有极致要求的人,我希望能够切换不同的3D布局(比如球体、螺旋),以便用更直观的方式理解元素之间的关系。
  • 作为一个想要深入研究某个元素的人,我希望点击某个元素后能查看它的详细信息(如名称、用途、原子质量),以便获取我需要的具体知识。

你看,这样一来,我们想要做的那些“功能”就不再是浮于空中的想法,而是一个个服务于具体用户、有具体目的的任务。

2.3 第二步:区分“必要”与“锦上添花”

有了”用户想要“,我们就有了一个功能清单。但一股脑全做进去,我们的项目可能永远也做不完。这时候,我们就必须要学会做一个艰难的判断:分清主次

这里我们要再次引入第一章提到的那个工具,叫做MVP(Minimum Viable Product,最小可行产品)

它不是指一个烂到让人没法用的半成品,它指的是:用最少的功能,来验证我们核心想法的产品。对于THE118™项目,我们的核心想法是“纯数学驱动的3D元素周期表”。那么,能让这个想法跑起来的最小功能集是什么呢?

我们可以把功能分成三类:

  • P0(核心功能,必须做):没有它,项目就不成立。
  • P1(重要功能,应该做):让产品更好用、更完整。
  • P2(增强功能,可以做):锦上添花,未来有时间再加。

来看看我们的清单:

功能优先级理由
展示全部118个元素P0这是周期表的基本定义
5种3D布局及切换动画P0项目的核心亮点,“纯数学驱动3D”的体现
鼠标拖拽旋转场景P1让3D场景真正可交互,极大提升体验
点击元素查看详情P1从“看”到“探索”的升级,让应用更有深度
元素详情面板P1承载详情信息的容器
亮色/暗色主题切换P2提升视觉体验和长时间使用的舒适度
搜索特定元素P2方便用户快速定位,但交互上与点击聚焦有重叠
移动端适配P2让手机用户也能使用,涉及更多交互适配
多语言国际化P2让更多国家的用户能使用

这个优先级列表,就是我们项目的“施工计划书”。我们会严格遵循它,先集中精力完成所有P0功能,让它成为一个能跑通的项目。然后,我们再从容地加上P1和P2,不断完善它。

一个给自己的承诺:在完成P0之前,我绝不会分心去做P2的事情。这是避免项目无限期拖延的黄金法则。

2.4 第三步:画一张“导游图”

文字描述有时不够直观。尤其是当我们在设计交互流程时,一张图胜过千言万语。

现在,我们试着画一张简单的流程图,描述用户从进入网页到查看元素详情的完整路径。这在软件工程中,也是一个标准动作。

text

[用户打开网页]
      │
      ▼
[看到默认的3D布局 (比如表格)]
      │
      ├─ 鼠标拖拽 ───▶ [旋转/缩放场景]
      │
      ├─ 点击底部图标 ───▶ [切换到其他3D布局 (球体/螺旋等)]
      │
      └─ 点击某个元素 ───▶ [元素飞到屏幕中央,高亮显示]
                             │
                             ├─ 点击空白区域 ───▶ [元素退回原位]
                             │
                             └─ 双击该元素 ───▶ [弹出详情面板]
                                                 │
                                                   └── 点击遮罩层 ───▶ [关闭面板]

这张流程图虽然简单,但它就是我们编写代码的“导游图”。当我们迷失在各种 Hooks 和组件里的时候,回到这张图,它总能提醒我们:。

2.5 最后一步:写下我们的里程碑

有了功能清单和交互流程,我们就可以制定一个粗略的版本计划。这不只是为了好看,而是为了给自己设立一个个可以庆祝的“里程碑”,让漫长的开发旅程变得有节奏、有成就感。

我们可以简单地将功能映射到之前设想的三个版本中:

版本核心任务里程碑意义
v0.1.0展示118个元素,实现5种3D布局及切换动画核心引擎跑通:证明我们自研的数学矩阵方法是可行的
v0.2.0加入拖拽旋转、卡片点击选中与聚焦、详情面板交互体验成型:用户终于可以用手去探索这个3D世界了
v1.0.0加入主题切换、搜索、移动端适配、国际化等产品完整发布:这是一个可以骄傲地分享给任何人的作品

好了,我们已经花了一整章的时间,还没写一行代码。你是不是觉得有点手痒了?

但我希望你能记住这种“先规划,后行动”的感觉。这是一种强大的方法论。从下一章开始,我们将要做出一个更关键的、甚至可能有些颠覆常识的技术决策。

我们为什么放弃了别人都在用的 Three.js?又为什么选择了一条看似更难的路——去直接操控 CSS transform 中的 matrix3d() 函数?这背后的“为什么”,才是我们整个项目中最闪亮的部分。

准备好打开“引擎盖”了吗?

第三章:技术选型的艺术 —— 为什么选择“更难”的路?

3.1 一个让你看起来不太聪明的选择

如果此刻有一个经验丰富的前端开发老手路过,瞥见你在构建一个3D元素周期表的项目。他大概会拍拍你的肩膀说:“嘿,用Three.js啊,很简单的,几行代码就搞定了。”

他说得没错。用Three.js来做我们这个项目,确实会省很多力气。

但你回想一下,我们最初的那个念头是什么?

“如果不依赖任何3D库,我能做到吗?”

所以,你要做的,可能是一个在老手看来“不太聪明”的选择。但这恰恰是我们整个项目存在的理由。我们不是要最快地到达终点,我们是要把走向终点的每一步路都看得清清楚楚

请记住这个比喻:你面前有两辆车。一辆是豪华的自动驾驶汽车,你只需说出目的地,它就能把你带到终点;另一辆是手动挡的老式跑车,你需要亲手操控离合、油门和方向盘,去感受每一个机械齿轮的咬合。Three.js 就是那辆自动驾驶汽车。而我们,选择坐进那辆手动挡跑车。

这将是一次“引擎盖下的旅程”。

3.2 Three.js 的魔法,和魔法背后的代价

在用“不好”来评价一个东西之前,我们必须先了解它的“好”。

Three.js 做了什么?

它在我们和 GPU(图形处理器,你电脑里专门处理图像的那块芯片)之间,架起了一座桥。它把“渲染3D场景”这件极其复杂的事情,包装成了几个简单的概念:

  • 场景 (Scene):一个可以容纳所有3D物体的空间。
  • 相机 (Camera):我们的眼睛,决定了我们能看到什么。
  • 物体 (Mesh):由几何体 (Geometry,形状) 和材质 (Material,表面) 组成。
  • 渲染器 (Renderer):负责把相机看到的场景“画”到屏幕上。

用Three.js画一个旋转的立方体,大概只需要写一个类的代码。它会处理好所有复杂的数学问题:光线照射、物体遮挡、视角变换……它全包了,就像是在变魔法。

但魔法带来的问题是什么?

舒适是有代价的。这个代价就是:你不理解它。 你不知道它为什么要创建一个叫 WebGLRenderer 的东西,你不知道你的立方体是如何从一堆坐标点变成屏幕上彩色的像素块的,你更不知道那个控制着物体旋转、平移、缩放的,叫做“矩阵”的东西,到底是个什么玩意。

所以一旦你离开了Three.js,你就什么都不会了。更要命的是,当Three.js的行为和你的预期不一致时,你连从哪开始调试都不知道。你只能去网上搜“Three.js 为什么xxx”,然后寄希望于有人遇到过同样的问题。

但我们不仅仅是来当司机的,我们更是来当一个合格的机修师的。

所以,不是Three.js不好,而是它不适合我们此刻的目标。我们的目标是用一个项目,去撬动对3D渲染基础原理的理解,是要去赋能,而不是被封装。

3.3 那么,原生 CSS 3D 够用吗?

好,我们决定了不依赖 WebGL。那我们用什么在浏览器里画3D图形呢?

答案是:CSS 3D Transform

你用过的 rotateX()rotateY()translateZ(),就是它的一部分。这是浏览器提供的一套原生能力,不需要任何插件或库。

用它们来做我们的五种布局,理论上也是可行的,但过程会非常痛苦。为什么?

旋转矩阵 vs. matrix3d()—— 一个遥控器的比喻

想象一下,你面前有118个元素卡片。你需要让一个ID为5的元素:先绕X轴旋转20度,再沿Y轴平移100px,再绕Z轴旋转-15度,再缩放到0.5倍,再……你可能会写出这样的CSS字符串:

css

/* 这看起来还行,对吧? */
transform: rotateX(20deg) translateY(100px) rotateZ(-15deg) scale(0.5);

这完全没问题,但请思考这几个问题:

  1. 顺序陷阱:你试过把 translateY 放到 rotateX 前面吗?结果完全不一样。当你要对118个元素进行复杂的、顺序敏感的变换时,用空格拼接字符串简直是噩梦。
  2. 性能瓶颈:如果你需要不断地改变一个元素的旋转角度,你只能通过JavaScript去更新这个由多个函数组成的CSS字符串。每一次更新,浏览器都要去解析这个字符串。这在需要同时更新118个元素、且每秒更新60次(动画帧率)时,是巨大的性能浪费。
  3. 无法批量计算:这是我们遇到的最大问题。在每一个模型布局中,我们都需要根据数学公式,为每个元素算出一个唯一的、包含了旋转和平移的最终变换。这种复杂的数学计算,很难映射到一连串分散的CSS变换函数上。

此时用 rotateX()translateY() scale(0.5) 就像是在用一堆功能单一的遥控器,分别按顺序去控制设备。

这就好比,打开门禁你需要找到你家大门的遥控器,打开空调你需拿到空调遥控器,你想看会电视,还得去电视柜里翻出电视机的遥控器。不难想象,管理这一堆“遥控器”是一件很麻烦的事情。

如果将所有的遥控器都统一装到一个APP里,我们只需要用到一台手机,就可以远程控制你家里的所有设备。尽管你人还没回到家,依然可以开启空调,而无需先打开门禁。

transform 中的 “matrix3d()” 就是这样的一个“万能遥控器”,它让我们拥有了一个统一的、可编程的控制接口。可以一次性地把一个元素从初始状态“变”到最终状态

3.4 万能遥控器:matrix3d() 登场

幸好,CSS 提供的不只有零散的工具,它还提供了一个“万能遥控器”:matrix3d()

它接收16个数字,这16个数字构成了一个 4x4 的矩阵。这个矩阵,就是所有3D变换的终极统一表达。

css

/* 一个包含了旋转、平移和缩放的复合变换,被压缩成了一个4x4矩阵 */
transform: matrix3d(
   0.87, 0, -0.5, 0,
   0, 0.5, 0, 0,
   0.5, 0, 0.87, 0,
   100, 50, -200, 1n
);

为什么它这么强大?

1. 它是单一的、可计算的值。
我们不再需要处理一长串的CSS函数。我们可以用JavaScript随心所欲地生成、修改、缓存这16个数字的数组。这为我们的项目带来了决定性的优势:

  • 可批量计算:我们可以在JavaScript里根据球体公式,轻松地用一个循环为118个元素计算出它们各自的 matrix3d 数组。
  • 可缓存:计算好的矩阵数组可以存起来。当用户切换布局时,我们直接把缓存好的矩阵应用到元素上,性能极佳。
  • 可组合:多个复杂的变换,可以通过矩阵乘法合并成一个新的矩阵。这意味着,我们可以把一个元素的“布局位置”和“用户交互旋转”合并,得到一个最终的变换矩阵,一次性应用到元素上。

2. 它给了我们一个机会,去触碰一个更大的世界——线性代数。
一旦你接受了用 matrix3d 来控制元素,你就打开了线性代数的大门。你会开始明白,原来所有的旋转、缩放、平移,本质上都是在改变物体的坐标系。这种认识,能让你在将来面对任何3D图形库(无论是Three.js还是游戏引擎)时,都能一眼看穿它们的底层原理。

所以,当前我们的技术选型,不是基于“方不方便”,而是基于“学不学得到东西”。 我们放弃方便,就是为了能亲手摸到这些底层的基础概念。

对于我们的项目来说,我们需要为118个元素分别计算它们在5种不同布局下的精确位置,这正是用数学矩阵编程控制的优势所在。

3.5 除了matrix3d(),我们还选了些什么?

除了核心的 matrix3d() 渲染方案,我们还需要为项目搭建一个现代前端应用的骨架。这部分我们也会做出深思熟虑的选择。

1. 编程语言:TypeScript

我们从JavaScript换到了TypeScript。为什么?因为当你的代码量超过一定规模(比如我们现在的项目),你会遇到一个很尴尬的问题:不知道这个变量里存的到底是什么。

javascript

// JavaScript: a 是什么?数字?字符串?数组?
function calculate(a) {
    return a + 2; // 如果a是字符串 "1" 呢?
}
typescript

// TypeScript: a 必须是 number,编译阶段就会帮你检查错误
function calculate(a: number): number {
    return a + 2;
}

TypeScript 能在代码运行之前,帮你发现那些低级错误。更重要的是,它的类型注解就像一张活的代码文档,让你在看别人(哪怕是三个月前的自己)写的代码时,不再一头雾水。

2. UI 框架:React

我们选择 React,不是因为它是“最好的”,而是因为它是“最合适的”之一。它基于组件的构建方式,非常适合我们将复杂的3D界面拆分成可管理的积木块。

3. 构建工具:Vite

Vite 是新一代的构建工具,它有两个核心优势:

  • 快得离谱:启动开发服务器几乎在瞬间完成,热更新让你不用刷新就能看到代码变更。
  • 开箱即用:它原生支持 TypeScript、CSS模块等我们需要的技术,几乎不需要复杂配置。

3.6 做决策的艺术:跳出“好不好”,思考“为了什么”

在我们结束这一章之前,我想和你分享一个超越本章具体内容的、更通用的思维模式。

以后,当你自己独立面对一个技术选择时,你会发现网上充满了各种争论:这个好,那个不好。React 好还是 Vue 好?Vite 好还是 Webpack 好?用 Redux 还是不用?

不要陷入这种非黑即白的争论。正确的思维方式是,先问自己三个问题:

  1. 我的目标是什么? 我是要快速上线产品,还是要深入学习原理?我是做一个小工具,还是做需要多人协作的大型应用?
  2. 这个技术解决了什么问题? 它为什么被发明出来?它出现的背景是什么?
  3. 我为此付出了什么代价? 它带来了哪些复杂性?它增加了我的学习成本吗?它锁死了我未来的选择吗?

好的技术决策,不是找到“最好”的技术,而是找到最适合你当前目标的技术。

对于我们来说,我们的目标是学习。所以,我们宁愿选择一条更难、更底层、但能让我们理解原理的路。

好了,技术的航海图已经标好。我们已经决定了方向,解释清了原因。是时候清理我们的船坞,准备开工了。

在开始写核心代码之前,我们先要做好一个容易被忽视,但又极其重要的工作:搭建开发环境。从下一章开始,我们将配置 Git、Node.js、Vite,并逐步引入那些能将我们塑造成专业开发者的工具。

不要急着看3D画面,相信我,把脚手架搭稳,大楼才能盖得高。

第四章:磨刀不误砍柴工 · 搭建开发环境

4.1 一个可能拯救你无数小时的起点

在上一章的末尾,我们说:“先搭好脚手架,大楼才能盖得高。”

你可能已经迫不及待想看到那些元素方块在屏幕上飞了,我完全理解。但请再给我一点点时间。因为在编程的世界里,有一个残酷的真相:你未来80%的抓狂和崩溃,都不是因为你的代码逻辑错了,而可能是你的开发环境没配好。

想象一下,你要画一幅画。你会随便找张皱巴巴的废纸就开始吗?肯定不会。你会先铺好画布,调好颜料,然后再摆好画架。本章要做的,就是为你未来的所有代码,铺好这张平整、干净、专业的“画布”。

我们今天的任务清单如下:

  1. 确认 Node.js 已准备就绪。
  2. 用 Vite 为我们搭好项目骨架。
  3. 引入 TypeScript,给我们的项目代码加上“安全带”。
  4. 配置路径别名,告别可怕的 ../../../ 地狱。
  5. 跑起来!在浏览器里看到我们的第一个页面。

4.2 第一步:让 Node.js 在你的电脑里住下来

现代前端开发,已经离不开 Node.js 了。它为我们提供了两样至关重要的东西:

  1. npm(Node 包管理器):一个能够安装、卸载、管理无数第三方代码包(比如 React、Vite)的工具。没有它,我们得自己从各个网站下载代码包,然后手动管理它们之间的依赖关系。
  2. 一个JavaScript运行时:它让我们可以直接在终端里执行JS程序,而不是只能在浏览器中。Vite 的构建脚本就是用它来跑的。

如何安装?

我不会在这里贴出一大段安装命令,因为软件的下载链接和安装步骤是会变的。我教你一个通用方法,这个方法能让你学会安装任何工具:

  1. 打开你最熟悉的搜索引擎
  2. 输入关键词Node.js download
  3. 找官网:在搜索结果里,找到网址是 nodejs.org 的那个结果,点进去。“记住,找官网是解决这类问题的第一步”。
  4. 下载长期支持版(LTS):官网首页通常有两个大按钮,一个写着最新版,一个写着LTS。选LTS,它最稳定,问题最少。
  5. 安装:下载完成后,双击安装包,一路点“Next”就行。安装程序会自动帮你把环境变量配置好。

验证是否成功

安装完成后,我们需要验证一下。打开你的命令行终端。怎么打开?Windows 上按 Win + R,输入 cmd 回车;Mac 上按 Command + 空格,输入 Terminal 回车。

然后分别输入以下两条命令,如果能正常显示版本号,就说明成功了:

node -v
# 应显示类似 v18.x.x 或 v24.x.x 的版本号
npm -v
# 应显示类似 9.x.x 或 11.x.x 的版本号

npm 会随着 Node.js 一起安装,所以你不需要额外操作。现在,我们的工具箱里已经有了第一个,也是最基础的工具。

4.3 第二步:让 Vite 在三秒钟内,为我们生成一个项目

接下来,我们需要一个工具来帮我们把项目的“骨架”搭起来。这个骨架包括:基本的目录结构、一个能启动的开发服务器、以及一系列默认的配置文件。这个工具,就是 Vite

你可能会问:“老师,我看网上很多项目都是手动创建文件夹和文件的,为什么我们要用工具?”

问得好。手动创建当然可以,但那就像明明有电钻,你却非要用螺丝刀去钻墙。Vite 这类脚手架工具能帮你做好所有默认的、枯燥的配置工作,让你把精力集中在写代码上,而不是折腾配置文件。善用工具,是专业素养的一部分。

在终端里,我们使用 npm create 命令来调用 Vite 的脚手架:

# 进入你想放项目的目录,比如桌面
cd ~/Desktop

# 用 Vite 创建一个新项目
npm create vite@latest the118-pTable -- --template react-ts

上面这行命令很长,我们把它拆开看看是什么意思:

  • npm create:这是 npm 提供的创建项目的命令。
  • vite@latest:告诉 npm,我要用的创建工具是最新版的 Vite。
  • the118-pTable:这是我们的项目名称,也是文件夹的名字。
  • --:一个分隔符,后面的参数是传给 Vite 的,而不是传给 npm 的。
  • --template react-ts:告诉 Vite,我要用 React + TypeScript 的模板。

当你按下回车,Vite 会快速生成一个文件夹,里面装满了文件。然后,它会提示你进行下一步操作。跟着它做就行:

cd the118-pTable    # 进入项目目录
npm install         # 安装所有依赖包
npm run dev         # 启动开发服务器

最后一个命令执行后,你会看到终端出现一行类似这样的信息:

  VITE v8.x.x  ready in 300 ms

  ➜  Local:   http://localhost:5173/

按住 Ctrl(Mac上是 Command)点击那个链接,或者手动在浏览器里输入 http://localhost:5173/。如果一切顺利,你会看到一个旋转的 React 图标,和一行 “Vite + React” 的文字。

恭喜!你的项目已经跑起来了!

我们现在有了一个可以工作的“毛坯房”。虽然它还很简陋,只有四面墙和一个屋顶,但它是完全属于你的。从这一刻起,你可以用 npm run dev 来启动它,打开浏览器边写代码边看效果。这就是所谓的热更新——你改了代码,网页自动刷新,不需要手动按F5。

4.4 第三步:给我们的代码系上“安全带”——配置 TypeScript

我们的项目模板已经自带了 TypeScript,但我们需要确保它“严格”到让我们受不了。

打开项目根目录下的 tsconfig.app.json 文件(这是 Vite 为了区分应用代码和构建工具代码,而拆分的配置文件)。你会看到一堆配置选项。别怕,我们只需要关注几个关键的:

{
  "compilerOptions": {
    // ... 其他选项
    "strict": true,  // 开启所有严格检查
    "noUnusedLocals": true,  // 定义了但没用的变量,报错
    "noUnusedParameters": true,  // 定义了但没用的函数参数,报错
    "noFallthroughCasesInSwitch": true, // 防止 switch 漏写 break
  }
}

"strict": true 是一个“全家桶开关”,它会开启 TypeScript 所有严格的类型检查。这意味着,你不能再把数字当字符串用了,你不能忘记给变量指定类型,你不能随便写一个不存在的属性。

为什么我们要对自己这么狠?

用TypeScript就像开车系安全带。在低速的时候,你感觉它有点勒,不舒服。但当意外来临时,它是唯一能救你的东西。在我们的代码量从100行涨到1000行、10000行时,TypeScript会在我们每一次粗心大意时,给我们亮起红灯,拒绝编译。这些被它拦截下来的错误,就是我们省下来的、未来调试的时间。

4.5 第四步:告别 ../../../ 地狱——配置路径别名

你有没有看到过这样的代码?

import { foo } from '../../../utils/foo';

那些 ../ 就像羊屎蛋子,一路走一路拉,恶心又难缠。当你的文件结构变深,你会发现你总是在数“我到底在第几层?”。

幸好,我们可以用 路径别名(Path Alias) 来一劳永逸地解决这个问题。

我们的目标是什么?就是把那个又长又臭的相对路径,变成一个清爽、绝对不会搞混的绝对路径,比如:

// 原本的样子
import { foo } from '../../../utils/foo';

// 我们想要的样子
import { foo } from '@/utils/foo';

这里的 @ 就是我们定义的别名,它永远指向项目的 src 目录。这样,无论你当前的文件在哪个深度的子目录里,@/ 都指向同一个根,你再也不用去数那些烦人的 ../ 了。

要实现这个魔法,我们需要在两个地方进行配置,让它们都认识 @ 这个符号。

1. 告诉 TypeScript 编译器:@ 就是 src/

打开 tsconfig.app.json,找到 "compilerOptions",在里面添加 "paths" 配置:

{
  "compilerOptions": {
    // ... 其他配置
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}

这句配置的意思是:“嘿,TypeScript,以后你看到以 @/ 开头的路径,就把它当作相对路径 ./src/ 来处理。”这样,在你写代码时,TypeScript 就能正确地找到文件,不会给你报错了。

2. 告诉 Vite 构建器:@ 就是 src/

TypeScript 只是负责在写代码阶段不报错。但真正把代码打包成能在浏览器里运行的文件的,是 Vite。我们还需要告诉 Vite 这个别名规则,否则它会不认识 @/ 而报错。

打开项目根目录下的 vite.config.ts 文件,把它修改成这样:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import path from 'path';  // 引入 Node.js 的路径处理模块

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      // 将 @ 映射到 src 目录
      '@': path.resolve(__dirname, './src'),
    }
  }
})

这里 path.resolve(__dirname, './src') 会动态地计算出你电脑上 src 文件夹的绝对路径,然后把它赋值给 @。现在,Vite 也认识 @/ 了。

测试一下

为了确保配置生效,我们来做一个小实验。在 src 目录下新建一个文件夹 utils,在里面新建一个文件 test.ts,写一行代码:

// src/utils/test.ts
export const hello = 'Hello, THE118!';

然后,打开 src/App.tsx,在顶部用我们的新别名引入它:

import { hello } from '@/utils/test';
console.log(hello);

保存文件。如果浏览器控制台打印出了 Hello, THE118!,而且编辑器没有任何报错,那就说明我们大功告成了!


本章我们完成了从零到一的开发环境搭建。现在,我们拥有了一个用 Vite 启动的、有 TypeScript 严格检查的、路径清爽的前端项目。它就静静地躺在你的电脑里,等待着我们去赋予它生命。

工欲善其事,必先利其器。工具有了,下一步,我们要学习如何像一个专业的工匠一样,保持我们的工作台(代码)整洁、规范。

从下一章开始,我们将引入一系列约束工具:Git 的代码历史管理、ESLint 的代码风格检查、Commitlint 的提交信息规范。它们会让你感觉有点“被束缚”,但请相信我,正是这些束缚,才能让你在未来的复杂项目中,不至于被自己的代码沼泽所淹没。

第五章:约束即自由 · 代码规范与 Git 工作流

5.1 一个关于“存档”的故事

你一定玩过那种可以“存档”和“读档”的游戏吧?

你在打一个很难的boss之前,小心翼翼地存了个档。然后你冲进去,很快就被boss一巴掌拍死了。没关系,读档,再来一次。你换了一种打法,这次坚持了五分钟,又死了。再读档。这个过程中,你可能会尝试几十种不同的策略,犯下无数个错误,但你知道,屏幕里那个角色的“人生”是绝对安全的。因为你有一个存档。

现在想象一下,如果这个游戏没有存档功能,会怎么样?

你只能一次通关。中途任何一个小失误——不小心掉进陷阱、选错了一条路、打boss时手滑了一下——都意味着你要从第一关开始重新来过。你会玩得心惊胆战,不敢尝试任何新东西,因为你根本输不起。这游戏你大概玩十分钟就想砸键盘了。

你知道吗,我们写代码,本质上就是在玩一个没有存档功能的游戏。

你花了三天时间,把一段代码改来改去,终于让它跑起来了。一切都很完美。第四天,你突然灵光一闪,想出了一个更巧妙的实现方法。你兴奋地开始修改,改着改着,程序崩了。你想回退到昨天的状态,但你发现——你完全记不起来昨天到底改了什么,哪些文件被动了,哪一行是关键。就像那个没有存档的游戏,你一不小心掉进陷阱,就再也回不去了。

这就是为什么,我们需要 Git。

我们这一章要做的事,就是给我们的项目加一套“存档系统”。不仅如此,我们还要给它加一套“自动化检查系统”,保证我们每一次存档都是有意义、有条理、经得起时间考验的。

这套系统的核心组件有四个:

  • Git:那个“存档”和“读档”的工具。
  • ESLint:你的代码拼写和语法检查器。
  • Commitlint:你的存档说明书写规范。
  • Husky:你的自动化守门员。

别看名字花里胡哨,拆开了,一个比一个简单。

5.2 Git:你的时光机,你的存档点

我们首先来搞定 Git。

Git 是什么?一句话,它是一个版本控制工具。但“版本控制”这个词太大了,太抽象了。我们换个说法:Git 就是给代码拍照的工具

你每完成一小段功能,觉得“嗯,现在这个状态是好的,可以记下来”,你就用 Git 给它拍一张“快照”。以后,你随时可以翻阅这些快照,也可以随时回到某一张快照的状态。

这个功能听起来简单,但它彻底改变了我们写代码的方式。

以前我们怎么管理版本?

项目.js
项目_改了一下.js
项目_又改了一下.js
项目_最终版.js
项目_最终版不改了.js
项目_最终版不改了又改了一下.js
...

现在呢?只有一个文件,但它的所有“历史版本”都被 Git 默默记录着。

我们来试试。

首先,确保你的终端在 the118-pTable 项目目录里,然后输入:

git init

看到 Initialized empty Git repository 了吗?这意味着,Git 已经接管了这个目录。它会在里面创建一个叫 .git 的隐藏文件夹,这个文件夹就是它存储所有“快照”的地方。从现在起,这个目录就是一个受 Git 管理的仓库了。

但 Git 有点强迫症。它会默认跟踪这个目录里的每一个文件。可有些文件我们根本不想让它管。比如 node_modules,那个文件夹里有成千上万个我们从来没手动改过的第三方文件,拍它们的快照既浪费空间又浪费时间。

所以我们需要给它写一个“忽略清单”——.gitignore 文件。

在项目根目录创建这个文件,写上:

node_modules
dist

现在,我们来完成第一次存档。存档在 Git 里分两步,这是一个你要牢牢记住的动作组合:

  1. git add .:把当前目录(.代表当前目录)里所有文件,添加到“预备拍照区”。
  2. git commit -m "这里写你做了什么":正式拍下快照,并附上一句说明。
git add .
git commit -m "chore: 项目初始化,搭建基础环境"

成了。你的代码现在有了第一个“存档点”。

以后,你每次想“好了,这个状态值得记住”,就做一遍 add + commit。久而久之,你就有了一本完整的“代码日记”,可以随时翻阅,随时“读档”。

5.3 ESLint:在你写代码的时候就帮你找茬

存档的问题解决了。但我们的代码本身,也可能藏着一些不易察觉的问题。

来看这段代码:

var count = 10;
if (count == "10") {
    console.log("数字是十");
}

这段代码能跑吗?能跑。输出 "数字是十",符合预期。

但它“干净”吗?不,它有几个不干净的地方。

  • var 是 JavaScript 早期的变量声明方式,它的作用域规则很古怪,容易导致意料之外的问题。现代JavaScript都推荐用 let 或 const
  • == 在比较两个值的时候,会尝试进行“类型转换”。这意味着它拿数字 10 和字符串 "10" 比较时,会认为它们是相等的。大多数情况下这并非你想要的,你应该用 === 来做严格比较。
  • 语句结尾缺少分号。JavaScript 有自动补分号的功能,但这个功能有时候反而会“帮倒忙”。

这些问题放在几十行代码里,你一眼就能看出来。但当你的项目膨胀到几百个文件、几千行代码时,你再指望肉眼去检查每一个细节,根本不现实。

这就是 ESLint 要做的事。

ESLint 是一个代码静态分析工具。“静态”的意思是,它不需要运行你的代码,只需要把你的源代码当作文本读一遍,就能找出里面不符合规范的写法。

它就像一个不知疲倦的校对员,在你按下保存键的瞬间,就把那些不规范的、有风险的地方给你标出来。

配置 ESLint

我们的 Vite 模板已经帮我们安装好了 ESLint 的依赖,所以我们可以直接用。

在终端运行:

npx eslint src/

你可能会看到一些警告。别担心,这说明它在干活。它会告诉你:第几行、第几个字符、出了什么问题、违反了哪条规则。

你可以修改 eslint.config.js 来定制规则。比如,如果你在开发中需要 console.log 来调试,可以加上:

rules: {
    'no-console': 'off', // 允许 console
}

有了 ESLint,你的代码就有了最低限度的质量保障。它就像一个保护网,帮你拦下那些因为粗心大意而产生的低级错误。

5.4 Commitlint + Husky:给你的存档加一道质量检查

还记得我们的 git commit -m "存档说明" 吗?那个 -m 后面的引号里,是要写一段话来说明你这次做了什么。

但你有没有见过这样的提交记录?

fix
update
改了改
终于能跑了

一周之后你再回来看,你完全不知道这些“存档”分别对应着什么。这就失去了存档的意义。

我们需要的,是每一次提交都能写清楚:你是做了什么事,是加了新功能,还是修了bug,还是只是改了一下文档?

这就是 Commitlint 出场的时候了。

Commitlint 会强制要求你的提交信息,遵循一套固定的格式。这套格式叫 约定式提交(Conventional Commits),它的基本结构是这样的:

<类型>: <简短描述>

类型必须是下面这些词之一:

类型含义
feat新增了一个功能
fix修了一个 bug
docs只改了文档
style调整了代码格式,不影响运行逻辑
refactor重构了代码,没加新功能也没修 bug
perf做了性能优化
test添加或修改了测试
chore杂务,比如改了配置文件

所以,一个好的提交信息应该是这样的:

git commit -m "feat: 添加元素详情弹出面板"
git commit -m "fix: 修正球体布局中元素计数偏差"

格式统一了,别人(包括三个月后的你自己)一看提交记录,就能清晰地知道这个项目是怎么一步步走到今天的。

但问题来了:我们怎么保证自己每次都会遵守这个格式呢?毕竟人总是会偷懒的,深夜写代码的时候,手一滑就写了一个 "fix" 上去。

这时候,我们需要一个“守门员”——Husky

Husky 能在 Git 执行特定操作之前,自动触发我们预先写好的脚本。它的名字来源于哈士奇,那种精力旺盛、拉着你到处跑的雪橇犬。

我们想要的效果是:每次执行 git commit 的时候,先让 Commitlint 检查一下我写的说明合不合格。合格,放行;不合格,打回去重写。

来,我们把它配好。

  1. 安装依赖
  npm install -D @commitlint/cli @commitlint/config-conventional husky
  1. 初始化 Husky
  npx husky init
这条命令会创建一个 `.husky` 文件夹,专门存放各种“守门员脚本”。
  1. 添加 commit-msg 钩子

    echo "npx --no -- commitlint --edit \$1" > .husky/commit-msg
    

就这么简单。现在,你每次 git commit,Commitlint 都会跳出来检查。写得不对?直接拒绝提交。你只能乖乖按照规范重写。

一开始你可能会觉得麻烦,但相信我,习惯成自然。这套规范会保护你的项目历史永远清晰可读。

5.5 让工具再顺手一点

你可能在想:“这个约定式提交的类型好多,我怕记不住。”

没关系。我们的 package.json 里已经配置了一个叫 cz-git 的小工具。它的作用,就是把写提交信息的过程变成一个终端里的交互式菜单

你不需要手动敲 git commit -m "feat: ..." 那么长一串。只需要输入 git cz(或我们配置好的命令),终端会出现一个友好的界面,让你用上下方向键选择“这是什么类型的提交”,然后填写简短描述。一切自动完成,格式永远正确。

好的工具,不会让你觉得被束缚,而是让你感觉“就应该这样用”。

5.6 约束的哲学:围栏不是为了关住你

我们现在回过头来看这一章做的事情。

我们给项目加上了四道约束:

  • Git 管着我们的版本历史
  • ESLint 管着我们的代码风格
  • Commitlint 管着我们的提交信息
  • Husky 在关键时刻执行这些检查

这是不是太多了?我们是不是被绑住了?

我想和你分享一个感受。

在我自己的第一个大型项目中,我没有用任何约束。Git 提交信息随手写,代码风格随心所欲。一开始感觉很自由,像在草原上策马奔腾。但项目做到第三个月的时候,我开始害怕。我不敢改那些“看起来好像不太对”的旧代码,因为我不记得当初为什么那样写;我不敢删掉那些“好像没用”的变量,因为我怕它其实在某个角落被用到了;我甚至不敢看两个月前的提交记录,因为那些记录毫无意义。

那不是自由。那是被自己的混乱所困住。

后来我开始用 Git、用 ESLint、用 Commitlint。一开始确实不适应,感觉处处被管着。但过了一段时间,我意识到,这些约束不是牢笼,而是围栏

它们就像悬崖边上那一道坚固的围栏。当你在晴天白日下慢慢散步时,会觉得它有点碍事。但当你在暴风雨中狂奔时,这道围栏就是唯一能保护你、让你不至于坠入深渊的东西。

所以,约束不是目的,自由才是。 约束只是通往真正自由的那条路。

从下一章开始,我们将正式进入这个项目最令人兴奋的部分——我们不会再用配置文件和各种工具了,我们要开始写真正的核心代码。

我们将从零开始,亲手构建一个数学矩阵引擎。正是它,将赋予我们那118个元素在三维空间中自由驰骋的能力。

准备好了吗?让我们进入那个充满数学之美的新世界。