尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
React核心知识点实战复盘:组件设计、状态管理与渲染优化
做前端这些年React 是我用得最多、也踩坑最多的一套框架。老实说网上的教程和文档写得很全但零零散散读完总有种我知道了可一到项目里还是慌的感觉。这篇笔记就是把我自己工作里反复用到的 React 核心知识点——从组件设计、状态管理到 Hooks 原理、渲染优化——重新梳理了一遍不追求面面俱到只挑那些真正影响开发效率和项目质量的关键点。如果你正在学 React 或者准备整理自己的知识体系这篇文章适合你把它当作一份检查清单看完能对着自己的项目想一想哪些地方已经吃透了哪些地方还在模糊地带。我一开始学的也是教科书那套先搭环境、再写组件、然后用 state 管理数据。但真正跑过几个项目之后才发现React 的难点从来不是语法而是思考方式。组件边界怎么划、状态放在哪一层、为什么渲染次数总是超出预期、一个 Context 改动为什么会拖累整棵组件树——这些问题不搞清楚写得越多越容易埋雷。这篇笔记会尽量把这些为什么也讲透配合实际场景和排查经验希望能让你少走一些我之前走过的弯路。1. 从搭建到运行React 项目的基础逻辑1.1 脚手架选择与项目结构规划我最初用的是官方的 CRA 脚手架后来新项目基本都在 Vite 上起步了。并不是说 CRA 不好而是对于一个以前端为重的单页应用Vite 的开发冷启动速度确实快了一个量级。特别是组件多起来以后CRA 的 Webpack 热更新经常要等好几秒Vite 基于原生 ES Module 的方案几乎是秒开。说回项目结构。很多新手会习惯把所有组件平铺在一个 components 目录里前期组件少还好一旦页面多起来找文件就成了一场灾难。我现在的划分方式是把src下面按功能拆成pages、components、hooks、utils、services和store这几块。pages放路由级别的页面组件components只放可以跨页面复用的通用组件页面私有的小块组件我直接放在对应页面目录内部不强制提出来。这个规则不是死板的但它能强制我去思考这个组件到底是通用的还是只属于某一个页面的想不清楚就往外面放最后往往是一堆看似通用但没人复用的死代码。另外一个容易被忽略的点是别名配置。Vite 里可以通过resolve.alias把指到src目录这样组件里引入路径就不需要一堆../../../。这个配置虽然小但对长期维护的帮助非常大能减少重构目录时牵连全局的路径修改。1.2 JSX 的本质与编译逻辑JSX 看起来像 HTML但它不是模板引擎而是React.createElement的语法糖。我在很长一段时间内把 JSX 当成可以在 JS 里写 HTML直到排查一次动态渲染问题才真正转变认知。看一个最简单的例子const element div classNamecontainerHello/div这段代码经过编译之后等价于const element React.createElement( div, { className: container }, Hello )所以 JSX 上的属性名才必须遵循驼峰规则比如className而不是class。它本质上是在构造一个描述 UI 的 JavaScript 对象这个对象再交给 React 去生成真实 DOM。理解这一点之后很多问题就通透了为什么Component /大写开头而div小写开头因为小写会被当作原生标签处理大写才会被识别为组件并执行函数调用。为什么 JSX 里能写{condition span显示/span}因为它本来就是 JavaScript 表达式求值的返回值会被当作 createElement 的参数处理。千万不要把 JSX 和模板字符串混为一谈。差异的核心在于模板是静态分析和字符串拼接而 JSX 是纯粹的表达式组合。这意味着你可以在 JSX 中直接调用函数、使用展开运算符、甚至内联执行复杂的逻辑不会受到模板语法各种限制。提示如果看到编译产物会发现 Babel 会把React.createElement换成jsx()运行时函数这就是自动运行时带来的性能优化。理解这个机制有助于理解为什么新的 JSX 转换不再强制引入 React 到每个文件顶部。2. 组件设计思维与数据流2.1 函数组件与类组件的取舍现在写新代码我基本只用函数组件但这不代表类组件是废品。事实上很多老项目里类组件的存量代码依然在跑你至少得看得懂this.setState和生命周期函数。两者的核心差异在于心智模型。类组件通过生命周期来组织逻辑componentDidMount、componentDidUpdate、componentWillUnmount是三个主要节点。问题在于一段业务逻辑常常被拆到不同生命周期里比如一个数据请求要在挂载时发起、在收到新 props 时重新发送、在卸载时清理你就得动三个方法。函数组件配合 Hooks 之后useEffect可以用依赖数组把这三件事表达在一处逻辑内聚性强了很多。另外要注意的是类组件里this指向的问题。事件处理函数如果不做 bind 或者不写成箭头函数this就会在回调执行时指向 undefined严格模式下。这个问题在函数组件中完全消失了因为闭包天然捕获了当前渲染作用域里的变量。类组件的 setState 是合并更新函数组件的 useState 是替换更新。比如类组件里多个 setState 可以在一个更新周期里合并而函数组件修改对象类型的 state 时必须手动展开旧字段。这个差异在从类组件重构到函数组件时最容易踩坑。2.2 Props 与 State 的正确分工State 是组件的内部状态Props 是父组件传入的外部数据。听起来很简单但实际项目里很多人把两者的边界用错。我自己犯过的错误是把从服务端请求来的数据存在某个组件的 state 里然后通过 props 一层层往下传导致组件层级改动时数据流断裂。判断一个数据该放哪的常见方法单一数据源原则——任何需要被多个子组件共享的数据都应该提升到最近的公共父组件中管理。如果共享层级跨度太大比如页面级数据被三层组件之下的按钮修改这时就该考虑用 Context 或者全局状态管理了而不是继续层层透传 props。Props 的设计也讲究。一个组件的 props 越多它的复用性和可维护性就越差。我看到有些项目里一个表单组件能收二十来个 props改起来极其痛苦。解决思路是给 props 分组核心配置项、数据项、事件回调、样式扩展。能用默认值的地方都给默认值能用组合代替继承的地方就组合。比如一个下拉框组件与其设计一个type属性来切换样式变体不如直接拆成两个基础组件再组合复用改动面能小很多。2.3 setState 的批处理机制初次接触 React 的人常会遇到一个问题在事件处理函数里连续调用两次 setState第二次拿到的是第一次更新之前的值。这是因为 React 的 setState 是异步批处理的它不会立即更新 state而是把多次更新合并到一次渲染里。// 这样写最后 count 只加了 1 setCount(count 1) setCount(count 1)要基于旧值累加必须使用函数式更新setCount(prev prev 1) setCount(prev prev 1)React 18 之后不仅事件处理器里是批处理的setTimeout、Promise 回调、原生事件里的更新也会自动批处理。这一改动大幅减少了不必要的重新渲染但也带来了一个隐蔽问题如果你在某个异步回调里通过 ref 读取最新状态读到的可能是更新前的值。所以设计原则是组件渲染阶段不要依赖可变 ref 来存储业务状态渲染结果应当完全由 state 和 props 决定。3. Hooks 核心机制深入3.1 useState 和 useEffect 的依赖管理Hooks 是函数组件的心脏而useEffect是最容易写错的一个 Hook。我见过太多人把所有副作用堆在一个巨大的 useEffect 里然后不断加依赖项最后自己也搞不清什么时候会执行。正确做法是把副作用“拆小”。每个 useEffect 只负责一件独立的事情依赖数组里只放真正参与逻辑的状态。举个例子一个需要在 id 变化时重新拉取用户信息的场景正确的依赖是[id]而不是把整个查询条件对象都塞进去。如果副作用依赖多个状态可以考虑把它们合并成一个派生值再用这个派生值作为依赖。还要注意useEffect的返回清理函数。监听事件、创建定时器、建立 WebSocket 连接这类的副作用必须在清理函数中解除。我踩过一次很经典的坑在组件里写了window.addEventListener(resize, handler)但忘了返回清理函数导致组件反复挂载卸载后内存里堆了好几十个监听器页面肉眼可见地变卡。另一个官方文档一直在强调但很多人忽略的规则Hooks 必须放在函数组件的顶层调用不能放在条件语句、循环或者嵌套函数里。这个规则背后的原因是 React 需要依靠调用顺序来把 Hook 与对应的 state 关联起来。一旦顺序不固定React 就不知道该把哪个 state 返回给哪次调用会出现非常难查的顺序错乱问题。3.2 useMemo 与 useCallback 的缓存逻辑先给结论不要滥用useMemo和useCallback。它们本身有开销首次调用要运行函数并缓存结果依赖变化时还要做比较。绝大多数情况下一个普通计算直接写在组件里就可以了JavaScript 的计算能力比我们想象中强得多。正确的使用时机是计算结果本身比较昂贵比如大量数据排序、过滤、格式化或者计算结果被作为依赖传给另一个 memo 化的子组件时。const filteredList useMemo(() { return list.filter(item item.isActive) }, [list])关键在于如果子组件被React.memo包裹父组件传入的 props 必须是稳定引用。每次渲染都新建一个对象或数组作为 propsReact.memo的比较结果永远是不相等缓存就失效了。这时候useMemo和useCallback不是优化工具而是保证引用稳定的必要手段。我个人的经验是先写不带缓存的版本遇到性能瓶颈用 Profiler 分析确认是某个计算或子组件渲染导致的再针对性地加缓存。提前优化往往是浪费时间还容易因为依赖遗漏引入 bug。3.3 useRef 的两个用途useRef最常见的用途是获取 DOM 节点但它还有一个容易被忽略的能力在组件多次渲染之间保存一个可变值并且这个值的变化不会触发重新渲染。const hasLoadedRef useRef(false) useEffect(() { if (hasLoadedRef.current) return hasLoadedRef.current true fetchData() }, [])这个模式很实用尤其是在效果只希望执行一次的场景里。React 18 的 StrictMode 下开发环境 effect 会执行两次用 ref 可以防止重复请求之类的副作用。但请注意不要用 ref 去充当读取最新 state的角色。它绕过了渲染流程时间长了会导致界面数据和真实数据不一致非常难排查。经典的反模式是state 更新后从 ref 里读值结果读到旧值代码看着没毛病但结果就是不对。3.4 useReducer 与状态管理useState适合管理简单的独立状态但当一个 state 的更新逻辑包含多个步骤、多个分支时用useReducer会更清晰。它把哪些操作会导致状态变化集中到一个 reducer 函数里让状态变化有迹可循。function reducer(state, action) { switch (action.type) { case increment: return { count: state.count 1 } case decrement: return { count: state.count - 1 } default: return state } }我不建议在简单场景里强行引入 useReducer它会让代码变长。判断标准是如果 state 的更新需要相互关联的多个字段配合比如表单里有用户名、密码、记住我等字段且存在联动校验useReducer 能把复杂度收拢起来。另一个适合的场景是 state 更新逻辑比较重、打算单独做单元测试的时候reducer 是一个纯函数测试起来非常方便。React 18 之后 useReducer 在并发渲染中的行为更加稳定dispatch 的调用不会因为渲染被打断而丢失更新这也让它更适合作为复杂交互的状态容器。3.5 自定义 Hook把逻辑抽出来自定义 Hook 本质就是一个以use开头的普通函数内部可以调用任何官方 Hook。它是组件之间复用有状态逻辑的最佳方式。我以前写过一段很长的表单页面包含校验、提交、重置三套逻辑逻辑散落在组件里代码又长又难维护。后来抽了一个useForm自定义 Hook把字段值、错误信息、绑定事件全部收进去页面组件只剩下一堆 JSX 和调用。这里最关键的不是代码变短了而是组件只承担展示和分发事件的职责改动字段规则时只需要动 Hook不会影响 UI 结构。自定义 Hook 的约定有两个要注意第一函数名必须以use开头否则 React 的 lint 插件无法识别它内部调用了 Hooks 就会报错第二Hook 内部如果有共享状态每次调用都会重新创建一份多个组件各自独立。如果你真希望多个组件共享同一份状态那应该把状态放在公共父组件或 Context 中而不是在自定义 Hook 内部模拟全局变量。4. 渲染机制与性能优化4.1 React.memo 的适用边界React.memo是一个高阶组件它让函数组件在 props 不变时跳过重新渲染。听上去很诱人但它的比较逻辑是浅比较如果 props 里包含了对象、数组、函数除非这些引用的地址没变否则 memo 依然会失效。这解释了为什么 useCallback 和 useMemo 常常要和 memo 配合使用。实际项目里我只有在两类组件上使用 memo一类是渲染成本很高的列表项组件另一类是被频繁作为 children 传入的稳定内容组件。对于普通组件React.memo的收益并不明显反而增加了比较的开销。判断一个组件是否需要 memo核心是看它是否因为父组件渲染而做了大量无用功而不是看组件本身有多复杂。4.2 key 的作用与 diff 算法的关系渲染列表时给每个元素加key这个知识点几乎人人知道但很少人真正理解背后的原因。React 的 diff 算法在比较新旧两个列表时通过 key 来判断哪些节点是复用的、哪些是新增的、哪些需要删除。如果 key 不变React 就认为这个节点可以保留只更新变化的部分如果 key 变了它会卸载旧节点、创建新节点。这意味着一个很容易踩的坑用数组下标当 key。当列表发生插入、删除或排序操作时下标对应的内容会错位React 会错误地复用 DOM 节点导致状态错乱。比如一个输入框列表用户在第一个输入框输入内容之后在列表头部插入一条新数据原第一个输入框里的内容会跑到第二个输入框上。非常典型的 bug而且特别难排查。正确的 key 应当是该条数据在业务上唯一且不变的标识通常是数据库 ID 或唯一编码。如果真的没有唯一字段可以自己维护一个递增标识但绝不要用下标。4.3 代码分割与懒加载单页应用打包后所有页面代码会挤进一个巨大的 JS 包里首屏加载时间会明显变长。React 生态里解决这个问题的标准方案是React.lazy配合Suspense把路由组件按需要拆分。const UserPage React.lazy(() import(./pages/UserPage)) Suspense fallback{Loading /} UserPage / /Suspense首次进入某个路由时才加载对应的 JS 文件而不是一打开首页就全量下载。对中大型项目来说这个优化带来的首屏速度提升是非常直观的。要注意的细节是React.lazy的加载是异步的在 Suspense 的边界之外使用会直接报错。另外不要追求极致将所有组件都拆成极小的 chunk那样会产生大量小文件浏览器请求数暴增反而拖慢加载。一般以路由级别或者功能模块级别拆分就足够了。5. Context 与跨层级通信5.1 Context 的使用模式Context 解决了多层级嵌套组件传值的问题。它提供一种在组件树里共享数据的机制任何层级的子组件都能读取而不需要逐层透传 props。const ThemeContext React.createContext(light) function App() { return ( ThemeContext.Provider valuedark Toolbar / /ThemeContext.Provider ) } function Toolbar() { const theme useContext(ThemeContext) return div主题{theme}/div }需要注意Context 的 value 是存在于 Provider 所在组件中的。如果 Provider 的 value 每次渲染都是新对象那么所有消费了这个 Context 的子组件都会跟着渲染即使它们只用到了一个根本不变化的深层字段。这个问题很隐蔽标准解法是使用 useMemo 来缓存 value。5.2 用 Context 管理全局状态的注意点把 Context 当全局状态管理来用是可行的但不能把它当作 setState 的免费替代品。Context 本质上没有状态管理能力它只负责传递更新仍然要配合 useState、useReducer 等状态容器。当全局状态变得复杂时我倾向于把 Context 按领域拆开一个用于用户信息一个用于主题配置一个用于全局弹窗控制。如果所有状态都塞进一个 Provider任何一次 value 变化都会让所有消费方重新渲染页面性能会受到明显影响。判断是否需要引入第三方状态管理库比如 Redux 或 Zustand的标准很简单如果你的全局状态已经出现大量 slice、需要跨模块互相联动、并且有比较强的异步数据流可以考虑引入轻量的状态库。如果只是主题、语言、用户信息这类低频变化的数据Context 完全够用没必要为它引入额外依赖。6. 常见问题与排查技巧实录6.1 组件渲染次数异常现象一个不依赖任何 props 的子组件在父组件每次输入时都会重新渲染。排查思路先检查父组件是否把内联函数或内联对象作为 props 传给了子组件。比如Child config{{ name: test }} /这样的写法每次父组件渲染都会生成新对象引用导致子组件的 memo 永远失效。解决方式是把 config 用 useMemo 缓存或者把函数改成 useCallback 版本。如果组件没有用 memo那重新渲染其实是 React 的正常行为不需要恐慌。真正需要优化的是渲染成本高且确无必要的组件。6.2 useEffect 无限循环现象组件不断发起请求、页面表现为卡死或频繁刷新。这是使用 useEffect 时最经典的坑。问题一般出在依赖数组上如果 useEffect 内部更新了某个 state而这个 state 又被写进了依赖数组更新会触发 effect 重新执行形成死循环。// 错误示例 useEffect(() { setFilteredList(list.filter(item item.keyword.includes(keyword))) }, [keyword, filteredList])这里filteredList一旦更新又会触发依赖变化再次执行 effect循环不止。正确做法是从依赖数组移除filteredList或者把过滤逻辑放到计算过程里用 useMemo而不是用 effect 去同步状态。6.3 父组件 state 更新后子组件不更新现象子组件明明依赖父组件的某个 state数据变化后视图却没有刷新。两个常见原因一是用React.memo缓存了子组件但 props 传入的引用没有保持稳定结果 memo 的比较逻辑认为 props 没变二是把不该变的值塞进子组件的 state 里做了一份拷贝父组件数据更新后子组件内的本地 state 还停留在旧值。第一种情况检查 props 引用稳定性第二种情况检查本地 state 是不是多余的缓冲。如果子组件只是需要展示父组件的数据就直接用 props不要拷贝到本地 state。6.4 列表 key 导致的输入框内容错乱现象一个列表项每个里面都有输入框输入内容后对列表排序或插入项输入框的内容跑到了别的行。这个就是我前面提到的下标 key 问题。修复方式是把 key 从数组索引换成数据的唯一标识。如果列表数据没有唯一标识就给每条数据增加一个 id 字段在创建时生成。注意即使 key 用的是当前可见且稳定的字段比如用户名如果用户可以重名这个字段也会失效。最稳妥的方案永远是数据条目的唯一 ID。7. 最后补充一些实操心得再分享一个我每次整理项目代码都会做的动作把 hooks 目录里的自定义 Hook 都写上一份简短的 README注明它解决了什么问题、输入输出是什么、有哪些边界情况。这个习惯救过我很多次。因为自定义 Hook 一旦抽象出来过了三四个月再看你完全不记得当初为什么这样设计参数而重新读一遍实现成本又很高。另一个建议是在项目里统一安装 lint 插件把 React Hooks 的规则检查打开。它能在你写错依赖数组或 Hook 调用顺序时立刻给出提示省去很多只在运行时才会暴露的 bug。很多团队不做这一步觉得 lint 是束缚但实际体验下来对 React 这种约定大于配置的框架来说静态检查是性价比最高的防线。写这篇笔记的过程其实也是我自己对这些知识点的一次回炉。每个项目踩过的坑都在加深对 React 设计哲学的理解。如果你读到某个地方产生了原来如此的感觉不妨打开自己的项目验证一下如果发现了更深入的问题也别犹豫继续往底层挖下去。
RELATED

