如何学懂一门技术?
回答由 AI 基于 batype 的笔记生成,不代表本人实时观点。
我一般是先搞懂原理,再动手验证,最后整理成笔记。
具体来说我会这样学:
-
先看问题本质
先问自己:它解决什么问题、为什么会有这个设计。 -
再看官方文档或权威资料
先把概念框架搭起来,不急着记细节。 -
直接写小例子验证
能跑起来、能复现,理解会快很多。 -
记录成自己的话
我更偏向把知识写成“为什么”和“什么时候用”,而不是只抄 API。 -
过一段时间回头看
真正记住的是反复用过、反复解释过的内容。
如果你愿意,我也可以直接按我学 React / TypeScript / 浏览器原理的方式,给你拆一套更具体的学习路线。
我一般按“能不能帮我快速建立正确模型”来挑,不按“讲得热不热闹”来挑。
我会优先看这几类:
-
官方文档 / RFC / 源码 / 原作者文章
这是第一手资料,最接近事实。 -
能讲清原理的长文或教程
我看重的是它有没有把“为什么”讲透,而不只是“怎么用”。 -
有完整例子、能自己验证的资料
能跑、能复现、能对照结果的,我更信。 -
观点稳定、更新及时的资料
技术变化快,过时内容会直接带偏。
我会避开这几类:
- 只堆结论、不讲过程的
- 纯标题党、碎片化但没上下文的
- 只教“技巧”,不解释边界和代价的
- 和当前版本明显不一致的旧文章
我的筛选顺序通常是:
- 先官方
- 再源码 / 规范
- 再高质量博客
- 最后才是视频、短内容
如果你想,我可以顺手把我选学习资料的“检查清单”列成一版,平时直接照着筛。
可以,我会用这份清单筛资料:
先看可信度
- 作者是否真正做过相关项目,或参与过该技术
- 是否引用官方文档、RFC、规范、源码
- 结论能否被其他独立资料交叉验证
- 发布时间和适用版本是否明确
再看内容质量
- 是否解释了“为什么”,而不只罗列 API
- 是否交代使用场景、限制和代价
- 示例是否完整、可运行、可复现
- 是否区分事实、经验和个人观点
- 是否覆盖失败情况与边界条件
最后看学习效率
- 内容结构是否从问题出发
- 能否帮我建立整体模型
- 是否包含最小示例,而不是一上来就搭大项目
- 信息密度是否合适,有没有大量重复和铺垫
- 学完后能否用自己的话解释,并独立实现一遍
快速排雷
出现这些情况,我通常会换资料:
- 标题很绝对,如“彻底掌握”“一篇精通”
- 只给结论,不提供依据
- 代码无法运行,或省略关键上下文
- 用旧版本解释新版本行为
- 把某种工程选择说成唯一答案
- 大量复制其他文章,却没有验证和修正
实际使用时,不需要给每篇资料做完整评审。我通常先用官方文档确定事实,再找一篇讲解建立理解,最后写一个最小 Demo 验证。三者对不上时,以当前版本的规范、源码和实际运行结果为准。
我主要靠“版本、时间、运行结果”三件事分辨,不能只看文章写得像不像真的。
1. 先确认资料对应的版本
重点找这些信息:
- 发布时间和最后更新时间
package.json、CDN 地址里的版本号- 安装命令是否锁定版本
- 文中使用的 Node.js、浏览器或框架版本
- API 属于稳定版、实验版还是已废弃
没有版本信息的教程,我只拿来理解思路,不直接照抄。
2. 对照当前官方文档
我会重点查:
- Migration Guide
- Changelog / Release Notes
- Deprecated 标记
- Breaking Changes
- RFC 或升级指南
尤其是跨大版本,例如 React 17 → 18、Webpack 4 → 5,这时默认存在不兼容,不会假设旧代码还能直接用。
3. 识别旧写法的典型信号
例如 React 资料里出现下面这些内容,我会立即检查版本背景:
ReactDOM.render(<App />, document.getElementById('root'))
React 18 通常改成:
import { createRoot } from 'react-dom/client'
createRoot(document.getElementById('root')).render(<App />)
其他常见信号包括:
- API 已被标记为 deprecated
- 配置字段在当前文档里搜不到
- 示例依赖很老,却没有锁版本
- 使用已经停止维护的插件
- 报错信息与教程完全不同
- 教程依赖某个旧版默认行为
4. 建最小项目实际验证
我不会直接把旧教程塞进业务项目,而是先建一个最小 Demo:
npm create vite@latest version-check -- --template react-ts
cd version-check
npm install
npm run dev
然后逐段加入教程代码,观察:
- 安装时有没有 deprecated warning
- TypeScript 是否报错
- 开发环境和生产构建是否都通过
- 控制台有没有警告
- 行为是否与文章描述一致
5. 检查依赖的真实状态
常用命令:
npm view react version
npm view react versions
npm outdated
npm ls
还会看 npm 发布时间、GitHub Release、维护频率和 issue。文章发布日期新,不代表里面的依赖新;有些文章只是转载或重新发布。
6. 区分“过时”和“错误”
旧资料不一定没价值。我会分三层看:
- 原理仍然成立:可以继续学,例如 Event Loop、Fiber 的设计动机。
- API 已变化:保留理解,代码按新文档重写。
- 底层模型已变化:整篇需要重新评估,不能只替换 API。
我的最终判断标准很简单:当前官方资料负责定版本边界,最小 Demo 负责验证实际行为,旧文章只负责提供理解路径。
我会用“查版本 → 对文档 → 跑最小 Demo → 验生产构建”四步快速验真,通常十几分钟就能判断教程值不值得继续看。
1. 先找版本锚点
从教程里找:
package.json- 安装命令中的版本号
- API、配置字段和 import 路径
- Node.js、框架、构建工具版本
- 文章发布时间与最后更新时间
如果完全没有版本信息,我只参考思路,不直接复制代码。
2. 搜当前官方文档
直接搜索教程里的关键 API:
site:react.dev API名称
site:vite.dev 配置项名称
site:typescriptlang.org API名称
重点检查:
- 当前文档是否仍存在
- 是否标记
deprecated - import 路径是否改变
- 默认行为是否改变
- Migration Guide 是否提到它
搜不到不一定代表错误,也可能是内部 API、旧 API,或者教程本身写错了。
3. 建最小 Demo
不要先放进真实项目。用当前版本单独验证:
npm create vite@latest verify-demo -- --template react-ts
cd verify-demo
npm install
npm run dev
只复制教程中最核心的代码,不复制整套工程。这样报错时更容易判断是 API 过期,还是业务代码干扰。
4. 同时验证“旧版”和“当前版”
如果教程注明了依赖版本,我会先复现原环境:
npm install react@18.2.0 react-dom@18.2.0
然后再升级测试:
npm outdated
npm install react@latest react-dom@latest
判断结果可以分成三种:
- 旧版也跑不通:教程本身可能缺步骤或写错了。
- 旧版能跑,当前版不能跑:教程当时正确,但已经过时。
- 两个版本都能跑:继续检查行为和生产构建,不能只看“没有报错”。
5. 检查警告和构建结果
至少跑这几项:
npm run dev
npm run build
npm ls
npm outdated
重点看:
- deprecated warning
- TypeScript 类型错误
- peer dependency 冲突
- 浏览器控制台警告
- 开发环境正常但生产构建失败
- 页面结果是否真的符合教程描述
6. 最后做一次行为断言
“页面能打开”不等于教程正确。我会给核心行为加一个最小测试或明确输出:
const result = tutorialFunction(input)
console.assert(
result === expected,
`expected ${expected}, got ${result}`
)
我的快速判断标准是:能锁定原版本复现、能从官方变更记录解释差异、能在最小 Demo 中得到预期行为。缺任何一项,我都不会直接把旧教程的代码带进项目。
可以。我建议不要把 React、TypeScript、浏览器原理拆成三门孤立课程,而是按“先会用 → 追调用链 → 做最小实现 → 回到项目验证”推进。主线做一个 React + TypeScript 项目,遇到问题再下钻浏览器和框架原理。
第一阶段:补齐 JavaScript 与浏览器执行基础
目标不是刷语法,而是能解释代码为什么按这个顺序运行。
学习内容
- 执行上下文、调用栈
- 作用域、闭包、
this - Promise、
async/await - Event Loop
- DOM 事件传播
- HTTP 请求与浏览器渲染的基本流程
必做实验
console.log('start')
setTimeout(() => {
console.log('timeout')
}, 0)
Promise.resolve()
.then(() => console.log('promise 1'))
.then(() => console.log('promise 2'))
console.log('end')
不要只背输出顺序,要能解释:
- 同步代码为什么先执行
- Promise 为什么先于
setTimeout - microtask 在哪个阶段清空
- 每一步调用栈和任务队列里有什么
阶段产出
自己画一张图,完整说明:
JavaScript 代码
→ Call Stack
→ Web APIs
→ Task / Microtask Queue
→ Event Loop
如果画不出来,说明还只是记住了结论。
第二阶段:TypeScript 从工程使用开始
TypeScript 不要一上来就钻类型体操。先解决“如何为真实数据建模”。
学习顺序
- 基础类型和类型推断
interface与type- 联合类型、交叉类型
- 类型收窄
- 泛型
keyof、typeof、索引访问类型- 映射类型、条件类型
infer- 常用工具类型的手写实现
先做一个数据模型
type Status = 'idle' | 'loading' | 'success' | 'error'
type User = {
id: number
name: string
}
type UserState =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User[] }
| { status: 'error'; error: Error }
function renderState(state: UserState) {
switch (state.status) {
case 'idle':
return '等待加载'
case 'loading':
return '加载中'
case 'success':
return state.data.map(user => user.name).join(',')
case 'error':
return state.error.message
}
}
这个例子比到处写 as 更能体现 TypeScript 的价值:让非法状态更难表达。
再手写工具类型
按这个顺序:
type MyPick<T, K extends keyof T> = {
[P in K]: T[P]
}
type MyReadonly<T> = {
readonly [P in keyof T]: T[P]
}
type MyOmit<T, K extends keyof any> = MyPick<
T,
Exclude<keyof T, K>
>
type MyReturnType<T extends (...args: any[]) => any> =
T extends (...args: any[]) => infer R ? R : never
type DeepReadonly<T> =
T extends (...args: any[]) => any
? T
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T
每写一个都回答三个问题:
- 输入类型是什么?
- 遍历或匹配发生在哪里?
- 最终生成了什么类型?
阶段产出
为一个请求模块完整建模,禁止使用裸 any:
type ApiResponse<T> =
| { ok: true; data: T }
| { ok: false; message: string }
async function request<T>(url: string): Promise<ApiResponse<T>> {
try {
const response = await fetch(url)
if (!response.ok) {
return {
ok: false,
message: `HTTP ${response.status}`,
}
}
return {
ok: true,
data: await response.json() as T,
}
} catch (error) {
return {
ok: false,
message: error instanceof Error ? error.message : 'Unknown error',
}
}
}
同时要清楚:泛型不会自动验证运行时返回的数据。TypeScript 类型检查不能替代 runtime validation。
第三阶段:先把 React 当成 UI 工具使用
这一阶段先建立组件化思维,不急着读 Fiber。
学习内容
- JSX 与组件
- Props
- State
- 事件处理
- 条件渲染和列表渲染
useStateuseEffectuseRef- 自定义 Hook
- Context 与状态提升
项目建议
做一个任务管理器,至少包含:
- 新增、修改、删除任务
- 状态筛选
- 搜索
- 本地持久化
- 请求 loading、error、success 状态
- 自定义 Hook
- React Router
- 基础测试
类型模型可以这样开始:
type Todo = {
id: string
title: string
completed: boolean
createdAt: number
}
type TodoAction =
| { type: 'add'; payload: Todo }
| { type: 'toggle'; payload: { id: string } }
| { type: 'remove'; payload: { id: string } }
这能把 React 状态管理和 TypeScript 联合类型连接起来。
useEffect 的学习重点
不要把它理解成“组件生命周期替代品”。先理解它用于和外部系统同步:
import { useEffect, useState } from 'react'
function OnlineStatus() {
const [online, setOnline] = useState(navigator.onLine)
useEffect(() => {
const handleOnline = () => setOnline(true)
const handleOffline = () => setOnline(false)
window.addEventListener('online', handleOnline)
window.addEventListener('offline', handleOffline)
return () => {
window.removeEventListener('online', handleOnline)
window.removeEventListener('offline', handleOffline)
}
}, [])
return <span>{online ? '在线' : '离线'}</span>
}
每个 Effect 都问:
- 在和哪个外部系统同步?
- 依赖为什么是这些?
- 是否需要 cleanup?
- 这段逻辑能不能直接在事件处理函数里完成?
第四阶段:下钻 React 原理
会写基本项目后,再研究 React 为什么这样工作。
学习顺序
1. JSX 到页面
先能解释完整链路:
JSX
→ 编译后的元素对象
→ Fiber 节点
→ render/reconciliation
→ commit
→ DOM 更新
→ 浏览器重新渲染
可以直接观察 JSX 编译结果:
const element = <button className="primary">Save</button>
它本质上会转成创建 React Element 的调用,而不是直接创建 DOM。
2. reconciliation
重点理解:
- 为什么同层比较
type变化为什么会重建key的真正作用- 为什么不能随便用数组索引作为
key - State 为什么和组件在树中的位置相关
做一个实验:列表重排时分别使用稳定 ID 和数组索引,观察输入框状态是否错位。
3. Fiber
不要先背字段。先理解旧递归更新的问题:
- 同步递归工作难以中断
- 大任务可能长期占用主线程
- Fiber 把工作拆成节点
- render 阶段可以组织和调度工作
- commit 阶段执行实际 DOM 变更
然后再看常见字段之间的关系:
child
sibling
return
alternate
flags
4. Hooks
重点研究:
- Hooks 为什么依赖调用顺序
- 为什么不能放在条件语句里
- State 更新为什么可能是异步表现
- 闭包为什么会导致 stale state
- 函数组件每次渲染意味着什么
5. 合成事件
把 React 事件和浏览器事件连接起来:
- 捕获与冒泡
- 事件委托
- SyntheticEvent
- React 事件处理与原生监听器的关系
- 更新批处理发生在什么上下文中
阶段产出
自己实现一个简化版 React,至少包含:
createElement
→ 构建 Fiber
→ 可中断的 work loop
→ 创建 DOM
→ commit
→ useState
目的不是造框架,而是把抽象压缩成能运行的代码。
第五阶段:系统学习浏览器导航与渲染
这部分要和项目中的真实现象连接,而不是背面试题。
主线一:输入 URL 到页面展示
按顺序学习:
URL 解析
→ 缓存检查
→ DNS
→ TCP
→ TLS
→ HTTP
→ 服务端响应
→ HTML 解析
→ 资源加载
→ DOM / CSSOM
→ Render Tree
→ Layout
→ Paint
→ Composite
每一层都追问:
- 输入是什么?
- 输出是什么?
- 哪些步骤会阻塞后续过程?
- 浏览器在哪些地方能够复用缓存?
- 出错时能从 DevTools 哪里看出来?
主线二:渲染与性能
重点区分:
- Style calculation
- Layout
- Paint
- Composite
- 回流与重绘
- 主线程与合成线程
- 长任务
- requestAnimationFrame
做一个可观察实验:
const box = document.querySelector('.box')
for (let i = 0; i < 1000; i++) {
box.style.width = `${box.offsetWidth + 1}px`
}
这里在循环中交替读取布局信息和修改样式,可能触发强制同步布局。再改成一次读取、集中计算、一次写入,对比 Performance 面板。
主线三:网络与缓存
建立这套模型:
强缓存:
Cache-Control / Expires
协商缓存:
ETag / If-None-Match
Last-Modified / If-Modified-Since
不要只背状态码,要在 DevTools Network 中验证:
- 请求是否真的发出
- 状态显示是
200、304还是 memory/disk cache - response header 和 request header 如何对应
- reload、hard reload 的结果有什么不同
第六阶段:把三条线合并
最后做一个小型但完整的 React + TypeScript 项目,例如文章列表或后台管理页。
技术要求
- React + TypeScript
- 请求状态使用可辨识联合类型
- 自定义 Hook 封装请求或本地存储
- 路由级懒加载
- 错误边界
- 基础单元测试
- 构建产物分析
- 缓存策略说明
- Performance 与 Network 面板验证
需要回答的问题
项目做完后,我会要求自己解释:
- JSX 如何变成 DOM?
- 一次
setState之后发生了什么? key如何影响节点复用?- Effect 为什么执行,为什么清理?
- TypeScript 在哪里消除了非法状态?
- 哪些类型只存在于编译期?
- 点击按钮后,事件经过了哪些阶段?
- 请求经历了哪些网络步骤?
- 页面为什么会发生 Layout 或 Paint?
- 首屏慢,应该先测什么,而不是先优化什么?
如果这些只能背答案,项目价值还没完全转化成理解。
一个可执行的 12 周安排
| 周数 | 主线 | 产出 |
|---|---|---|
| 1 | JavaScript 执行模型、Event Loop | 任务执行顺序实验 |
| 2 | DOM 事件、HTTP、渲染基础 | URL 导航流程图 |
| 3 | TypeScript 基础与类型收窄 | 请求数据模型 |
| 4 | 泛型、映射类型、条件类型 | 手写常用工具类型 |
| 5 | React 组件、Props、State | Todo 基础功能 |
| 6 | Effect、Ref、自定义 Hook | 持久化与请求功能 |
| 7 | 路由、状态组织、测试 | 完整小项目 |
| 8 | JSX、React Element、reconciliation | JSX 转换实验 |
| 9 | Fiber、render、commit | 简化 Fiber 实现 |
| 10 | Hooks、合成事件 | 简化 useState |
| 11 | 浏览器导航、缓存、渲染 | DevTools 验证记录 |
| 12 | 性能优化与综合复盘 | 项目分析报告 |
我的验收标准
每个知识点都过四关:
- 能使用:能独立写出代码。
- 能解释:能说清输入、过程和输出。
- 能验证:能通过 Demo、DevTools 或测试证明。
- 能判断边界:知道它何时不适用,以及有什么代价。
路线不必严格按天执行,但顺序别反:先建立可运行的经验,再研究抽象和源码。只看源码不做项目容易失去上下文;只做项目不研究原理,又容易停留在 API 层。