ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

React 核心知识体系:虚拟 DOM、组件化与 Hooks 实战指南

React 核心知识体系:虚拟 DOM、组件化与 Hooks 实战指南 分享一个我自己带新人时常说的做法学 React 之前先别急着写组件用十分钟搞清楚它到底是干什么的。很多学生把 JSX、useEffect、Redux 背得滚瓜烂熟但一问“React 和 Vue 的区别”“为什么要有虚拟 DOM”整个人就愣住了。这篇总结整理了我这些年带学生、做项目、被面试官盘问之后沉淀下来的 React 知识体系适合正在入门、准备期末或者面试、以及学了一段时间但知识很散的人。重基础、讲原理、给经验尽量不堆术语。1. React 整体认知先弄明白它解决什么问题1.1 React 是什么从“命令式 DOM”到“声明式 UI”React 是 Meta原 Facebook开源的一个用于构建用户界面的 JavaScript 库。它不是 Vue、Angular 那种大而全的框架它解决的核心问题只有一个当数据变化时如何高效、可预测地更新界面。在 React 出现之前前端写页面是典型的“命令式”拿到数据后手动找到 DOM 节点、修改它的内容或 class、再绑定事件……代码逻辑和数据是混在一起的。比如往一个列表里加一项原生 JS 的套路是 createElement、appendChild、还要考虑清空旧节点避免重复。项目一大这种操作 DOM 的代码就非常难维护你永远不知道哪个函数改过哪个节点。React 换了个思路你只需要声明“UI 长什么样”也就是把数据映射成界面结构剩下的交给框架。这就是“声明式”。我用一个最简单的例子说明这种差异const [list, setList] useState([]); // 当我 setList([...list, 新项]) 时页面上的 ul 会自动多出一行React 内部通过组件化把界面拆成一个个独立单元每个组件只管自己的数据和外观。组件可以复用、组合、嵌套这让大型项目的维护成本大幅下降。学 React 的第一课应该记在心里组件化、声明式、由数据驱动视图这三件事是 React 的立身之本。顺便说一下开发环境React 项目通常跑在 Node.js 环境下用 npm 或 yarn 装依赖、用 Vite 启动开发服务器所以学 React 之前最好先会 Node.js 的基本命令比如npm install、npm run dev。1.2 虚拟 DOM 与 DiffReact 凭什么能“自动更新”React 不会在每次数据变化时直接操作浏览器 DOM因为直接操作 DOM 是很昂贵的重排、重绘、样式计算都很耗性能。React 的做法是在内存里维护一棵虚拟 DOM 树它用一个普通的 JS 对象来描述真实 DOM比如{ type: div, props: { className: box }, children: [...] }。当 state 变化时React 会重新生成一棵新的虚拟 DOM 树然后用 diff 算法把新旧两棵树进行对比找出哪些节点真正变了最后只把这部分最小更新同步到真实 DOM。这个过程叫协调reconciliation。这里要澄清一个很多学生理解错的点虚拟 DOM 并不一定比直接操作 DOM 快。在某些极端场景下手写原生 DOM 操作可能更快。虚拟 DOM 的真正价值有两个一是把 DOM 操作从业务代码里抽象出去开发者不用关心怎么改 DOM只关心数据是什么这是工程效率的提升二是 DOM 的抽象层带来了跨平台能力——同一套 React 代码可以渲染到浏览器也可以渲染到原生移动端React Native或桌面应用。所以面试被问“为什么需要虚拟 DOM”时别只说快要提到跨平台、开发体验、批量更新这几个关键词。1.3 React 与 Vue、Angular 的区别面试第一问怎么答React、Vue、Angular 有什么区别是学生面试几乎必遇的题。我习惯用三个维度回答设计思路、语法风格、生态定位。Angular 是一个完整的框架自带路由、HTTP 客户端、依赖注入、表单校验等一整套东西适合大型企业项目但学习曲线陡峭模板逻辑相对严格。Vue 是渐进式框架模板语法写起来顺手响应式系统自动追踪依赖开发者上手快中文社区活跃中小型项目和内部系统用起来很舒服。React 更“小而专”核心只解决 UI 渲染状态管理、路由、请求方案等都需要你自己组装。好处是灵活、生态极其丰富坏处是选择太多新手容易迷茫。再补充一个更细节的对比面试官很喜欢追问维度ReactVue视图描述JSX本质是 JS 表达式逻辑灵活模板语法 指令接近 HTML学习曲线平缓数据更新机制状态变化后从组件重新渲染配合虚拟 DOM diff基于 Proxy 的响应式系统精确追踪依赖组件写法规格函数组件 Hooks / 类组件选项式 / 组合式 API生态与就业大厂使用多生态庞大TS 结合好国内中小厂使用广上手快我的观点是对初学者React 和 Vue 选一个主攻就行但原理层面的东西是相通的——组件化、数据驱动、生命周期、虚拟 DOM这些概念在哪个框架里都存在。把 React 的底层逻辑学透以后切到 Vue 的成本很低。这两年 AI 应用和 Agent 场景火起来React 作为 UI 层的地位依然稳固因为任何 AI 能力最终都要落到界面交互上而这恰恰是 React 最擅长的领域。2. 组件与 JSXReact 最基础的表达方式2.1 JSX 不是模板那些容易踩的语法坑JSX 是 React 自己发明的语法允许你在 JavaScript 文件里直接写类似 HTML 的标签。很多学生会以为 JSX 就是在 JS 里写 HTML其实它不是模板引擎它最终会被编译成React.createElement调用。正因为 JSX 是 JS 的语法糖所以它有几个看起来像 HTML、但其实是 JS 规则的细节新手经常在这上面翻车class 要写成 className因为 class 在 JS 里是保留字。style 要传对象style{{ color: red, fontSize: 14px }}CSS 属性名用驼峰。花括号里面写表达式{name}、{1 2}、{condition span显示/span}都是合法的但不能直接写 if 语句要用三元表达式或者提前算好变量。注释要写在花括号里{/* 这是注释 */}。列表渲染必须加 keykey 帮助 React 识别哪些元素变化了不要默认用数组 index 当 key。一个典型的学生 Demo 会踩的坑是想给一组数据渲染列表结果忘了加 key控制台警告一堆。其实原理很简单React 用 key 来跟踪每个节点当列表顺序变化或增删时key 能让 React 判断哪些是同一个元素避免把整个列表全部重建。这个知识点面试爱考后面第 7 章会细说。2.2 函数组件与类组件从类组件时代到 Hooks 时代React 早期只有类组件因为必须用 class 才能拥有 state 和生命周期。类组件长这样class Counter extends React.Component { constructor(props) { super(props); this.state { count: 0 }; } componentDidMount() { /* 挂载后 */ } componentDidUpdate(prevProps) { /* 更新后 */ } componentWillUnmount() { /* 卸载前 */ } render() { return ( button onClick{() this.setState({ count: this.state.count 1 })} {this.state.count} /button ); } }类组件的烦恼是this 指向容易出问题必须用箭头函数或 bind、逻辑分散在多个生命周期里、代码复用靠高阶组件和渲染 props容易形成嵌套地狱。所以 React 16.8 推出 Hooks 之后官方推荐优先写函数组件。函数组件接收 props使用 Hooks 管理状态和副作用代码更短、逻辑更内聚。不过面试中还是要懂类组件的生命周期因为老项目里大量存在。记住三大阶段即可挂载constructor 到 render 到 componentDidMount、更新render 到 componentDidUpdate、卸载componentWillUnmount。在 componentDidMount 里发请求、在 componentWillUnmount 里清理定时器这是老 React 开发的经典模式。2.3 Props 与 State数据的两个来源组件的数据只有两个来源父组件传进来的props和自己内部维护的state。Props 是外部如何看这个组件它是只读的子组件不能修改父组件传来的 props这保证了单向数据流。如果你的代码里出现了props.name xx说明设计错了。Props 可以传任何东西基本类型、对象、数组、函数甚至是 JSX 片段children。State 是组件内部自己管理的数据。修改 state 必须用专门的更新函数setState或useState的 setter绝不允许直接改state.count 3这种写法 React 侦测不到界面不会更新。state 的更新是异步且批量合并的在同一个事件处理函数里连续多次 setStateReact 会合并成一次更新去渲染。另一个经典概念叫状态提升lifting state up当两个兄弟组件需要共享同一个数据时正确的做法是把这个数据放到它们共同的父组件里由父组件通过 props 传给两个子组件。这是 React 推崇的自顶向下数据流的核心比刻意引入状态管理库要自然得多。3. Hooks 完全指南现代 React 的核心3.1 useState 与 useEffect最常用的两个钩子Hooks 本质上是一组让函数组件拥有状态和执行副作用的能力。函数组件本来是纯函数的思维Hooks 则给它注入了生命周期能力。useState用来声明状态const [count, setCount] useState(0);注意useState(0)里的 0 是初始值只在首次渲染时生效setCount是更新函数它触发重新渲染。如果你要基于旧值更新最好用函数式写法setCount(prev prev 1)这样即使多个更新排队也不会因为闭包拿到过期值而出错。useEffect用来执行副作用——数据请求、订阅、定时器、手动操作 DOM 都算副作用。它的依赖数组决定了 effect 什么时候执行// 每次渲染都执行不推荐滥用 useEffect(() {}); // 只挂载时执行一次模拟 componentDidMount useEffect(() {}, []); // 依赖变化时执行 useEffect(() {}, [userId]);依赖数组是 useEffect 的灵魂也是新手最容易写错的地方。我见过太多同学在 effect 里用了某个变量却忘记把它写进依赖数组结果拿到的是旧值也见过把依赖数组写满导致每次渲染都触发请求。实时数据页面最常见的写法就是在 useEffect 里建立 WebSocket 或 SSE 连接然后在清理函数里关闭连接这个模式几乎是固定套路面试问实时通信场景时可以直接讲出来。3.2 useEffect 的闭包陷阱老值从哪里来闭包陷阱大概是 React Hooks 里最著名的坑几乎每个做过项目的人都被坑过。先看这段代码function Counter() { const [count, setCount] useState(0); useEffect(() { const timer setInterval(() { console.log(count); // 这里拿到的永远是初始值 0 setCount(count 1); // 一直卡在 1 }, 1000); return () clearInterval(timer); }, []); }问题出在哪依赖数组是空的所以 effect 只在第一次渲染时执行一次。那次执行时闭包捕获的count是初始值 0。之后 count 再变这个 timer 内部的 count 也不会更新永远闭着 0。解决方法有三种按推荐程度排序用函数式更新setCount(c c 1)setter 能拿到最新值不依赖闭包里的 count。把依赖数组写全把count加进依赖但这样会导致每次 count 变化都会重新创建定时器得配合清理函数。用 useRef 保存最新值countRef.current count定时器里读countRef.current。我个人的经验是只要在 effect 里用到状态就先检查依赖数组是否包含它。ESLint 的exhaustive-deps规则强烈建议打开它就是用来治这个毛病的。很多同学觉得这个规则烦人其实它是在救你。3.3 useContext 与 useReducer轻量级全局状态方案跨组件共享状态时不一定马上上 Redux。Context 是 React 内置的依赖注入机制可以在不经过中间组件的情况下把数据传到深层子组件。const ThemeContext createContext(light); function App() { return ( ThemeContext.Provider valuelight Toolbar / /ThemeContext.Provider ); } function Toolbar() { const theme useContext(ThemeContext); return div当前主题{theme}/div; }useReducer是和 Redux 同思想的状态管理 Hook把多个状态更新收敛成一个 reducer 函数通过 dispatch 一个 action 来描述发生了什么由 reducer 决定状态变成什么。适合状态逻辑复杂、多个状态有联动关系的场景。比如购物车有加商品、减商品、清空、改数量等多个操作用 useState 写会堆一大串 setState用 useReducer 则清晰很多function cartReducer(state, action) { switch (action.type) { case ADD: return [...state, action.payload]; case REMOVE: return state.filter(item item.id ! action.payload.id); default: return state; } } const [cart, dispatch] useReducer(cartReducer, []);Context useReducer 组合起来能覆盖中小型应用的全局状态需求用 useReducer 管理数据用 useContext 把 dispatch 和 state 注入到组件树里。这是我做中小型项目时的默认方案比裸 Context 的性能和可维护性都好。3.4 useRef、useMemo、useCallback三个容易混的钩子这三个钩子的功能经常被搞混分开讲useRef有两个用途一是拿到真实 DOM 节点比如聚焦输入框inputRef.current.focus()二是保存一个不会让组件重新渲染的变量。定时器 id、上一次的值、各种可变引用都适合放 useRef。它的.current是可变对象修改它不会触发渲染所以不要用 useRef 去存会影响 UI 的数据。useMemo缓存计算结果。它接收一个返回值的函数和依赖数组只有当依赖变化时才重新计算const expensive useMemo(() filterList(list, keyword), [list, keyword]);适合做昂贵的计算比如大数组过滤、排序、格式化。它和 useEffect 的共同点是都有依赖数组区别是 useMemo 发生在 render 阶段在它里面做有副作用的操作会出问题。useCallback缓存函数引用。它的本质是 useMemo 的一个特例useCallback(fn, deps)等价于useMemo(() fn, deps)。目的是让传给子组件的函数保持引用稳定配合 React.memo 避免子组件无意义的重渲染。一句话总结三者的使用场景useRef 存可变数据useMemo 存计算值useCallback 存函数引用。注意不要为了性能优化而盲目给每个值套 useMemo 和 useCallback滥用反而增加内存和代码复杂度。优化的前提是先确认存在性能问题。4. 组件通信与状态管理多组件协作的套路4.1 四种常见通信场景从就近到全局React 组件通信可以按远近分成几档父子通信父传子用 props子传父用回调函数。这是最基本的形式。父组件把onChange{setValue}传给子组件子组件在合适时机调用onChange(newValue)数据自然流向父组件。兄弟通信通过状态提升把共享数据放到最近的公共父组件。比如两个输入框需要联动校验就把两个值都放在父组件的 state 里。跨层级通信用 Context。比如主题、语言、当前登录用户这种全局信息用 Context 可以绕开中间层直接获取避免“prop drilling”逐层透传造成的繁琐和无谓的重渲染。任意组件通信全局状态库Redux、Zustand或者事件总线。适合多个互不相干的组件共享同一份数据比如购物车、全局通知、登录态。选型原则我总结成一句话优先用 props其次状态提升再考虑 Context最后才上全局状态库。很多学生一上来就装 Redux结果复杂度和维护成本反而成了负担。4.2 Redux 核心思想单一数据源与不可变更新Redux 的核心思想非常简单就三条单一数据源整个应用的状态存在一个 store 里是一个 JavaScript 对象树。state 是只读的无法直接修改 state必须通过 dispatch 一个 action 来描述发生了什么。使用纯函数 reducer 执行修改reducer 接收旧的 state 和 action返回新的 state。因为 reducer 是纯函数它不能直接修改参数、不能有随机数、时间或网络请求等副作用。每个 action 经过 reducer 之后得到一份全新的 state配合不可变数据让调试和回溯变得容易——这也正是 React 依赖引用变化来触发更新的前提。现在主流的 Redux 用法是 Redux ToolkitRTK它简化了很多样板代码。你不需要再手写一堆 switch用createSlice就能同时生成 action 和 reducer。学生阶段至少要知道Redux 适合全局复杂状态不适合所有项目。如果只是几个组件共享一个状态用 Context useReducer 是完全够的不用引入额外依赖。4.3 轻量方案Zustand、Jotai 什么时候用这几年 Zustand 在 React 社区很火因为它 API 极简、不需要 Provider 包裹、可以直接在组件外读写 storeTypeScript 支持也顺手。一个计数器 store 大约十行代码import { create } from zustand; export const useStore create((set) ({ count: 0, increment: () set((state) ({ count: state.count 1 })), }));选择状态管理方案时我一般这样判断只有父子或兄弟通信用 props 或状态提升。少量跨层级数据Context useReducer。全局复杂状态、多模块共享、需要时间旅行调试Redux Toolkit。想要轻量、现代、TS 友好Zustand 或 Jotai。这个问题的本质不是哪个库更好而是你的项目复杂度到哪个程度。面试时这样回答“什么时候用 Redux”会比单纯背概念得分高。5. React 与 TypeScript让代码更靠谱5.1 为什么建议学生从一开始就上 TypeScript现在大厂的 React 项目基本默认 TypeScript面试也大概率会问到 React TS。TypeScript 用类型系统约束变量和函数最大的价值不是消灭 bug而是让代码意图变得明确组件接收哪些 props、每个字段是什么类型看一眼接口定义就懂编辑器还能提供自动补全和重构支持。对学生来说TS 还有一个隐性好处它逼着你把数据结构想清楚。很多人写 React 报错是因为数据形状不明确——接口返回的字段到底是字符串还是数字要不要可选用 TS 把这些定义出来很多潜在 bug 在写代码阶段就暴露了。学习上建议顺序先掌握 TS 基础interface、type、泛型、联合类型再学 React 场景下的常见写法。不要一开始就把 TS 学成“反人类语法”实际项目里常用的知识点其实就那几个。5.2 组件常用类型写法一套可以直接抄的模板这是我在项目里常用的几种类型写法学生可以直接当模板用// 定义 Props 类型 interface ButtonProps { label: string; onClick: () void; variant?: primary | secondary; children?: React.ReactNode; } function Button({ label, onClick, variant primary, children }: ButtonProps) { return ( button className{variant} onClick{onClick} {label} {children} /button ); }// useState 的泛型 const [list, setList] useStateUser[]([]); const [name, setName] useStatestring(); // useRef 拿 DOM const inputRef useRefHTMLInputElement(null); inputRef.current?.focus(); // 事件对象类型 function handleChange(e: React.ChangeEventHTMLInputElement) { console.log(e.target.value); } // reducer 的 action 类型 type Action | { type: INCREASE } | { type: SET_COUNT; payload: number };写熟这份模板后你会发现“报错”从一种玄学变成了可以推理的判断。这里要补充一个细节React.ReactNode表示任何可以被 React 渲染的内容是所有 children 类型的第一选择比any安全得多。6. 性能优化让 React 应用更快6.1 React.memo useCallback阻止无意义的重渲染React 的渲染机制是父组件重渲染时默认所有子组件都会跟着重渲染。这里的重渲染不一定代表 DOM 变了但函数组件内部的代码会重新执行一遍。如果一个子组件渲染成本高或者父组件频繁更新就会造成无谓的性能损耗。React.memo是一个高阶组件它会对 props 做浅比较如果 props 没有变化子组件就跳过重渲染const ExpensiveChild memo(function ExpensiveChild({ data }) { // 只有 data 引用变化时才执行 return div{data.join(, )}/div; });但注意memo 只比较 props 的引用。如果父组件每次渲染都创建一个新数组或新函数传给子组件即使内容一样引用也都变了memo 依然会认为 props 变了。这时候要用useMemo去稳定数组的引用用useCallback去稳定函数的引用。三者是铁三角配合使用才能真正发挥作用。我的建议是不要过早优化。先正常开发遇到明显的卡顿再用 React DevTools 的 Profiler 找出渲染频繁的组件用 memo、useMemo、useCallback 精准优化而不是给所有组件无脑包 memo——过度 memo 同样会消耗比较开销。6.2 长列表渲染虚拟滚动的原理列表渲染是 React 项目最常见的场景也是性能问题的重灾区。渲染一个 1000 条数据的列表可能还感觉不出来但如果是 10 万条就会明显卡顿因为每条数据都是一个真实 DOM 节点浏览器处理不过来。虚拟滚动的原理是只渲染当前视口内可见的那一部分条目比如视口高度能显示 20 条那么不管数据是 1 万还是 10 万DOM 里始终只存在 20 个左右的节点。当用户滚动时动态替换显示的数据项。实际项目可以直接用react-window或react-virtualized这类库几行代码就能接入import { FixedSizeList as List } from react-window; List height{600} itemCount{10000} itemSize{50} width100% {({ index, style }) div style{style}第 {index} 项/div} /List这里要理解itemSize这个参数它告诉虚拟列表每个条目的固定高度是计算滚动位置和可视区间的关键。如果你的列表项高度不固定就要用动态高度的方案复杂度会上升不少。所以产品设计时如果能保持列表项统一高度会给前端省很多事。6.3 代码分割与懒加载首屏加载更快打包工具Vite 或 Webpack默认会把整个应用打到一个 bundle 里首屏要下载全部代码这在项目大起来以后是灾难。代码分割可以把不同路由对应的代码拆成独立 chunk按需加载。React 官方提供的两件套是React.lazySuspenseconst Home React.lazy(() import(./pages/Home)); const About React.lazy(() import(./pages/About)); function App() { return ( Suspense fallback{div页面加载中.../div} Routes Route path/ element{Home /} / Route path/about element{About /} / /Routes /Suspense ); }用户访问首页时只下载 Home 的代码访问关于页时再下载 About。首屏体积小了加载自然变快。面试被问首屏优化时除了代码分割还常提到这些路由懒加载、图片懒加载、压缩静态资源、减少 DOM 层级、避免大包依赖。把这些按网络层面、构建层面、运行时层面分好类回答会很有条理。7. 生态与面试高频考点7.1 React Router 与 Next.js路由与工程化选型React 本身不管路由通常用 React Router 或 Next.js。React Router 是客户端路由库核心 API 就几个BrowserRouter、Routes、Route、Link、useNavigate、useParams。路由参数变化时组件可以读取useParams拿到动态段比如/user/:id里的 id用useNavigate做编程式跳转// 路由定义 Route path/user/:id element{UserDetail /} / // 组件里取参数 function UserDetail() { const { id } useParams(); const navigate useNavigate(); return button onClick{() navigate(/)}返回首页/button; }Next.js是目前 React 生态里最流行的全栈框架。它把 React 组件提升到框架层面自带文件路由、服务端渲染SSR、静态站点生成SSG、API 路由。面试问 React 和 Next.js 的区别时核心答法是React 是库解决 UI 渲染Next.js 是基于 React 的框架解决整个应用的工程问题特别是 SEO 和首屏速度。另外不少同学会问到 React Native。它是 React 在移动端的技术方案用同一套声明式写法渲染原生组件。启动白屏常见原因是加载 JS bundle 耗时、首帧数据未就绪优化方向通常是拆分 bundle、开启 Hermes 引擎、把初始化数据提前注入以及做好启动屏到首帧的过渡。如果只想应付面试知道这些方向就够了。React 里做图表也是常见需求。轻量场景直接用 ECharts 的封装或者react-chartjs-2如果对性能要求很高比如做 K 线图可以看看 uPlot它的渲染机制极其轻量数据量大的时候优势非常明显。7.2 合成事件与 Fiber面试加分项合成事件React 并没有直接使用浏览器原生事件而是做了一层包装。所有事件都绑定到根容器上通过事件委托来分发。这样做的好处是跨浏览器行为统一比如onChange在不同浏览器上的表现被抹平了还能按需分配内存不至于每个组件都绑一堆原生监听器。如果你在事件处理函数里需要访问原生事件对象可以通过e.nativeEvent拿到。Fiber是 React 16 引入的底层调度架构。在老的 Stack 架构里更新过程是同步递归的一旦开始就不能中断遇到复杂的组件树会阻塞主线程导致页面卡顿。Fiber 把渲染工作拆成一个个小任务单元赋予不同优先级可以中断、可以恢复、可以丢弃低优先级任务让 React 能够在浏览器帧之间见缝插针地执行更新从而保证交互流畅。学生了解到这里就足够应付大部分面试题再深入就是源码级别的 diff 和 schedule 细节了。7.3 高频面试题与手写题速览最后整理一批我在学生面试中经常见的高频题附上核心答题思路为什么调用 setState 后不能立刻拿到新值因为更新是异步的React 会把多次 setState 合并到一次渲染中。如果确实要基于新值做操作用 useEffect 监听该 state或者在事件处理函数里用函数式更新。为什么 Hooks 不能在循环、条件语句中调用Hooks 内部依赖调用顺序来建立状态和 effect 的对应关系每次渲染必须以完全相同的顺序调用 Hooks。条件调用会打乱顺序导致状态错乱。本质上是 Hooks 用了链表或数组下标这种实现方式不是 Map 数据结构。key 的作用是什么key 是列表元素的唯一标识帮助 React 在 diff 时判断节点是新增还是移动。不要用 index 当 key否则在列表头部插入或删除时React 会误判所有节点的身份造成状态错乱或重复渲染。受控组件与非受控组件受控组件的值由 state 控制每个输入都会触发 setState再回填到输入框表单数据与 UI 完全同步非受控组件通过 ref 直接读取 DOM 值。受控组件更可预测是 React 推荐的默认方式。手写一个简易 useState用闭包和数组下标模拟。每次渲染会重新执行组件函数Hooks 从全局存储里读取上一次的状态再返回当前值和新 setter。能写出这个思路说明你真的理解了 Hooks 的运行机制。let hookIndex 0; const stateList []; function useState(initial) { const currentIndex hookIndex; stateList[currentIndex] stateList[currentIndex] ?? initial; function setState(newValue) { stateList[currentIndex] newValue; render(); } hookIndex; return [stateList[currentIndex], setState]; }这段代码不能直接用于生产但它是理解 Hooks 工作原理很好的起点为什么顺序不能乱因为每个 hook 在 stateList 里的位置是按调用顺序分配的为什么状态能跨渲染保留因为 stateList 是组件渲染之外的全局存储。最后再分享一个我自己的体会React 的知识体系看起来庞大——Hooks、路由、状态管理、TS、性能优化、Fiber——但核心永远是一条主线数据驱动视图。面试官问的花样再多考的都是你对这条主线的理解有多深。学生阶段最好的学习方式不是背 API而是批量做小实验自己写一个计数器、做一个 todoList、再模拟请求拉数据渲染列表把这些基础动作做熟再往上叠加状态管理和工程化。如果你现在正处在“看了很多教程但还是记不住”的阶段我的建议是把每个知识点压缩成自己的话写一遍配合一个最小可运行的 Demo 跑一遍。写不出来就说明没懂跑不出来就说明没会。React 没有捷径但它也不是玄学把每个概念背后的为什么想清楚积累到一定量后你会突然发现整个体系都通了。
返回列表