相关推荐

Afkayas.2 CrackMe 逆向实战:从脱壳到注册机

Afkayas.2 CrackMe 逆向实战:从脱壳到注册机

说句实在话,CrackMe 这玩意儿在很多人眼里就是个“破解练习”,但在逆向圈子里,一套精心设计的 CrackMe 往往比好几本教程都管用。Afkayas 系列作为经典的入门序列,里面的手法覆盖了字符串定位、API 断点、关键跳转分析、算法还原这…

📅 2026/10/20 15:48:21
diagram-design:如何设计可维护的架构图与流程图

diagram-design:如何设计可维护的架构图与流程图

1. 从一张草图到一套系统:diagram-design 到底在解决什么问题 第一次看到 “diagram-design” 这个标题,很多人会以为它只是教你怎么画流程图、架构图或者线框图。但真正做过项目的人都知道,图不是画给自己看的,而是画给未来的自己…

📅 2026/10/20 15:48:21
PHP批量删除MySQL相同前缀数据表:高效安全的运维实践

PHP批量删除MySQL相同前缀数据表:高效安全的运维实践

简介:这是一份针对MySQL数据库批量清理场景的PHP源码工具,可根据指定前缀一次性删除匹配的数据表,主要面向开发、测试及运维人员,解决手动逐表删除效率低、易出错的问题,尤其适合清理临时表或重置项目数据。压缩包仅含…

