React笔记 1.React是什么用于构建用户界面UI的开源 Js库, 将数据渲染成html视图; 最初由 Facebook现 Meta开发并维护旨在提供一种高效、灵活且可重用的 UI 组件开发方式。核心工作原理与特性组件化架Component-basedReact 将复杂的页面拆分为独立、可复用的“组件”如按钮、导航栏、整个页面。每个组件都包含自己的逻辑和外观这使得代码更加模块化、易于维护。虚拟 DOMVirtual DOM这是 React 高效的关键。当组件的状态发生变化时React 会先在内存中更新轻量级的“虚拟 DOM”然后与真实的 DOM 进行对比Diff 算法最后只将发生变化的部分更新到真实页面上从而极大地减少了浏览器的重绘开销提升了性能。声明式设计与单向数据流React 采用声明式范式开发者只需描述界面“应该长什么样”当数据变化时界面会自动更新。同时它遵循单向数据流通过props从父组件向子组件传递数据保证了数据的可控性和可预测性。关键语法与开发模式JSX 语法开发者在 js中直接编写类似 HTML 的结构。这打破了传统前端将结构与逻辑分离的做法让渲染逻辑和内容展示维护在同一个地方开发更加直观。React HooksHooks如useState、useEffect是 React 的重大创新它允许开发者在不编写类Class的情况下在函数组件中使用状态管理和其他 React 特性大幅简化了代码结构。服务端组件Server Components较新的特性允许组件仅在服务器端运行有助于实现前后端工作分离进一步提升应用性能和数据获取能力。生态系统与跨平台能力丰富的生态React 拥有极其庞大的社区和第三方库支持开发者可以方便地集成路由、状态管理等工具灵活选择适合自己项目的技术栈。跨平台开发除了用于构建 Web 单页应用SPAReact 的设计思想还衍生出了 React Native允许开发者使用类似 React 的语法来开发 iOS 和 Android 原生移动应用实现“一次学习多端开发”。1. 核心语法与设计哲学React定位为“UI 库”推崇极致的灵活性。它采用 JSX 语法将 HTML 结构与 JavaScript 逻辑写在同一个文件中要求开发者具备较强的函数式编程思维。React 采用单向数据流和不可变数据设计状态变更需要手动调用setter函数来显式触发重渲染。Vue定位为“渐进式框架”追求“自动高效”。它采用 Template模板语法将 HTML、JS 和 CSS 分离在单文件组件中对传统前端开发者非常友好。Vue 拥有强大的响应式系统基于 Proxy能够自动追踪数据依赖数据变化时视图会自动更新且支持双向数据绑定v-model。2. 底层原理与渲染性能React采用 虚拟 DOM Fiber 架构。当状态更新时React 会重新执行组件函数生成新的虚拟 DOM 树通过 Diff 算法计算最小变更。其 Fiber 架构支持并发渲染可以将渲染任务拆分为小单元并支持中断在处理复杂交互和高频更新时能提供极其流畅的用户体验。Vue同样采用虚拟 DOM但结合了编译时优化。Vue 的模板编译器在构建阶段会进行静态分析跳过不必要的对比。Vue 3 引入的 Vapor Mode 更是能将模板直接编译为极高效的命令式 DOM 操作指令实现元素级精准更新在中小型应用中性能表现极为出色。3. 学习曲线与开发体验Vue上手极快。官方提供了“全家桶”Vue Router、Pinia 等开箱即用适合快速原型开发和中小型项目。React开发者需要自行在庞大的社区中挑选路由、状态管理如 Redux、Zustand等三方库。适合大型复杂应用、高交互性产品2.创建项目使用vite创建:npm create vitelatest my-react-app -- --template react使用webpack创建:npx create-react-app my-react-appcd my-react-appnpm installnpm run devJsx注意事项不是字符串不可以加引号html标签是小写React组件是大写开头只有一个根标签使用大括号嵌入表达式Jsx标签必须正确结束如果表达式是空值布尔值 undefined 将不会显示属性可以在标签中直接设置style的class要写className事件绑定无自定义参数:const clickHandle (e){e是事件对象}return button onClick{clickHandle}有参数:const clickHandle (name,e){}return button onClick{(e)clickHandle(jack,e)}表单受控绑定const [value,setValue] useState()input type]text value{value} onChange{(e)setValue(e.target.value)}/父子组件传值propsSon name{name} /function Son(props){console.log(props.name)}子父组件传值function Son({onGetSonMsg}){return button onClick{()) onGetSonMsg}}function App(){const getMsg(msg){console.log(msg)}return divSon onGetSonMsg{getMsg} / /div}使用状态提升,实现兄弟组件传值(先传给父组件,父组件再传给兄弟组件)context跨层级通信1.使用createContext创建一个上下文对象Ctx2.在顶级组件中使用Ctx.Provide组件提供数据3.在底层组件通过useContext钩子函数获取数据AppABconst MsgContext createContext()提供:MsgContext.Provide value{msg}AMsgContext.Provide使用:const msg useContext(MsgContext)自定义hooks1.声明一个use开头的函数2.在函数体内封装可复用的逻辑3.把组件中用到的状态或回调return返回出去(对象或数组)4.在需要使用的组件中,执行这个函数,解构出状态和回调函数进行使用3.hooksReact Hooks 是 React 16.8 版本引入的一项核心特性它允许开发者在函数组件中直接使用状态state及其他 React 特性而无需编写类组件。这一创新极大地简化了代码结构提升了开发效率并增强了组件的可复用性。reacthooks只能在组件或hooks中调用,只能在组件的顶层使用,不能嵌套在if for其他函数中使用不要在循环、条件语句如 if、嵌套函数或 try/catch 代码块中调用 Hook。必须确保每次渲染时 Hook 的调用顺序一致。只能在 React 函数组件或自定义 Hook 的主体顶层调用不能在常规的 JavaScript 函数或类组件中调用。为什么 Hook 不能写在条件语句中:React 内部是通过链表和绝对索引来管理 Hook 状态的条件语句会破坏这种固定的顺序在函数组件中每调用一次useState或useEffectReact 都会在内存中为这个组件创建一个对应的 Hook 对象。React 并没有把这些 Hook 放在一个 Map 里比如以名字为 key而是把它们串成了一个单向链表。每个 Hook 对象内部都有一个next指针指向下一个 Hook。匹配机制是按顺序索引读取当组件发生更新时React 需要把当前渲染时产生的新 Hook 和内存中旧的状态链表对应起来。React 是怎么对应的呢非常简单粗暴按顺序索引。第一次执行组件函数遇到第 1 个 Hook就去链表里找第 1 个节点。遇到第 2 个 Hook就去链表里找第 2 个节点。React在设计 Hooks 时面临两个选择允许条件调用这就需要给每个 Hook 命名比如useState(name)React 内部用 Map 去匹配。但这会增加额外的内存开销且违背了 Hooks 极简的初衷。禁止条件调用强制要求开发者把 Hook 写在最顶层。这样 React 内部只需要一个极其轻量的指针链表就能完成状态的挂载和更新性能达到极致。useState在函数组件中添加和管理局部状态。它返回一个状态变量和一个更新该状态的函数useEffect在函数组件中执行副作用操作接受一个函数和一个可选的依赖项数组作为参数useMemo缓存计算结果。依赖项不变时返回缓存的值避免每次渲染都重复计算useCallback用于缓存回调函数的引用。当依赖项没有变化时它会返回缓存的函数避免子组件不必要的重新渲染useRef用于在函数组件中创建并返回一个可变的 ref 对象获取和访问DOM元素useContext在函数组件中访问和消费 React Context 的数据避免组件逐层传递 propsuseReducer类似Redux 的 reducer 函数适合用于管理包含多个子值或复杂逻辑的本地状态useLayoutEffect同步阻塞同步地执行所有useLayoutEffect回调。此时你可以拿到最新的 DOM 节点但屏幕还是旧的还没画出来。useLayoutEffect比useEffect先执行,浏览器绘制完成后执行useEffect什么时候必须使用useLayoutEffect仅当你需要读取 DOM 布局 修改 DOM 布局,且该操作直接影响首屏显示需要避免视觉闪烁时使用。典型场景包括定位弹窗/Tooltip根据触发按钮的位置动态计算弹窗坐标避免弹窗先出现在默认位置再“跳”到正确位置。动态测量尺寸根据容器宽度动态计算高度或一行展示的卡片数量避免页面先按默认值渲染再瞬间重排抖动。滚动位置修正例如进入聊天窗口时需要在绘制前将滚动条定位到底部防止用户看到先在顶部再跳到底部的过程。useOptimisticuseOptimistic允许在进行异步操作如网络请求时乐观地更新用户界面提升应用响应速度的体验。3.1.useStateuseState定义状态后,状态变化则函数组件会重新执行在React中认为状态是只读的,应该替换而不是修改,,直接修改状态不能触发视图更新初始值还可通过函数返回值来设置:const [state, setState] useState((){初始值}) 赋值前可以使用函数执行其他逻辑setState中给的set函数是异步修改状态的,所以执行set函数后立马拿的state还是更新前的值要拿状态更新后的最新值,要使用useUffect((){console.log(state)},依赖项变化时触发的回调函数,[state])如果在set修改状态时,连续累计修改会有更新不及时的bug,需要使用函数式更新setCount(count1)setCount(count1)实际并没加2,因为set函数是同步执行,但是set内部是异步的,第二次执行set函数时,第一次的set结果还没有执行完成,需要改成setcount(新值)或setCount((prev)prev1) // 如果新值基于旧值计算得到,需要通过函数形参拿到旧值,return 出新值如果要更改对象类型的值,并触发组件的重新渲染,必须用展开运算符或Object.assign()来生成一个新对象,使用新对象覆盖旧对象才能触发组件的重新渲染(内部判断对象是否变化,只判断了引用地址)user.name new // 不生效// 用新对象的引用替换旧对象的引用才会触发重新渲染setUser(...user)或setUser(Object.assign({},user))使用useState来模拟强制刷新const [, forceUpdate] useState({})const onRefresh (){forceUpdate({})}const [state, setState] useState({name: a,age: 12})setState({age: 16}) // name会丢失需要使用:setState({...,age: 16}) // name会丢失更新对象需要使用: .../map/filter来创建新的引用惰性初始化如果初始状态的计算开销很大可以向useState传入一个函数如useState(() computeExpensiveValue())。该函数仅在组件首次渲染时执行一次后续渲染会被忽略从而显著提升首屏性能。3.2.useEffectuseEffect是 React 中处理副作用的核心 Hook,接收两个参数包含副作用逻辑的回调函数以及可选的依赖数组不传依赖数组副作用会在每次组件渲染后,包括初始挂载和所有更新都执行。空数组副作用仅在组件首次挂载时执行一次并在组件卸载时触发清理函数。指定依赖[a, b]当数组中的任何一个值发生变化时副作用会重新执行。清理函数回调函数中可以返回一个函数该函数会在下次副作用执行前或组件卸载时触发用于清除定时器、取消订阅等防止内存泄漏。1.为什么 useEffect 的回调不能直接写 asyncuseEffect的回调函数必须返回一个清理函数或undefined。如果直接标记为async它会隐式返回一个Promise对象这会导致 React 在处理清理函数时出错。正确做法在useEffect内部定义一个异步函数然后立即调用它。useEffect(() { const fetchData async () { const res await fetch(/api/data); // ... }; fetchData(); }, []);2. 闭包陷阱在useEffect中使用的状态或 props是当前渲染时的快照。如果依赖数组没有正确配置副作用内部拿到的可能是旧值。经典场景在useEffect中设置定时器定时器回调中读取 state但 state 已经更新定时器里依然是旧值。解决方案将依赖项加入数组或者使用useRef保存最新值。3. 依赖数组的是否变更判断机制React 使用Object.is对依赖数组中的值进行浅比较。基本类型比较值是否相等。引用类型比较内存地址。如果每次渲染都创建一个新的对象或函数即使内容相同useEffect也会认为依赖发生了变化而重新执行。解决方案使用useMemo缓存对象或使用useCallback缓存函数。4. 执行顺序useEffect的执行是异步的。它不会阻塞浏览器的绘制过程。React 会先完成 DOM 更新和页面绘制然后在浏览器的空闲时间执行useEffect中的逻辑。这保证了页面的流畅响应。5. 什么时候不需要” useEffectReact 官方强烈建议不要滥用useEffect。以下场景应尽量避免数据转换如果只是根据现有的 props/state 计算出一个新值直接在渲染期间计算即可不要存入 state 再用 Effect 同步。用户事件处理如果逻辑是由特定交互如点击按钮引起的应该放在事件处理函数中而不是 Effect 里。重置状态当某个 prop 变化时需要重置内部 state应使用key机制或直接在渲染时通过条件判断处理。6. useLayoutEffect 与 useEffect 的区别useEffect异步执行不阻塞页面绘制。适用于数据请求、事件订阅等。useLayoutEffect在 DOM 更新后、浏览器绘制前同步执行。适用于需要在用户看到屏幕前同步测量 DOM 布局或触发重渲染的场景如 Tooltip 定位、动画初始位置计算。滥用会导致性能问题。3.3.useMemo缓存计算结果避免在每次组件重新渲染都重复计算接收两个参数计算函数和依赖项数组const memoizedValue useMemo(() {return XXX}, [a, b])缓存触发条件React 使用Object.is逐个比较依赖项。如果任一依赖项发生变化则重新计算并缓存新值如果所有依赖项都不变则直接返回上次缓存的值。存储位置缓存结果存储在组件对应的 Fiber 节点中组件卸载时缓存随之销毁。使用场景适用于大数据量的过滤filter、排序sort、复杂算法如递归、树结构处理稳定对象/数组的引用避免子组件无效渲染当派生数据如合并的对象、生成的数组作为 props 传递给子组件时如果不使用useMemo每次父组件渲染都会生成新对象导致子组件重渲染。使用useMemo可保持引用不变避免子组件不必要的更新。配合 React.memo 使用经典组合对纯展示型子组件使用React.memo并通过useMemo缓存其 props实现双重优化彻底阻断因父组件渲染导致的子组件无效更新。常见坑useMemo 与 useCallback 的区别useMemo 缓存值useCallback 缓存函数useMemo缓存的是计算结果任意值如对象、数组useCallback缓存的是函数本身返回一个函数引用useMemo 与 useEffect 的区别执行时机useMemo在渲染阶段同步执行useEffect在浏览器绘制后异步执行。主要用途useMemo用于缓存计算结果无副作用useEffect用于处理副作用如 API 调用、DOM 操作。返回值useMemo有返回值useEffect无返回值仅可返回清理函数。过度使用的危害反模式:useMemo本身也有开销需要比较依赖项。对于简单的计算如a b、字符串拼接直接执行可能比使用useMemo更高效。依赖项缺失或引用类型陷阱依赖数组中必须包含计算逻辑中使用的所有变量否则会导致缓存结果不更新。避免直接将新创建的对象/数组作为依赖项如useMemo(() ..., [{ id }])这会导致每次渲染都重新计算。应使用基本类型作为依赖或先用useMemo稳定引用。3.4.useCallback返回一个记忆化的函数版本只有当依赖项发生改变时才会重新生成新的函数实例。useMemo是缓存组件内部的计算结果useCallback是缓存传递给子组件的动作函数接收一个回调函数和一个依赖项数组const memoizedCallback useCallback(() { doSomething(a, b); }, [a, b]);使用场景配合React.memo优化子组件渲染当缓存的函数作为props传递给被React.memo包裹的子组件时保持函数引用的稳定可以避免子组件因父组件重渲染而进行不必要的重渲染。作为其他 Hook 的依赖当传递的函数可能作为某些 Hook如useEffect或另一个useCallback的依赖项时使用useCallback可以避免副作用或计算逻辑被频繁触发。1. useCallback 与 useMemo 的区别useMemo缓存的是函数调用的结果值。会立即执行传入的计算函数。useCallback缓存的是函数本身。它不会调用你传入的函数只是将其缓存起来直至用户触发或依赖改变。底层等价关系在 React 内部useCallback本质上可以视为useMemo的一种特例useCallback(fn, deps)等价于useMemo(() fn, deps)。2. 能否在任何地方添加 useCallback不能 。 滥用useCallback不仅不能优化性能还会降低代码可读性并增加内存开销。如果函数只在组件本地使用如普通的onClick且没有传递给性能敏感的子组件直接声明即可。创建函数本身的性能开销极低useCallback自身的依赖项比较开销有时会抵消其带来的收益。只有在确认存在性能瓶颈如复杂的列表渲染、绘图编辑器等精细交互时才应使用。3. 闭包陷阱与函数式更新在useCallback中如果依赖项为空数组[]函数内部捕获的状态将是初始渲染时的“快照”。为了避免闭包陷阱捕获过时状态在更新状态时应优先使用函数式更新// ❌ 错误可能捕获过时的 count 值 const increment useCallback(() { setCount(count 1); }, []); // ✅ 正确基于最新状态更新避免闭包问题 const increment useCallback(() { setCount(prev prev 1); }, []);4. 减少不必要记忆化的最佳实践React 官方建议通过架构设计来减少useCallback的使用传递 JSX 作为子元素当包装组件更新自身 state 时React 知道其子组件不需要重新渲染从而跳过记忆化。避免过度提升状态不要将表单输入、悬停等短暂状态保存在组件树的顶部或全局状态库中。保持渲染逻辑纯粹如果重新渲染组件会导致视觉瑕疵应修复组件自身的 Bug而不是试图用记忆化来掩盖问题。5.实际运用useCallback最经典、最常用的场景就是父组件将事件处理函数传递给被React.memo包裹的子组件尤其是列表渲染场景// 1. 子组件保持不变 const ProductItem React.memo(({ product, onAddToCart }) { console.log(子组件 ${product.name} 渲染了); return ( div span{product.name} - ¥{product.price}/span button onClick{() onAddToCart(product.id)}加入购物车/button /div ); }); // 2. 父组件 function ProductList() { const [selectedId, setSelectedId] useState(null); const products [{ id: 1, name: iPhone, price: 5999 }, { id: 2, name: MacBook, price: 9999 }]; // ✅ 核心优化使用 useCallback 缓存函数引用 // 因为 setSelectedId 是 React 内置的引用永远稳定所以依赖数组写 [] 即可 const handleAddToCart useCallback((id) { setSelectedId(id); }, []); return ( div {products.map(product ( // ✅ 每次父组件渲染传给子组件的 onAddToCart 内存地址都不变 // React.memo 浅比较通过子组件完美跳过渲染 ProductItem key{product.id} product{product} onAddToCart{handleAddToCart} / ))} /div ); }3.5.useRef受用useRef生成ref对象,绑定到dom标签上,dom可用时用ref.current来获取domconst refContainer useRef(initialValue);返回一个包含.current属性的普通 JavaScript 对象。初始值为你传入的initialValue。const inputRef useRefHTMLInputElement(null) // 元素的初始值都是nullinput typetext ref{inputRef}inputRef.current.focus()使用useRef还可以存储组件渲染周期的共享数据,或者是set函数变更前的数据组件重新渲染rerender时,useRef不会重复初始化ref.current变化时,不会导致组件重新渲染ref.current不可以当做其他hooks的依赖项在父组件中无法使用ref来直接引用函数式子组件,因为默认情况下组件不会暴漏内部DOM节点的refconst childRef useRef(null)Child ref {childRef} / 会报错需要使用React.forwardRef把函数式组件包装起来(他不是hooks,而是react提供的函数)const Child React.forward((props, ref){})使用场景访问和操作 DOM 节点常用于管理焦点、文本选择、触发命令式动画或集成第三方 DOM 库。存储不影响 UI 的可变值如存储定时器 IDsetInterval、WebSocket 实例、防抖/节流计时器、页面滚动位置等。因为这些数据的改变不需要反映在屏幕上用useRef比useState更轻量。保存前一个状态或 Props 的值结合useEffect可以实现类似 Vue 中watch获取旧值的功能如usePreviousHook。1. useRef 与 useState 的区别useState更新会触发组件重新渲染useRef更新不会。useState必须通过 setter 函数修改useRef可以直接修改.current属性useState的更新是异步批处理的useRef的修改是同步的赋值后立即可用如果值的改变需要反映在 UI 上用useState如果是组件的“基础设施数据”如 DOM、定时器用useRef。2. 为什么在 useEffect 中存储定时器要用 useRef 而不是 useState如果用useState存储定时器 ID每次启动/停止定时器都会触发组件重渲染造成不必要的性能开销。使用useRef可以在跨渲染周期持久化定时器 ID且在useEffect的清理函数中能访问到最新的 ID同时避免了闭包陷阱且不会触发额外渲染。3. 为什么不能把 ref 放在 useEffect 的依赖数组中useRef返回的对象引用本身是永远不会变4. 安全访问 DOM Ref 的时机安全时机在useEffect/useLayoutEffect内部或在事件处理函数如onClick中。此时 DOM 已经渲染完毕。不安全时机在组件的渲染期间函数体内部直接读取。首次渲染时 DOM 还没挂载.current为null后续渲染时它可能持有的是上一次渲染的旧 DOM 节点。5. 如何在父组件中获取自定义子组件的 DOM 或方法默认情况下不能直接将ref传递给函数组件。子组件必须使用React.forwardRef包裹并将接收到的ref转发给内部的 DOM 节点。如果不想暴露整个 DOM还可以配合useImperativeHandle自定义暴露给父组件的方法或属性。3.6.useContext跨层级组件共享状态,允许深层子组件直接从祖先组件读取数据。const value useContext(SomeContext);useContext接收一个由createContext创建的 Context 对象。React 会沿着组件树向上查找返回距离当前组件最近的对应 Context Provider 的value。如果没有找到则返回创建 Context 时指定的默认值defaultValue。响应式更新当 Provider 中的value发生变化时所有使用了该 Context 的子组件都会自动重新渲染。import { createContext, useContext, useState } from react; // 第一步建一个“微信群”创建 Context const RedPacketContext createContext(); // 爷爷组件群主发红包 function Grandpa() { const [money, setMoney] useState(100); return ( // 第二步在群里发广播Provider 提供数据 // 谁想听只要在这个标签内部就行 RedPacketContext.Provider value{{ money, setMoney }} div style{{ border: 1px solid #ccc, padding: 10px }} h3爷爷我发了一个 {money} 元的红包/h3 button onClick{() setMoney(money 100)}再发100/button Dad / {/* 爸爸夹在中间 */} /div /RedPacketContext.Provider ); } // 爸爸组件无情的传话机器完全不需要 props 传递 function Dad() { return ( div style{{ border: 1px solid #ccc, padding: 10px, marginTop: 10px }} h4爸爸我不管红包的事我只负责把儿子生出来。/h4 Son / {/* 孙子在更深层 */} /div ); } // 孙子组件直接拿红包 function Son() { // 第三步打开群聊接收广播使用 useContext // 直接拿到爷爷发出来的数据无视爸爸的存在 const { money } useContext(RedPacketContext); return ( div style{{ border: 1px solid #ccc, padding: 10px, marginTop: 10px }} h5孙子太爽了我直接拿到了爷爷的 {money} 元红包/h5 /div ); }1. 性能陷阱Context 的“广播式”重渲染痛点当 Context 的value发生变化时所有订阅该 Context 的组件都会重新渲染即使某些组件只依赖value中未发生变化的属性。在组件嵌套极深或逻辑复杂时这会造成巨大的性能开销。解决方案拆分 Context将频繁变化的状态如表单输入和不常变化的状态如用户信息拆分到不同的 Context 中。结合React.memo对子组件进行记忆化减少不必要的重渲染。使用useMemo在 Provider 中缓存传递的value对象避免父组件重渲染时生成新对象导致 Context 误触发更新。2. 查找规则只向上查找忽略自身机制useContext()总是在调用它的组件上方寻找最近的 Provider它不会读取调用组件自身内部包裹的 Provider。场景这意味着你可以在组件内部提供一个 Context 来覆盖外部的值但当前组件调用useContext时拿到的依然是外部的值只有它的子组件才会拿到内部覆盖的值。3. 默认值defaultValue的作用如果在组件树中没有为某个 Context 提供 ProvideruseContext会返回创建时传入的默认值。注意默认值是静态的永远不会改变。在实际开发中通常将默认值设为undefined或null并在自定义 Hook 中加上空值判断抛出明确的错误提示防止开发者忘记包裹 Provider。4. 对象与函数的引用稳定性与useEffect的依赖数组类似如果 Context 传递的是一个对象或函数每次 Provider 重渲染都会生成新的内存地址导致所有子组件强制重渲染。解决方案在 Provider 中必须使用useMemo缓存对象或使用useCallback缓存函数确保value的引用稳定。5. 模块重复问题构建工具陷阱通过 Context 传递数据只有在提供和读取数据的SomeContext是完全相同的对象通过比较时才有效。如果你的构建系统如 Webpack 符号链接导致模块被重复打包可能会破坏 Context 的连通性。3.7.useReducer适用于单个组件的复杂状态逻辑,比如一个状态变化会联动修改多个字段适合描述状态如何变化1.定义一个reducer函数,根据不同的action返回不同的新状态function reducer(state, action){switch(action.type){case: ADD:return state 1}}2.在组件中调用useReducer,传入reducer函数和状态的初始值const [state, dispatch] useReducer(reducer, 0)3.事件发生时,通过dispatch函数分派一个action对象dispatch({type: ADD})4.Redux和Mobx(React 生态主流状态管理库)Redux 适用场景大型复杂项目需要严格的单向数据流和强约束性保证多人协作下的代码结构清晰。函数式编程如果团队对纯函数、不可变数据有深入理解能充分发挥 Redux 的优势。MobX 适用场景中小型项目/快速迭代上手简单样板代码少开发效率高。面向对象编程MobX 的 Class 装饰器/响应式 API 写法更符合大多数开发者的直觉。追求极致渲染性能MobX 的自动依赖追踪机制能实现组件的精确更新避免不必要的重渲染。ReduxRedux核心概念Store单一数据源、Action描述动作的对象、Reducer处理状态变更的纯函数,状态是不可变的必须通过dispatch提交action触发更新Reducer 必须是纯函数。使用步骤:1.定义一个reducer函数2.使用createStore方法传入reducer函数生成store实例对象3.使用store实例的subscribe方法订阅数据变化4.使用store实例的dispatch方法提交action对象,触发数据变化5.使用store实例的getStatus方法获取最新状态1.定义reducer函数,根据不同的action对象(对象type可标记当前要做的修改),返回新的statefunction reducer(state {count:0}, action){if(action.typeADD){return {count: state.count1}}}2.使用reducer函数生成store实例const store Redux.createStore(reducer)3.通过store实例的subscribe订阅数据变化,回调函数在state变化时自动执行store.subscribe((){console.log(state变化了)})4.通过store实例的dispatch函数提交action更改状态store.dispatch({type: ADD})\5.通过store实例的getStatus方法获取最新状态store.getState().count在react中使用redux官方要求安装插件:Redux Toolkit(简化书写)和react-rudux(链接redux和react的中间件)import { createSlice, configureStore } from reduxjs/toolkit; // 1. 定义 Slice (包含 state 和 reducer) const counterSlice createSlice({ name: counter, initialState: { value: 0 }, reducers: { increment: (state) { // RTK 内部使用 Immer允许写“可变”逻辑但底层仍生成不可变状态 state.value 1; }, }, }); // 2. 配置 Store export const store configureStore({ reducer: { counter: counterSlice.reducer }, }); // 3. 在 React 组件中使用 // 读取状态: const value useSelector(state state.counter.value); // 触发更新: useDispatch()(counterSlice.actions.increment());MobXMobX 的核心概念包括Observable可观察状态、Action修改状态的动作、Computed派生状态、Observer响应式组件。核心特点状态是可变Mutable的采用响应式编程自动追踪依赖无需手动 dispatch。import { makeAutoObservable } from mobx; import { observer } from mobx-react-lite; // 1. 定义 Store (面向对象风格) class CounterStore { count 0; constructor() { // 自动将属性标记为 observable方法标记为 actiongetter 标记为 computed makeAutoObservable(this); } increment() { this.count 1; // 直接修改状态自动触发视图更新 } get doubleCount() { return this.count * 2; // 自动缓存的派生状态 } } const counterStore new CounterStore(); // 2. 在 React 组件中使用 const Counter observer(() { return ( div p{counterStore.count} - {counterStore.doubleCount}/p button onClick{() counterStore.increment()}1/button /div ); });1. 核心设计哲学对比Redux基于推模型Push和函数式编程。状态只读必须通过纯函数Reducer返回新状态。强调过程明确每一步变更清晰可查。MobX基于拉模型Pull 响应式和面向对象编程。状态可变通过 Proxy/getter/setter 自动感知依赖并更新。强调结果响应开发体验流畅。2. 状态更新机制的区别Redux通过dispatch广播 Action所有的 Reducer 都会被调用。更新粒度较粗需要通过对比前后的 state浅比较来控制组件是否重渲染。MobX利用observable实现精确更新。当状态变化时MobX 自动追踪哪些组件依赖了该状态只通知依赖的组件更新无关组件不受影响。3. 异步处理与副作用Redux原生 Reducer 必须是纯函数不能包含异步操作。必须借助中间件如 Redux-Thunk、Redux-Saga来处理异步逻辑学习曲线较陡。MobX没有中间件概念。在action中可以直接使用async/await语法处理异步任务代码更加简洁直观。4. 调试与可预测性Redux可预测性极强。配合 Redux DevTools可以轻松实现时间旅行调试、查看 Action 日志和状态快照非常适合排查复杂 Bug。MobX由于状态修改路径较隐式自动响应调试相对困难状态难以回溯。5. MobX 底层原理面试官可能会问 MobX 的响应式原理。核心在于MobX 将数据包装为 Observable当组件Observer渲染时MobX 会通过reaction.track拦截属性的 getter自动收集依赖当属性被修改setter时自动通知收集到的依赖进行更新。6.React.memo允许组件在props没有改变时跳过渲染react组件的渲染机制是如果父组件重新渲染子组件就会重新渲染useMemo是缓存组件内部的计算结果React.memo是直接缓存整个组件的渲染结果const MyComponent React.memo((props) { return div{props.value}/div; });父组件重新渲染时React.memo会先比较当前传入的props和上一次的propsprops为基本类型,props变化组件重新渲染props为引用类型比较内存地址,当父组件函数重新执行时,实际生成了新的引用地址保障引用确定,使用useMemoconst Son React.memo((props) { return div{props.value}/div; }); // 若不适用useMemo,则每次父组件执行都会新生成list,子组件也会重新渲染 const list useMemo((){ return [1,2,3] },[]) Son list{list}/经典组合React.memo useMemo / useCallback由于React.memo默认只做浅比较如果父组件每次渲染都传递新的对象或函数memo就会失效。因此它必须配合其他 Hooks 使用配合useCallback缓存传递给子组件的事件处理函数。配合useMemo缓存传递给子组件的复杂对象或数组。React.memo并不是万能的它只能阻止由父组件渲染导致的子组件重渲染。以下三种情况即使加了memo组件依然会重新渲染组件自身状态变更组件内部调用了useState或useReducer触发更新。Context 值更新组件消费了Context且 Context 的值发生了变化。强制更新父组件使用了key变更等手段强制子组件卸载并重新挂载。什么时候使用React.memo组件是纯展示型只依赖props。组件渲染开销较大如包含复杂计算、大型列表项、大量 DOM 节点。父组件频繁更新但该子组件的props实际很少变化。为什么不给所有组件都加React.memo缓存和浅比较本身是需要消耗内存和 CPU 的。如果组件很小、渲染成本极低加memo反而会增加比较开销得不偿失。代码可读性下降滥用会导致代码充满包裹的memo、useMemo、useCallback增加维护难度。React.memo与useMemo的区别React.memo用于记忆化整个组件优化子组件的渲染。useMemo用于记忆化函数组件内部的值或计算结果。7.forwardRef使用ref暴露DOM节点给父组件父组件:const sonRef useRef(null)Son ref{sonRef} / // 父元素无法直接通过sonRef获取到子元素的dom元素子组件:使用forwardRef包裹后父组件即可获取子组件的domconst Son forwardRef((props,ref){return input typetext ref{ref} /})

