DrillLab
八股题库Question bank

105 道问答题,一道一卡105 questions, one card each

默认只显示问题 —— 先自己在心里答一遍,再展开对答案。答不上来就标「不会」,下次抽认卡会先抽它。Only the question shows by default. Answer it in your head first, then open the answer. Mark the ones you miss; the flashcard round puts those first.

0 / 105道自评过self-assessed
0Got it0模糊Shaky0不会No idea105还没做Not seen
正在读这台浏览器里的标记…Reading your marks from this browser…
标记不是打分,是给下一轮排队Marks are a queue, not a score

标「不会」的题会排到抽认卡最前面,标「会」的进低频池排最后。所以别客气 —— 觉得答得磕磕巴巴就标「模糊」。准备好了就去抽认卡“No idea” jumps to the front of the next round; “got it” drops to the back. So be honest — if it came out shaky, mark it shaky. Then go run a flashcard round.

题目Questions

筛出 105 道 · 第 5 / 9 页。105 of 105 questions · page 5 / 9.
JavaScriptJavaScript#312

什么是 npm

What is npm

看答案Show answer

一句话:Node 的包管理器 + 全球最大的包仓库。 它负责装依赖、锁版本、跑脚本。

三件核心事:

  • 装依赖—— 读 package.jsondependencies, 递归下载到 node_modules
  • 锁版本——package-lock.json记下每一个包的确切版本和哈希, 保证队友和 CI 装出来的一模一样。它必须提交到版本库。
  • 跑脚本——npm run dev, 而且会把node_modules/.bin加到 PATH,所以能直接写vitest 而不用写全路径。

必答的两个区分:

  • dependencies vs devDependencies—— 前者是运行时需要的 (React),后者只在开发和构建时需要 (TypeScript、测试框架、打包工具)。 生产安装可以用npm ci --omit=dev 跳过后者。
  • npm install vs npm ci——install 会在需要时更新 lock 文件ci 严格按 lock 装, 对不上就直接报错。CI 里应该用 ci

会追问:^1.2.3~1.2.3 什么区别?」——^ 允许小版本和补丁升级 (<2.0.0),~ 只允许补丁 (<1.3.0)。正是因为 ^ 的存在, lock 文件才必不可少—— 否则不同时间装出来的版本会不同。

In one line: Node’s package manager, plus the largest package registry in the world. It installs dependencies, pins versions and runs scripts.

Three core jobs:

  • Install dependencies — read dependencies out of package.json and download the whole tree into node_modules.
  • Pin versionspackage-lock.json records the exact version and hash of every package, so a teammate and CI install precisely what you did. It has to be committed.
  • Run scriptsnpm run dev. It also puts node_modules/.bin on the PATH, which is why you can write vitest instead of a full path.

Two distinctions you must be able to make:

  • dependencies vs devDependencies — the first is what you need at runtime (React), the second only while developing and building (TypeScript, the test runner, the bundler). A production install can skip the second with npm ci --omit=dev.
  • npm install vs npm ci install will update the lock file when it has to; ci installs strictly from the lock and errors out if the two disagree. CI should use ci.

Follow-up: “What is the difference between ^1.2.3 and ~1.2.3?” — ^ allows minor and patch upgrades (<2.0.0), ~ allows patches only (<1.3.0). It is precisely because ^ exists that the lock file is indispensable — otherwise installing on two different days gives you two different trees.

JavaScriptJavaScript#283

Webpack 是怎么工作的

How does Webpack work

看答案Show answer

一句话:从入口出发递归分析所有 import, 建出一张依赖图, 路上用 loader 把各种文件转成 JS, 最后按规则打成若干个 bundle。

四个核心概念(必答):

  • entry—— 从哪开始找依赖。
  • output—— 打到哪里、叫什么名 (带 [contenthash] 做缓存)。
  • loader——把非 JS 转成 JSbabel-loader 转 JSX 和新语法、css-loader 让 CSS 能被importloader 是从右到左(从下到上)执行的, 这个细节常问。
  • plugin—— 在构建生命周期的各个钩子上干活, 能力比 loader 大得多 (生成 HTML、抽 CSS、压缩、分析体积)。