📅 2026/10/20 15:48:21
MORE NEWS

更多资讯

📰

FP8量化与长上下文推理:从权重压缩到KV Cache优化的工程实践

1. 从权重精度到推理效率:FP8 量化到底动了哪块蛋糕Llama 3 这代模型放出来之后,社区里讨论最多的除了上下文长度,就是显存和吞吐。405B 的版本用 BF16 存一遍权重就要吃掉将近 810GB 显存,别说单机多卡,很多小团队连加…

📰

企业外网互通标准化:从人治到规治的高确定性运维实践

1. 这事儿到底解决什么问题:企业外网互通的痛点拆解干企业网络运维的,尤其是负责总部到分支、总部到数据中心、以及和外部合作伙伴互联的兄弟们,一定对下面这些场景不陌生:业务部门说今天要开一个视频会议,要求跟某分支…

📰

S19转HEX实战:用hexview完成格式转换与校验

简介:一套专为车载电子与嵌入式开发准备的工具包,面向需要解析英飞凌S19文件、执行HEX文件查看与格式转换的工程师,也适用于汽车电脑硬件、信息安全等场景的固件分析。压缩包共22个文件,包含可执行程序、动态链接库、配置文件、日…

📰

Windows 10 下安装启动 MyCAT:分库分表中间件实战入门

数据库在业务初期很听话,数据量一上来就开始闹脾气。查询慢、单表数据爆炸、主从同步搞不定,这些问题我猜不少同行都经历过。解决方向无非两个:换更强的机器,或者换个架构把数据拆开。前者是花钱,后者是花时间。如果你…

📰

HVS融合视觉传感器:像素级帧事件双通路如何重塑下一代多模态视觉

在今年的国际计算机视觉大会上,我们团队分享了一个主题:从成像到感知。我讲了整整四十分钟,核心就是HVS融合视觉传感器,以及它为什么会成为下一代多模态视觉范式的重要基础。之所以想把这个分享沉淀成文字,是因为会后有…

📰

Keil5新建工程全流程:从芯片Pack到编译烧录避坑指南

刚接触嵌入式开发那会儿,我最怕的就是“Keil5新建工程”这一步。为啥?因为照着别人的教程一步步点,总会冒出芯片列表空白、编译报错、烧录不到板子这类幺蛾子,而且网上教程各说各话,有的说勾这个,有的说勾那…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