相关新闻

最新新闻

如何在3分钟内批量获取TikTok评论数据?这个开源工具给你答案

如何在3分钟内批量获取TikTok评论数据?这个开源工具给你答案

如何在3分钟内批量获取TikTok评论数据?这个开源工具给你答案 【免费下载链接】TikTokCommentScraper 项目地址: https://gitcode.com/gh_mirrors/ti/TikTokCommentScraper 想要从海量TikTok视频中提取用户评论进行深度分析,却苦于平台没有提供批…

2026/8/3 21:19:56
Unity编辑器Edge.WakeUp报错:从原理到根治的完整指南

Unity编辑器Edge.WakeUp报错:从原理到根治的完整指南

1. 项目概述:一个让Unity开发者头疼的“幽灵”报错 如果你在Unity编辑器里,尤其是在处理动画状态机、Shader Graph、Visual Scripting或者任何涉及节点图(Graph)的系统时,突然在控制台看到一个名为 UnityEditor.Graph…

2026/8/3 21:19:56
手把手教你学 Simulink—— 直线感应电机(LIM)的边端效应补偿与速度控制仿真

手把手教你学 Simulink—— 直线感应电机(LIM)的边端效应补偿与速度控制仿真

目录 一、为什么 LIM 控制必须考虑边端效应?难点在哪? 1.1 纯旋转电机视角(RIM 模型直接套用) 1.2 纯机械/结构视角 1.3 边端效应模型 补偿控制 Simulink 互补(核心思想) 二、仿真总体架构 三、关键…