为什么需要打包(这才是问题的本质):浏览器早期不支持模块、 不认识 JSX 和 TS、 请求数多会慢; 打包解决的是模块化、转译、 合并压缩、以及 tree shaking

会追问:「Vite 为什么快?」—— 这题现在很常问。 开发时 Vite 不打包, 直接用浏览器原生 ESM 按需提供模块, 所以启动是常数时间, 不随项目变大而变慢; 依赖预构建用 esbuild(Go 写的,快一个量级); 生产才用 Rollup 打包。
Webpack 是「先全量打包再服务」, Vite 是「先服务再按需转换」—— 这一句就说到了根本区别。

In one line: start at the entry, walk every import recursively and build a dependency graph, push files through loaders to turn them into JS along the way, and emit a handful of bundles according to your rules.

Four core concepts you have to name:

  • entry — where the dependency walk starts.
  • output — where the bundles land and what they are called (with [contenthash] for caching).
  • loaderturns non-JS into JS. babel-loader handles JSX and new syntax, css-loader makes CSS importable. Loaders run right to left (bottom to top) — that detail gets asked a lot.
  • plugin — hooks into the build lifecycle and can do far more than a loader (generate HTML, extract CSS, minify, analyse bundle size).

Why bundling exists at all — that is the real question: early browsers had no modules, they do not understand JSX or TS, and a lot of requests is slow. Bundling buys you modularity, transpilation, merging and minifying, and tree shaking.

Follow-up: “Why is Vite fast?” — asked constantly now. In development Vite does not bundle; it serves modules on demand over the browser’s native ESM, so startup is constant time and does not degrade as the project grows. Dependency pre-bundling runs on esbuild (written in Go, an order of magnitude faster), and only production goes through Rollup.
Webpack bundles everything and then serves it; Vite serves first and transforms on demand — that single sentence gets at the fundamental difference.

JavaScriptJavaScript#387

fetch 和 axios 的区别

What is the difference between making server requests via fetch and axios?

看答案Show answer

一句话:fetch浏览器内置的, 很基础;axios 是第三方库, 把常用的事都替你做了。

fetchaxios
依赖,浏览器/Node 18+ 内置要装(约 13 KB)
4xx / 5xx不 reject, 要自己查 res.ok自动抛错
JSON要手动 await res.json()自动解析到 data
超时要自己配 AbortControllertimeout 一个选项
拦截器没有,要自己包一层内置(统一加 token、统一处理 401)
上传进度很麻烦支持
Node 里可用18+ 才有一直可以

「404 不 reject」是这题的核心考点, 也是真实 bug 的来源:不查res.ok 就会把错误页当数据渲染。从 axios 转过来的人最容易漏这一条, 因为 axios 会自己抛。

怎么选:简单项目、在意包体积、 或者只发几个请求 → fetch包一个自己的小 wrapper; 需要拦截器 / 统一错误处理 / 上传进度,或者要兼容老 Node → axios
更常见的现实答案:用TanStack Query / SWR管缓存和请求状态, 底下用哪个都行 —— 因为fetchaxios都不管缓存、去重、重试。 能这么答说明你想过分层。

In one line: fetch is built into the browser and very bare-bones; axios is a third-party library that does the routine work for you.

fetchaxios
DependencyNone, built into browsers and Node 18+Must be installed (about 13 KB)
4xx / 5xxDoes not reject; you check res.ok yourselfThrows for you
JSONYou call await res.json() by handParsed into data already
TimeoutWire up an AbortController yourselfOne timeout option
InterceptorsNone; wrap it yourselfBuilt in (attach a token everywhere, handle 401 in one place)
Upload progressPainfulSupported
Available in NodeOnly from 18Always has been

“404 does not reject” is the heart of this question, and a real source of bugs: skip the res.ok check and you render an error page as if it were data. People coming over from axios miss this one most often, because axios throws on their behalf.

How to choose: a small project, a tight bundle budget, or only a handful of requests → fetch with your own small wrapper. Interceptors, one place for error handling, upload progress, or an old Node to support → axios.
The more realistic answer: reach for TanStack Query or SWR to manage caching and request state, and whichever one sits underneath hardly matters — because neither fetch nor axios handles caching, deduplication or retries. Answering that way shows you have thought about the layers.

