ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

React组件通信完全指南:从props到Context再到Zustand

React组件通信完全指南:从props到Context再到Zustand 做React开发这些年我越来越觉得组件通信是前端面试和实战里最“基础但见功力”的板块。说它基础是因为父传子、子传父这些套路闭着眼都能写说它见功力是因为一旦项目到了中大型规模组件之间怎么说话、数据往哪儿放、状态怎么同步直接决定代码是越改越顺还是越改越乱。这篇文章我打算把React组件通信这件事彻底聊透从最朴素的props讲到Context、从useReducer讲到Zustand顺便把我在实际项目里踩过的坑和排查思路也一并分享出来。不管你是刚接触React的初学者还是准备面试想查漏补缺又或者是维护一个状态管理比较混乱的旧项目想找找思路这篇文章应该都能给你一些参考。我会尽量用大白话讲原理配合可以直接运行的代码示例把每一种通信方式的适用场景、边界条件和注意事项都说清楚。1. 为什么组件通信总是绕不开先从数据流说起1.1 单向数据流到底解决了什么问题React最核心的设计思想之一就是单向数据流。简单来说数据只能从父组件流向子组件子组件不能直接修改父组件的数据只能通过父组件提供的回调函数来“请求”修改。这个设计的价值在于当数据发生变化时我们可以沿着组件树从根到叶子去追踪数据流向而不是像双向绑定那样一个数据可能在多个组件里被随意改来改去出问题时查无从查。很多刚接触React的同学会问这不麻烦吗我直接在子组件里改父组件的数据多方便。其实你只要经历过一次线上bug排查就明白了。在一个双向绑定的系统里如果某个状态变了但UI没更新你可能要排查五六个可能修改它的组件而在React的单向数据流里状态在哪个组件声明就只能在哪个组件里被修改其他组件只能通过回调函数间接影响它。这种“约束”看起来是限制实际上是给复杂项目上了一道保险。理解了单向数据流再看组件通信就会有完整的地图感既然数据只能向下流那么“子传父”本质上就是“父组件把修改数据的权利以函数形式传给子组件”“兄弟通信”本质上是“把共同状态放到父组件让他们共享”“跨层级通信”则是“绕过中间层组件直接传递数据”。万变不离其宗。1.2 通信方式全景图哪些场景用哪种方式在动手写代码之前脑子里先有一张通信方式的“选择清单”会非常有用。我把日常开发里常用的通信方式按场景整理了一下通信场景推荐方案为什么不推荐其他方案父传子props最简单符合单向数据流子传父回调函数数据归属不变修改权留在父组件子组件直接修改父组件状态useImperativeHandle ref打破数据流向应尽量避免兄弟组件之间状态提升到共同父组件避免引入全局状态降低维护成本跨多层传递Context相比逐层传props少写大量样板代码任意组件之间的临时通信事件总线mitt / 自定义EventEmitter不适合核心业务状态只适合临时事件通知复杂全局状态Redux / Zustand / MobX有配套调试工具和持久化方案这不是说这些方式互斥实际上一个成熟项目里它们往往是混合使用的。核心原则是能用props解决的不要用Context能用Context解决的不要引入状态管理库。每一层抽象的引入都在增加理解和调试的成本。2. 父子通信props与回调函数的正确打开方式2.1 父传子props的完整使用细节父传子是最基础的通信方式几乎任何React开发者都会写。但细节之处见真章有几个点我建议你留意。首先是props的只读性。React官方的规则是组件不能修改自己的props。这不仅仅是规范更是为了让数据流可预测。如果你在子组件里直接写props.value xxxTypeScript会直接报错JavaScript跑起来也不会有任何效果因为React根本不会用这种方式去触发更新。其次是默认值与类型校验。函数组件里可以通过defaultProps或者在函数参数里给默认值来设置props的缺省值更推荐后者因为defaultProps在函数组件的类型推导上一直有些别扭。类型校验方面TypeScript的interface比PropTypes好用得多新项目直接用TS就好PropTypes更适合纯JS项目做运行时兜底。还有一个容易被忽视的点props是组件间通信的“最小契约”。传什么、不传什么、什么时候传都应该在组件设计时想清楚。我见过不少代码父组件把整个业务对象user直接传给子组件子组件要用的时候自己从里面取user.name、user.email。短期看省事长期看耦合严重——子组件对整个对象的结构产生了隐式依赖父组件只要改一下对象的字段名子组件就悄悄出bug了。更好的做法是只传子组件真正需要的字段或者传一个稳定的值对象。2.2 子传父回调函数、ref与useImperativeHandle子传父最常见的做法是父组件传一个回调函数进去子组件在恰当的时机调用这个函数把数据“递”给父组件。举个例子function Parent() { const [list, setList] useState([]); const handleAdd (item) { setList((prev) [...prev, item]); }; return Child onAdd{handleAdd} /; } function Child({ onAdd }) { const [text, setText] useState(); return ( div input value{text} onChange{(e) setText(e.target.value)} / button onClick{() onAdd(text)}添加/button /div ); }这里有个容易被忽视的细节setList里用的是函数式更新setList((prev) [...prev, item])而不是直接setList([...list, item])。原因在于如果父组件里有多个异步操作同时触发更新函数式更新能保证每次都基于最新的state做计算避免闭包捕获旧值导致的数据覆盖。这是我建议所有React开发者养成的好习惯不只是组件通信任何依赖旧state计算新state的场景都应该用函数式更新。还有一种是子组件通过ref向父组件暴露方法。就像useImperativeHandle这个名字暗示的它的本质是“命令式”地让父组件驱动子组件执行某些操作比如聚焦输入框、重置表单、触发某个内部逻辑。function Child(props, ref) { useImperativeHandle(ref, () ({ focusInput: () inputRef.current?.focus(), reset: () setText(), })); return input ref{inputRef} value{text} /; } Child forwardRef(Child);这种做法在某些场景下非常高效比如父组件要控制子组件的表单状态但它有一个代价它绕过了React的声明式数据流。父组件调用childRef.current.focus()时本质上是直接操作DOM或子组件实例这种“命令式”调用会让数据流向变得不那么透明。我的建议是能用声明式的props 状态控制解决的尽量别用refref适合那些本质上就是命令式的操作DOM聚焦、动画触发、第三方库实例管理。2.3 受控组件与非受控组件一种容易被忽略的通信形态聊父子通信很少人会把受控组件和非受控组件牵扯进来但其实它们本质上也是一种通信模式。受控组件指的是表单的值由React state控制用户输入触发onChange回调回调更新statestate再回流到value。这其实是“父传值 子传事件”的组合。非受控组件则是表单值由DOM自己维护React通过ref去读取。它的代码更简洁但代价是你不能在其他地方自由控制表单值。在实际项目中我通常建议优先使用受控组件。虽然多写几行代码但它让表单状态变得可预测、可联动也更方便做校验和格式化。非受控框架组件比如一些老牌的日期选择器适合封装成独立组件对外提供命令式接口但业务页面层面的表单受控模式几乎是唯一推荐。3. 兄弟与跨层级通信状态提升与Context的取舍3.1 状态提升最朴素的方案为什么往往是首选当两个兄弟组件需要共享数据时最直接的做法是把共享状态放到它们的共同父组件里通过props分发下去这就是状态提升。这个方案几乎没有引入任何新概念完全遵循单向数据流可读性最好。举个例子一个筛选器和一个列表组件需要共享筛选条件function Page() { const [filter, setFilter] useState(all); return ( div FilterBar value{filter} onChange{setFilter} / ItemList filter{filter} / /div ); }filter只存在于Page里FilterBar负责展示和触发修改ItemList负责根据filter渲染列表。整个逻辑一目了然。状态提升在“仅此一层共享”时是最优解因为它的心智负担最低调试时从哪个组件拿到状态、从哪里修改状态都极其清楚。但状态提升也不是没有代价。如果你的组件树比较深共享状态在顶层而中间层组件完全不需要这个数据就出现了所谓的props钻探。比如Page Section List Item / /List /Section /Page如果Item需要Page里的某个状态而Section和List都只是“路过”那它们也不得不接收并转发这个props。代码变得冗长而且中间层组件和后端组件形成了隐式耦合。这时候就该考虑Context了。3.2 Context解决props钻探但别让整个应用跟着重渲染Context API是React官方的跨层级通信方案。它允许你在组件树顶层提供一个值树里任意层级的组件都能直接消费这个值无需逐层传递。const ThemeContext createContext(light); function App() { const [theme, setTheme] useState(light); return ( ThemeContext.Provider value{{ theme, setTheme }} Toolbar / /ThemeContext.Provider ); } function Toolbar() { const { theme } useContext(ThemeContext); return div当前主题{theme}/div; }Context解决了props钻探的问题但它有一个非常关键的性能陷阱很多人刚上手时都会踩Provider的value一旦变化所有消费这个Context的组件都会重新渲染。如果你在value里放了一个每次渲染都新建的对象比如useMemo都不用直接在JSX里写value{{ theme, setTheme }}那即使theme根本没变只要父组件渲染所有consumer也会跟着渲染。所以用Context时要记住两点一是value尽量用useMemo缓存让它可以保持不变二是把Context按职责拆开别把十几个状态塞进一个大而全的Context里宁可拆成UserContext、ThemeContext、ConfigContext等也不要做“万能Context”。拆开之后每个consumer只订阅它关心的那部分重渲染的影响面就小得多。我在实际项目里见过最典型的反面案例有人在根组件设置了一个AppContext里面放了用户信息、主题、权限、菜单折叠状态、当前路由等等十几个状态。结果改一下主题半个应用跟着重新渲染加一条用户信息字段几乎所有组件都间接依赖这个Context。后面我把这个巨型Context拆成了四个小Context再用selector模式优化读取重渲染问题一下子缓解了。说到selector模式React官方没有内置类似Redux的useSelector直接用在Context上但社区常用做法是自己在useContext外面包一层利用useSyncExternalStore做细粒度的订阅。如果你用的是React 18及以上版本这个hook值得研究一下它可以从外部store读取值并且保证并发特性下的数据一致性。3.3 事件总线适合临时通信但需要时刻警惕泄漏事件总线是另一种跨组件通信思路。核心是把一个自定义事件Emitter放在模块级别或全局让组件之间通过emit和on来发送和接收消息绕开组件树层级。import mitt from mitt; export const emitter mitt(); // 组件A emitter.emit(refresh, { id: 1 }); // 组件B useEffect(() { const handler (data) refreshList(data); emitter.on(refresh, handler); return () emitter.off(refresh, handler); }, []);事件总线非常适合临时通知场景比如“数据刷新了”“弹窗关闭了”“某个拖拽完成了”就像组件之间的消息广播。但它的缺点也很明显第一是全局命名空间冲突。事件名是字符串项目一大你很难保证“refresh”这个事件不会在另一个模块里被意外监听或重复触发。第二是调试困难。单向数据流里我们追踪数据流向很直观但事件总线一多代码就像电话线缠在一起出问题时很难定位。第三也是最重要的内存泄漏风险。组件卸载后如果没有在useEffect的cleanup里解绑事件监听函数会一直留在Emitter里指向被卸载组件的闭包导致内存泄漏甚至重复触发。我之前就踩过坑一个列表页面反复进出多次后发现滚动事件被绑定了N次每次滚动都要执行N个handler页面越来越卡。我的结论是事件总线适合页面内几个组件之间的临时协作不适合管理业务状态。业务状态应该放在明确的地方组件state、Context、状态管理库事件总线只做“通知”不做“存储”。4. Hooks时代的状态协作useReducer Context的组合拳4.1 为什么要用useReducer管理共享状态React 16.8推出Hooks之后组件通信的方式也跟着进化了。最典型的变化就是useReducer Context替代了很多原本需要Redux才能解决的场景。useReducer解决的问题是当状态更新逻辑比较复杂、多个动作都要修改同一个状态时把所有修改逻辑集中到一个reducer函数里让状态变化变得可预测、可测试。拿一个常见的购物车场景举例function cartReducer(state, action) { switch (action.type) { case ADD: return { ...state, items: [...state.items, action.payload] }; case REMOVE: return { ...state, items: state.items.filter((i) i.id ! action.payload.id) }; case CLEAR: return { ...state, items: [] }; default: return state; } }这个reducer是纯函数同样的输入永远得到同样的输出没有任何副作用。这个特性让状态更新逻辑可以单独做单元测试也方便在调试时回放每一步状态变化。配合Context可以把reducer挂在顶层让深层次的子组件都能dispatch动作来修改状态而不必把一堆回调函数层层传递下去。4.2 如何封装一个干净的Provider我比较推荐的做法是封装一个专门的Provider组件把Context、reducer、自定义hooks集中在一起让业务组件只依赖自定义hook不直接接触Context API。来看一个示例const CartContext createContext(null); export function CartProvider({ children }) { const [state, dispatch] useReducer(cartReducer, { items: [] }); const actions useMemo( () ({ addItem: (item) dispatch({ type: ADD, payload: item }), removeItem: (id) dispatch({ type: REMOVE, payload: { id } }), clearCart: () dispatch({ type: CLEAR }), }), [] ); const value useMemo(() ({ state, actions }), [state, actions]); return CartContext.Provider value{value}{children}/CartContext.Provider; } export function useCart() { const ctx useContext(CartContext); if (!ctx) { throw new Error(useCart must be used within CartProvider); } return ctx; }这里有两个细节值得说说。一是actions用useMemo缓存了空依赖数组这样actions的引用就永远不会变消费它的组件不会因为actions引用变化而无谓重渲染。二是value也用useMemo包了一层只有state或actions真正变化时才生成新对象。这两个优化对于Context性能至关重要能省掉大量不必要的子组件渲染。如果你的项目比较大一个Provider管的状态过多建议用组合Provider方式把不同领域的Provider分别封装然后在一个AppProviders组件里嵌套export function AppProviders({ children }) { return ( ThemeProvider AuthProvider CartProvider{children}/CartProvider /AuthProvider /ThemeProvider ); }这样分工明确每一个Provider只负责一个领域消费者也只订阅自己关心的部分。4.3 服务端数据同步场景里的通信新需求现代前端项目里组件通信的需求已经不局限于纯粹的客户端状态了。Service Worker、SSE、WebSocket推送的数据也经常需要跨组件分发。比如一个实时看板后端通过SSE推送数据多个组件需要同时响应这些数据并更新UI。这种场景下我建议把推送数据也纳入状态管理。最简单的方式是在Provider里监听SSE消息收到新数据后dispatch一个动作更新state所有订阅这个state的组件自然刷新function PriceProvider({ children }) { const [price, dispatch] useReducer(priceReducer, initialPrice); useEffect(() { const eventSource new EventSource(/api/prices/stream); eventSource.onmessage (e) { const data JSON.parse(e.data); dispatch({ type: UPDATE_PRICE, payload: data }); }; return () eventSource.close(); }, []); return ( PriceContext.Provider value{price}{children}/PriceContext.Provider ); }这样组件之间的通信逻辑就非常清晰推送源统一进reducerUI组件只负责消费state。组件不需要知道数据是从WebSocket来的、从轮询来的还是用户操作来的它们只关心“价格变了”这个事实。这种设计也让你可以随时替换底层数据来源比如从SSE换成WebSocket而不用改任何消费组件。5. 状态管理工具选型Redux、Zustand与MobX怎么选5.1 选型判据项目规模、团队熟悉度、调试需求每当聊到组件通信总绕不开状态管理工具选型。我的经验是没有“最好的状态管理库”只有“最适合当前项目状态的管理方案”。选型的时候我通常问自己三个问题第一项目的状态复杂度在什么量级如果只有一个全局用户信息和两个页面级状态根本不需要Redux或ZustandContext加useState就足够了。引入了外部库反而让新成员需要额外学习。第二团队对哪种模式最熟悉如果团队所有人都在Redux生态里深耕过那就算Zustand更好用也要认真衡量迁移成本和学习成本。工具是给人用的团队效率优先。第三需要什么样的调试能力Redux最著名的就是时间旅行调试和action日志这在排查复杂状态问题时极有帮助。Zustand这次虽然没有Redux DevTools那样强的调试生态但它的轻量简洁对很多项目来说更实用。5.2 Zustand为什么在2025年后成为多数新项目的默认选项Zustand在近两年几乎成了新项目的默认选择我觉得这不只是流行趋势而是有几个实实在在的产品设计优势。首先Zustand非常轻量核心API只有create、getState、setState、subscribe这么几个心智负担极低。不像是Redux那样需要理解store、reducer、action、dispatch、selector、middleware等一系列概念。一个简单的Zustand store是这样的import { create } from zustand; export const useCounterStore create((set) ({ count: 0, increment: () set((state) ({ count: state.count 1 })), })); // 组件中使用 function Counter() { const count useCounterStore((state) state.count); const increment useCounterStore((state) state.increment); return button onClick{increment}{count}/button; }注意这里useCounterStore接收一个selector函数这也是Zustand性能优势的核心每个组件只订阅它select的那部分状态其他状态变化不会导致这个组件重渲染。这一点天然解决了Context的那种“一改全改”的问题。其次Zustand的store可以在React组件之外访问。这个特性在处理“非React环境”比如路由拦截器、请求拦截器、页面事件handler时极其方便你不需要把状态通过props层层传进去直接useCounterStore.getState()就能拿到最新状态。这在React生态里是个很大的灵活性优势。另外Zustand也提供了middleware支持和持久化方案persist中间件做本地缓存非常方便。如果你对Redux的action日志特别依赖Zustand也有devtoolsmiddleware可以接入Redux DevTools近年来越来越顺手了。我并不是说所有项目都该用Zustand。如果你的项目有非常严格的action纪律、团队已经习惯Redux Toolkit的一切那继续用Redux完全没有问题。但如果你问我“新团队开一个新项目状态管理怎么选”我大概率会推荐Zustand因为它学习曲线平缓、性能可控、对现代React开发流程友好。5.3 工具再好也救不了乱设计跨页面状态应该怎么规划状态管理工具是放大器不是创可贴。如果你组件设计本身是乱的换再高级的库也只会把“乱”放大。我在团队里带新人时通常会给他们一条状态规划的基本准则一个状态放在哪里取决于它“影响的范围”和“被修改的频率”。影响范围只在单个页面内的放在页面组件的useState或useReducer里影响范围跨越多个页面或全局用户身份、权限、主题、语言等才考虑放全局store或Context。状态被修改得越频繁越要尽量缩小它的共享范围这样每次更新产生的重渲染影响面就小。另外一个常见的坏味道是把一个组件的内部状态提升成了全局状态。比如A页面有个筛选条件只有A页面和A页面里的子组件用到结果有人为了“保险”把它放进了全局store。后面维护的人看到全局store里有个filter以为其他页面也会用它就小心地不敢动累赘状态越积越多。这类问题不是工具能解决的只能通过代码评审和良好的模块边界来预防。所以我的建议是状态管理方案确定之后花点时间写清楚“状态分层规则”哪些状态属于组件层级、哪些属于业务模块层级、哪些属于全局层级写进团队文档里。这比任何库都管用。6. 组件通信的常见坑与排查实录6.1 阻塞Context重渲染如何排查先说一个我猜很多React开发者都遇到过的场景页面卡顿尤其是Context状态更新后大片组件在“不该动的都动了”。排查这种问题的第一步永远是确认是不是真的存在不必要的重渲染。用React DevTools的Profiler录制一段交互看看哪些组件在状态没变化的情况下也渲染了。常见的重渲染来源有三个一是Provider的value对象每次渲染都新建二是selectors没写好导致消费的组件订阅了不关心的状态三是中间层组件没有memo。针对第一个问题用useMemo包住value就可以针对第二个问题Zustand的selector方案比Context天然更有优势针对第三个问题把中间层组件用React.memo包起来确保props没有变化时不重渲染。如果项目用的还是React 18之前的版本Context重渲染问题会更头疼因为那时候没有useSyncExternalStore这些细粒度的订阅方案。升级到React 18之后你会发现配合useSyncExternalStore或者直接把某些订阅逻辑迁移到Zustand体验会好很多。6.2 幽灵更新依赖数组里的函数引用Hooks时代另一个非常经典的坑是在useEffect或useMemo的依赖数组里放进了一个“变量引用每次渲染都变”的对象。比如useEffect(() { doSomethingWithCallback(props.onChange); }, [props.onChange]);如果父组件传入的onChange是每次渲染都新建的箭头函数没有用useCallback这个effect就会每次父组件渲染后都执行造成看似“没改代码却有幽灵更新”的现象。解决方法是父组件里用useCallback把回调函数缓存起来或者子组件里对props做解构后单独引用稳定的依赖。有个经验法则一个函数如果作为props传给子组件或者放进useEffect的依赖数组就应该用useCallback包起来一个对象如果作为Context的value或者作为props传给memo组件就应该用useMemo包起来。这个习惯养成了能避免绝大多数无谓重渲染。6.3 死循环setState在渲染阶段的直接调用我在代码评审里见到过这样一个典型错误function List({ items }) { const [total, setTotal] useState(0); setTotal(items.length); // 错误渲染阶段直接修改state return div{total}/div; }在React 18严格模式下这种写法会直接报“Cannot update a component while rendering a different component”的错误React会阻止这种行为因为渲染期间修改state会导致无限循环。正确做法是在事件handler里修改或者在useEffect里监听items变化再计算。如果这个计算只是“根据props推导”并没有需要异步更新的逻辑那直接用const total items.length就行压根不需要state。组件通信里也有类似的问题子组件收到props后用state复制了一份同步数据然后一直在和props“打架”两边互相覆盖。这种情况通常是因为组件设计没想清楚“数据源头到底在哪”。如果数据由父组件拥有子组件应该直接用props需要修改时调用回调如果子组件需要一份可编辑的副本初始化时用state并且用key来重置或者用effect同步千万别在渲染阶段直接setState。6.4 性能避免不必要的状态提升与全局状态状态提升和全局store虽然方便但过度使用同样会带来性能问题。我建议你写代码前先想一个问题“这个状态真的需要被很多组件关心吗”。如果答案是没有那它就应该待在离使用方最近的地方。举个例子有个用户列表页面点击某一行展开详情抽屉。这个“当前选中的行”状态只影响列表高亮和抽屉内容根本不需要进入全局store。如果硬要放进全局store每次选中行变化就会导致所有订阅该store的组件重新渲染即使它们完全不关心当前选中的是哪一行。状态提升也是同理。只要你把一个状态提到共同父组件那么任何修改这个状态的操作都会导致父组件以及所有子组件整体重新渲染取决于memo情况。所以提升之前要想清楚这个共享是必须的吗能不能用组合children作为props来减少中间层的数据传递7. 面试速查组件通信题目怎么答才有区分度7.1 从父子到跨层级一题带出知识边界组件通信几乎是前端面试必考题问你“React组件有哪些通信方式”这类题目很多人张口就来父传子props子传父回调兄弟状态提升跨层级用Context或Redux。这个回答本身没错但只能算及格。真正有区分度的回答是要能把“为什么”和“边界”讲清楚。我建议按这样一个递进去组织答案先讲单向数据流说明React默认数据是从上往下传播的这个设计是组件通信的基石。再讲父子通信父传子用props子传父用回调函数重点是props不可变修改数据的权力应留在数据声明处。然后讲兄弟通信优先状态提升到共同父组件强调这种方式的可预测性。接着讲跨层级Context解决了props钻探但代价是value变化会触发大范围重渲染所以要用useMemo、useSyncExternalStore等做优化。最后讲全局状态引入Redux或Zustand这类外部store解决大型应用的状态共享同时也要承认这是一种权衡不是所有项目都需要。这个回答展示的不仅是知识点本身还有你的工程决策能力。面试官通常更关心的是你面对一个具体场景时会不会选择最合适的方案而不是把所有方案背一遍。7.2 答出原理和取舍才会拉开差距面试中另一个高频追问是“为什么子组件不能直接修改父组件的state”。答案不只是“React规定”而是“为了保证状态可追踪和可预测降低排查成本”。能够从工程角度解释设计动机会比只记住结论强很多。再比如面试官问“Context和Redux有什么区别”。优秀的回答不是列“Redux有reducer、有dispatch、有中间件”而是要说两者解决的问题维度不同。Context是React内置的跨层级传值机制适合中等规模的状态共享但它在重渲染治理上需要自己操心Redux提供的是更规范的状态变更流程和调试工具适合大型应用对状态可追溯性的要求。到这一步Zustand自然也可以融入进去作为第三种更轻巧的方案作对比。如果时间允许还能提一句“React 18的useSyncExternalStore让外部store接入React时可以获得更好的并发特性支持”这已经能体现出你对React生态新进展的敏感度。组件通信这块知识看起来是“各种API背下来就好”但背下来是最不重要的一步。真正有价值的是理解了每一次数据流动背后的约束和取舍那些坑踩过之后总结出来的经验才是面试和实战里真正拉开差距的东西。我自己在实际项目里最深的体会是组件通信的代码写得“可见”比写得“聪明”更重要。一段清晰直白的props传递永远好过一段花哨的全局状态设计一个明明白白的状态提升永远好过一个“这个状态到底在哪”的谜题。通信方式的选择本质上是在做设计决策而设计决策的好坏三年后的自己会告诉你答案。
返回列表