2026/8/3 21:19:56
如何快速使用pan-baidu-download:百度网盘高速下载终极指南

如何快速使用pan-baidu-download:百度网盘高速下载终极指南

如何快速使用pan-baidu-download:百度网盘高速下载终极指南 【免费下载链接】pan-baidu-download 百度网盘下载脚本 项目地址: https://gitcode.com/gh_mirrors/pa/pan-baidu-download 还在为百度网盘的龟速下载而烦恼吗?今天我要向你介绍一个神奇…

2026/8/3 21:19:56
手把手教你学 Simulink —— 航空燃油泵高速 BLDC 超前角换相(进阶实战版)

手把手教你学 Simulink —— 航空燃油泵高速 BLDC 超前角换相(进阶实战版)

目录 手把手教你学 Simulink —— 航空燃油泵高速 BLDC 超前角换相(进阶实战版) 一、 核心痛点:为什么高速必须用超前角? 二、 Simulink 建模的“生死线” 1. 必须使用 Simscape Electrical 物理模型 2. 求解器设置&#xff…

2026/8/3 21:19:56
PowerShell智能补全与预测配置指南:PSReadLine与CompletionPredictor实战

PowerShell智能补全与预测配置指南:PSReadLine与CompletionPredictor实战

1. 项目概述:为什么我们需要一个更聪明的PowerShell? 如果你每天都在和Windows打交道,无论是管理服务器、编写自动化脚本,还是处理日常文件,PowerShell绝对是你绕不开的工具。它比传统的CMD强大得多,但很多…

2026/8/3 21:14:56