JavaScript两者的核心差别与自制 wrapperThe main difference, and a wrapper of your own示意Illustrative
1// fetch:404 也是「成功」,必须自己判
2const res = await fetch(url);
3if (!res.ok) throw new Error(`HTTP ${res.status}`); // ← 漏了这行就会出 bug
4const data = await res.json();
5
6// axios:非 2xx 自己抛,data 已经解析好
7const { data } = await axios.get(url);
8
9// 自己给 fetch 包一层,就能补上大部分差距
10async function request(url, opts = {}) {
11 const c = new AbortController();
12 const t = setTimeout(() => c.abort(), opts.timeout ?? 10000);
13 try {
14 const res = await fetch(url, { ...opts, signal: c.signal });
15 if (!res.ok) throw new Error(`HTTP ${res.status}`);
16 return await res.json();
17 } finally {
18 clearTimeout(t);
19 }
20}
1// fetch: a 404 also counts as "success", so you have to check it yourself
2const res = await fetch(url);
3if (!res.ok) throw new Error(`HTTP ${res.status}`); // ← leave this line out and you get a bug
4const data = await res.json();
5
6// axios: it throws on anything that is not 2xx, and data is already parsed
7const { data } = await axios.get(url);
8
9// Wrap fetch yourself and you close most of the gap
10async function request(url, opts = {}) {
11 const c = new AbortController();
12 const t = setTimeout(() => c.abort(), opts.timeout ?? 10000);
13 try {
14 const res = await fetch(url, { ...opts, signal: c.signal });
15 if (!res.ok) throw new Error(`HTTP ${res.status}`);
16 return await res.json();
17 } finally {
18 clearTimeout(t);
19 }
20}
React 与生态React & ecosystem#321

什么是 SPA

What is a SPA

看答案Show answer

一句话:单页应用 ——只加载一个 HTML, 之后的页面切换由 JS 在前端换内容, 不再向服务器请求整页。

好处:切页面没有白屏刷新、体验接近原生 App、 前后端彻底分离(后端只给 JSON)。

代价(面试重点问这半边):

  • 首屏慢—— 要先下载并执行一大包 JS 才能看到内容。 解法是代码分割(React.lazy,见 #347) 和 SSR。
  • SEO 差—— 爬虫拿到的 HTML 是空的<div id="root"></div>。 解法是 SSR / SSG(Next.js)。
  • 路由要自己管—— 前进后退、深链接、刷新后还在当前页, 都得靠 history API 和服务端的 fallback 配置。「刷新 404」就是漏配了 fallback
  • 内存泄漏风险—— 页面不刷新, 定时器和监听器不会被自动清掉。这就是 useEffect必须写清理函数的现实原因。

会追问:「MPA 什么时候更好?」—— 内容型站点(博客、文档、电商详情页), 重 SEO、首屏优先、交互不复杂的。答得出「看场景」比一味夸 SPA 好。

In one line: a single-page application — one HTML document gets loaded, and from then on JS swaps the content on the client instead of asking the server for a whole new page.

Upsides: no white flash when you change pages, it feels close to a native app, and front end and back end are fully separated (the server only returns JSON).

Costs — this is the half they probe:

  • Slow first paint — you have to download and run a big JS bundle before anything shows up. The fixes are code splitting (React.lazy, see #347) and SSR.
  • Weak SEO — a crawler receives an empty<div id="root"></div>. The fix is SSR / SSG (Next.js).
  • You own the routing — back and forward, deep links, staying on the current page after a refresh, all of it rides on the history API plus a fallback on the server. “404 on refresh” means the fallback is missing.
  • Memory leaks — the page never reloads, so timers and listeners are never cleared for you. That is the practical reason useEffect needs a cleanup function.

Follow-up: “When is an MPA better?” — content sites (blogs, docs, product detail pages) where SEO and first paint matter and the interaction stays simple. “It depends on the case” beats praising SPAs unconditionally.

React 与生态React & ecosystem#320

React 的优势是什么

React advantage

看答案Show answer

一句话:声明式 + 组件化 + 单向数据流—— 你描述「界面应该长什么样」, React 负责把 DOM 变成那样。

  • 声明式—— 你写{items.map(...)}, 不写「找到 ul、创建 li、appendChild」。省掉的是「怎么从状态 A 变到状态 B」这类过程代码, 而这正是 bug 最多的地方。
  • 组件化—— UI 拆成可复用、 可独立测试的单元。
  • 单向数据流—— props 往下、事件往上。 出问题时排查路径是确定的: 数据只可能从一个方向来。
  • 生态—— Router、 状态库、Next.js、React Native (同一套心智模型能写移动端)。

会追问缺点(一定要准备): 只是个视图库,路由和状态都要自己选,选型成本高; 性能优化要手动(memo /useMemo,React 19 之前没有自动记忆化); JSX 和 hooks 规则对新手有门槛;版本迁移的心智负担不小(class → hooks → Server Components)。

In one line: declarative + component-based + one-way data flow — you describe what the UI should look like and React makes the DOM match.

  • Declarative — you write {items.map(...)}, not “find the ul, create an li, appendChild”. What you drop is the step-by-step code for getting from state A to state B, and that is where most bugs live.
  • Components — the UI splits into reusable units you can test on their own.
  • One-way data flow — props go down, events go up. When something breaks, there is exactly one path to trace: the data can only have come from one direction.
  • Ecosystem — Router, state libraries, Next.js, React Native (the same mental model gets you a mobile app).

They will ask for the downsides (have them ready): it is only a view library, so you pick the router and the state layer yourself — the cost of those decisions is real; performance work is manual (memo / useMemo; there was no automatic memoization before React 19); JSX and the hook rules are a hurdle for beginners; and version migrations carry a lot of mental load (class → hooks → Server Components).

React 与生态React & ecosystem#319

React vs Angular

React vs Angular

看答案Show answer

一句话:React 是(只管视图,其他自己选); Angular 是框架(路由、HTTP、表单、依赖注入、 测试全都自带)。

ReactAngular
定位全套框架
语言JS / TS,JSXTypeScript 强制,HTML 模板
数据流单向支持双向绑定(ngModel
DOM 策略虚拟 DOM增量 DOM + 变更检测
学习曲线入门低,但选型多入门陡(DI、RxJS、装饰器),之后规范统一
适合灵活、迭代快、团队愿意自己搭大型企业项目、要求统一规范

怎么答得体面:别踩一捧一。说 「React 给自由也给选型负担, Angular 给约定也给学习成本;小而快的项目和需要长期多人维护的大项目, 答案不一样」。

会追问 Vue—— Vue 在两者之间:有官方路由和状态库 (比 React 统一), 但比 Angular 轻;模板语法上手快。

In one line: React is a library (it handles the view, you choose the rest); Angular is a framework (routing, HTTP, forms, dependency injection and testing all come in the box).

ReactAngular
What it isA libraryA full framework
LanguageJS / TS, JSXTypeScript required, HTML templates
Data flowOne-wayTwo-way binding available (ngModel)
DOM strategyVirtual DOMIncremental DOM + change detection
Learning curveEasy to start, but many choices to makeSteep at first (DI, RxJS, decorators), consistent after
FitsFlexible work, fast iteration, a team happy to assemble its own stackLarge enterprise projects that need one standard

How to answer without picking a fight: do not talk one down to lift the other. Say “React gives you freedom and the burden of choosing; Angular gives you conventions and a learning cost. The answer differs for a small fast project and for a big one many people maintain for years.”

They will ask about Vue — Vue sits between the two: it has an official router and state library (more unified than React) but stays lighter than Angular, and the template syntax is quick to pick up.

React 与生态React & ecosystem#326

什么是 JSX

What is JSX

看答案Show answer

一句话:JavaScript 的语法扩展, 让你在 JS 里写类似 HTML 的结构。浏览器不认识它, 要经过 Babel 编译成普通函数调用。

编译成什么(这是考点): 旧版编译成React.createElement(type, props, ...children)React 17 之后用新的 JSX 转换, 编译成 _jsx(...), 所以不用再手动import React 了。

要说清的几条规则:

  • 必须有单一根节点—— 因为函数只能返回一个值。 不想多套 div 就用Fragment(见 #338)。
  • 属性名用小驼峰——className(因为class 是 JS 关键字)、htmlForonClick
  • {} 里放表达式, 不能放语句 —— 所以条件渲染用三元或&&,不能写 if
  • JSX 默认转义, 所以天然防 XSS; 要插 HTML 得显式写dangerouslySetInnerHTML——名字故意起得难听, 就是让你警觉。

会追问:「JSX 是必须的吗?」—— 不是,你可以手写createElement, 只是没人愿意。JSX 的价值是让 「UI 结构」在代码里长得像结构

In one line: a syntax extension for JavaScript that lets you write HTML-like structure inside JS. The browser does not understand it — Babel compiles it into plain function calls.

What it compiles to (this is the part being tested): old versions produced React.createElement(type, props, ...children); since React 17 the new JSX transform emits _jsx(...), which is why you no longer have to write import React by hand.

The rules you should state clearly:

  • One root node is required — a function can only return one value. If you do not want another div, use a Fragment (see #338).
  • Attribute names are camelCase className (because class is a JS keyword), htmlFor, onClick.
  • {} holds an expression, not a statement — so conditional rendering uses a ternary or &&, never if.
  • JSX escapes by default, so you get XSS protection for free; injecting raw HTML takes an explicit dangerouslySetInnerHTML the name is deliberately ugly so that you stop and think.

Follow-up: “Is JSX mandatory?” — no, you can call createElement yourself, nobody wants to. The value of JSX is that UI structure looks like structure in the code.

JSXJSX 编译成什么示意Illustrative
1// 你写的
2const el = <button className="btn" onClick={handle}>点我</button>;
3
4// Babel 编译后(React 17 之前)
5const el = React.createElement(
6 "button",
7 { className: "btn", onClick: handle },
8 "点我",
9);
10
11// {} 里只能放表达式
12{if (ok) <A />} // ✗ 语法错误
13{ok ? <A /> : null} // ✓
14{ok && <A />} // ✓(注意 0 会被渲染出来,见 #281)
1// What you write
2const el = <button className="btn" onClick={handle}>Click me</button>;
3
4// After Babel compiles it (before React 17)
5const el = React.createElement(
6 "button",
7 { className: "btn", onClick: handle },
8 "Click me",
9);
10
11// Only an expression can go inside {}
12{if (ok) <A />} // ✗ syntax error
13{ok ? <A /> : null} // ✓
14{ok && <A />} // ✓ (careful: 0 does get rendered, see #281)
React 与生态React & ecosystem#330

虚拟 DOM 和 diff 算法

Virtual DOM and diffing algorithm

看答案Show answer

一句话:虚拟 DOM 是用普通 JS 对象描述真实 DOM 的一棵轻量树。 状态变了先在内存里生成新树, 和旧树 diff,算出最小改动,再一次性打到真实 DOM 上

为什么快 —— 说准这两条:

  • 批量—— 十次 state 更新 合并成一次 DOM 操作, 避免十次重排(见 #288)。
  • 最小化—— 只改真正变了的属性和节点, 不重建整棵子树。

但要说出这层真相(加分点):虚拟 DOM 不一定比手写 DOM 快—— 精心手写的原生操作永远更快, 虚拟 DOM 还额外付出了「建树 + diff」的开销。它真正的价值是「在保持声明式写法的同时, 性能仍然够好」—— 是可维护性和性能的折中, 不是性能银弹。

diff 的三条启发式规则(把 O(n³) 降到 O(n) 的关键):

  1. 只比同层,不跨层移动节点。 跨层的话就是删了重建。
  2. 类型不同直接整棵重建——div 换成 span, 子树全部丢弃重做(state 也丢)。
  3. 同层列表用 key 认身份

key 为什么不能用 index—— 这是 React 面试最实用的一条: 在开头插入或删除一项时, 所有元素的 index 都变了, React 会认为「每一项的内容都变了」, 于是大量误更新; 更糟的是非受控输入框的内容会串到别的行, 因为 DOM 节点被复用了。Q1 那道真题里删除笔记的 bug 就是这个。

In one line: the virtual DOM is a lightweight tree of plain JS objects describing the real DOM. When state changes, React builds a new tree in memory, diffs it against the old one, works out the smallest set of changes, and applies them to the real DOM in one go.

Why it is fast — get these two right:

  • Batching — ten state updates collapse into one DOM write, so you avoid ten reflows (see #288).
  • Minimising — only the attributes and nodes that really changed get touched; whole subtrees are not rebuilt.

But say this part too — it is the bonus point: the virtual DOM is not necessarily faster than hand-written DOM code — carefully tuned native operations always win, and the virtual DOM pays extra for building a tree and diffing it. Its real value is that you keep the declarative style and performance is still good enough — it is a trade-off between maintainability and performance. It is not the right choice everywhere.

The three diff heuristics (what turns O(n³) into O(n)):

  1. Compare the same level only; nodes never move across levels. Crossing a level means delete and rebuild.
  2. A different type rebuilds the whole subtree — swap a div for a span and the subtree is thrown away and redone, state included.
  3. Lists on the same level use key for identity.

Why index must not be the key — the most useful thing you can say in a React interview: insert or delete at the front and every index shifts, so React believes the content of every row changed and does a pile of needless updates; worse, text typed into an uncontrolled input ends up on the wrong row, because the DOM node got reused. The delete-a-note bug in the real Q1 question is exactly this.

JSXkey 的选择Choosing the key示意Illustrative
1// 虚拟 DOM 就是普通对象
2{ type: "button", props: { className: "btn", children: "点我" } }
3
4// ✗ index 当 key:在开头插一项,所有 key 都变了
5{todos.map((t, i) => <Row key={i} todo={t} />)}
6
7// ✓ 稳定的业务 id
8{todos.map((t) => <Row key={t.id} todo={t} />)}
1// The virtual DOM is just a plain object
2{ type: "button", props: { className: "btn", children: "Click me" } }
3
4// ✗ index as key: insert one at the front and every key changes
5{todos.map((t, i) => <Row key={i} todo={t} />)}
6
7// ✓ a stable id from the data
8{todos.map((t) => <Row key={t.id} todo={t} />)}
只有「列表永不重排、不增删中间项」时 index 才安全。既然多数列表都会变,直接养成用 id 的习惯。An index is only safe when the list is never reordered and no item is inserted or removed in the middle. Most lists do change, so make using an id your default habit.
React 与生态React & ecosystem#353

什么是 reconciliation

What is reconciliation

看答案Show answer

一句话:reconciliation(协调)是「比较新旧虚拟 DOM 树, 决定要对真实 DOM 做哪些操作」的整个过程。 diff 算法是它的一部分。

diff 和 reconciliation 什么关系?这是本题的考点:diff 是「怎么比」的算法, reconciliation 是「比 + 决定 + 提交」的完整流程。 说它们是一回事不算错,但分得清更好。

React 16 之后的 Fiber 架构把这个过程拆成两个阶段 —— 这是必答的:

  • render 阶段(可中断)—— 构建 Fiber 树、做 diff、 标记要改什么。这个阶段可以被打断和恢复, 所以高优先级的更新(比如用户输入)能插队。
  • commit 阶段(不可中断)—— 把标记好的改动一次性打到真实 DOM 上, 然后跑 useEffect这一段必须同步完成, 否则用户会看到半渲染的界面。

为什么要能中断?因为老架构(Stack Reconciler)是递归的、一旦开始就停不下来, 大列表更新时会阻塞主线程十几毫秒以上, 输入卡顿。Fiber 用链表 + 循环替代递归, 每处理一小块就检查「有没有更急的事」。这就是「并发特性」的基础(见 #344)。

会追问:「StrictMode 为什么渲染两次?」—— 因为 render 阶段可能被中断和重跑, 所以渲染函数必须是纯的; 两次渲染就是帮你把不纯的地方暴露出来(见 #332)。

In one line: reconciliation is the whole process of comparing the new and old virtual DOM trees and deciding which operations to run on the real DOM. The diff algorithm is one part of it.

How do diff and reconciliation relate? That is the point of the question: diff is the algorithm for how to compare; reconciliation is the full compare, decide and commit flow. Calling them the same thing is not wrong, but telling them apart is better.

The Fiber architecture, from React 16 on, splits the process into two phases — answer this every time:

  • Render phase (interruptible) — build the Fiber tree, diff, mark what has to change. This phase can be paused and resumed, which is how a high-priority update such as typing jumps the queue.
  • Commit phase (not interruptible) — apply the marked changes to the real DOM in one pass, then run useEffect. This part must finish synchronously, otherwise users would see a half-rendered screen.

Why does it need to be interruptible? Because the old Stack Reconciler was recursive and could not stop once it started, so a large list update blocked the main thread for tens of milliseconds and typing stuttered. Fiber replaces recursion with a linked list plus a loop, and after each small chunk it checks whether something more urgent came in. This is the foundation of the concurrent features (see #344).

Follow-up: “Why does StrictMode render twice?” — because the render phase can be interrupted and re-run, so the render function has to be pure; the double render is there to expose the impure parts (see #332).

React 与生态React & ecosystem#337

React 项目里 babel 和 webpack 干什么

What do we use babel and web pack for in React applications

看答案Show answer

一句话分工:Babel 负责「翻译」(JSX 和新语法 → 浏览器能懂的 JS);Webpack 负责「打包」(把一堆模块和资源合并成能上线的几个文件)。

  • Babel——@babel/preset-react 转 JSX,@babel/preset-env按目标浏览器把 ES2020+ 降级。它只做语法转换, 新 API(PromiseArray.flat)要靠 polyfill 补。这个区分是加分点。
  • Webpack—— 解析 import 建依赖图、 让 CSS 和图片也能被 import、 tree shaking、代码分割、 开发时提供 dev server 和热更新。

顺序:Webpack 遇到 .jsx时调用 babel-loaderBabel 是 Webpack 流水线上的一个环节

会追问:「现在还用它们吗?」—— 这题答得出现状才显得在跟进: 新项目多用 Vite(开发用原生 ESM + esbuild, 生产用 Rollup),Babel 常被 esbuild / SWC 取代(快一个量级)。Webpack 仍在大量存量项目和 需要复杂定制的场景里。
我们这门课的 React 源项目用的就是 Vite —— 所以node_modules 里根本没有 webpack。

The split in one line: Babel translates (JSX and new syntax → JS the browser understands); Webpack bundles (a pile of modules and assets become the few files you ship).

  • Babel@babel/preset-react handles JSX, @babel/preset-env down-levels ES2020+ for your target browsers. It only transforms syntax; new APIs (Promise, Array.flat) still need a polyfill. Drawing that line scores points.
  • Webpack — reads your imports to build a dependency graph, lets you import CSS and images too, does tree shaking and code splitting, and gives you a dev server with hot reload while you work.

The order: when Webpack hits a .jsx file it calls babel-loader, so Babel is one stage of the Webpack pipeline.

Follow-up: “Are they still used?” — knowing the current state is what shows you keep up: new projects mostly reach for Vite (native ESM plus esbuild in development, Rollup for production), and Babel is often replaced by esbuild or SWC — an order of magnitude faster. Webpack is still everywhere in existing codebases and wherever heavy customisation is needed.
The React source project in this course uses Vite — which is why there is no webpack in its node_modules at all.

React 与生态React & ecosystem#322

函数组件 vs 类组件

Functional components vs Class components

看答案Show answer

一句话:现在一律写函数组件。 类组件只在维护老代码和写错误边界时才用 (错误边界目前还只能用 class)。

类组件函数组件
状态this.state / setStateuseState
副作用生命周期方法useEffect
this要处理绑定没有 this,不存在这问题
逻辑复用HOC / render props(嵌套很深)自定义 hook(平铺)
代码量

为什么官方推函数组件—— 答这三条比列表格更有说服力:

  • 逻辑能按关注点组织, 而不是按生命周期切碎。 类组件里「订阅」和「取消订阅」被迫分在componentDidMountcomponentWillUnmount 两个方法里;useEffect 让它们写在一起
  • 复用逻辑不用套娃。HOC 叠三层就变成「wrapper 地狱」, 自定义 hook 是平的。
  • this 的问题彻底消失。

会追问:「函数组件里怎么拿 shouldComponentUpdate?」——React.memo, 但它默认是浅比较,需要自定义就传第二个参数。

In one line: write function components, always. Class components are for maintaining old code and for error boundaries, which still have to be classes.

Class componentFunction component
Statethis.state / setStateuseState
Side effectsLifecycle methodsuseEffect
thisYou deal with bindingNo this, so no such problem
Reusing logicHOC / render props (deep nesting)Custom hooks (flat)
Amount of codeMoreLess

Why the official line favours function components — these three points land better than the table:

  • Logic groups by concern instead of being sliced up by lifecycle. In a class, subscribing and unsubscribing are forced apart into componentDidMount and componentWillUnmount; useEffect lets them sit together.
  • Reusing logic needs no nesting. Three stacked HOCs turn into wrapper hell; custom hooks stay flat.
  • The this problem disappears completely.

Follow-up: “How do you get shouldComponentUpdate in a function component?” — React.memo, though it compares shallowly by default; pass a second argument when you need your own comparison.

React 与生态React & ecosystem#323

React 的生命周期有哪些

Explain the React component lifecycle and its methods

看答案Show answer

一句话:三个阶段 ——挂载、更新、卸载

  • 挂载constructorgetDerivedStateFromPropsrendercomponentDidMount(DOM 已经有了,发请求、订阅、操作 DOM 都在这
  • 更新getDerivedStateFromPropsshouldComponentUpdate(返回 false 就跳过渲染)→rendergetSnapshotBeforeUpdatecomponentDidUpdate这里改 state 必须加条件, 否则死循环)
  • 卸载componentWillUnmount(清定时器、解绑监听、取消请求)
  • 出错getDerivedStateFromError +componentDidCatch(错误边界,见 #333)

三个被废弃的要知道componentWillMountcomponentWillReceivePropscomponentWillUpdate原因是 Fiber 的 render 阶段可能被中断和重跑, 这几个方法可能被调用多次, 放在里面的副作用会重复执行。能说出这个原因很加分。

会追问:「请求为什么不放componentWillMount?」—— 除了上面的原因, 它在 SSR 时也会执行, 而且并不会更早拿到数据 (请求是异步的,反正要等)。

In one line: three phases — mounting, updating, unmounting.

  • Mounting: constructor getDerivedStateFromPropsrender componentDidMount (the DOM exists now, so fetching, subscribing and DOM work all belong here)
  • Updating: getDerivedStateFromProps shouldComponentUpdate (return false to skip the render) → render getSnapshotBeforeUpdate componentDidUpdate (setting state here needs a condition, or you get an infinite loop)
  • Unmounting: componentWillUnmount (clear timers, detach listeners, cancel requests)
  • On error: getDerivedStateFromError + componentDidCatch (error boundaries, see #333)

Know the three that were deprecated: componentWillMount, componentWillReceiveProps, componentWillUpdate. The reason is that Fiber can interrupt and re-run the render phase, so these could fire more than once and any side effect inside them would run twice. Giving that reason earns real credit.

Follow-up: “Why not fetch in componentWillMount?” — besides the reason above, it also runs during SSR, and it does not get the data any sooner: the request is async, so you wait either way.

这些题从哪来Where these come from

99 道来自面试题库 #269–#387;TypeScript 深度那 6 道是 DrillLab 自出的(senior 补强,卡片上有标注)。答案都是 DrillLab 写的,所以讲解里的代码块一律标「示意」。每道题都能点回它出处的那一节课。99 questions come from the interview bank (#269–#387); the 6 TypeScript deep-dive ones are DrillLab-made (marked on the card). All answers are written by DrillLab, so every code block here is labelled “demo”. Each card links back to the lesson it came from.
八股题库 / Interview drills · DrillLab