资讯动态

Redux Store 完全指南:创建、配置、中间件与 DevTools 调试

发布时间:2026/9/18 9:38:34 来源:尧图企业网站定制
Redux Store 完全指南创建、配置、中间件与 DevTools 调试【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux本篇技术指南聚焦 Redux 应用的核心枢纽——Store完整讲解如何通过createStore创建单一 store、如何加载初始状态、如何用dispatch/subscribe/getState驱动状态流转并深入 store 增强器Enhancer与中间件Middleware两大扩展机制最后接入 Redux DevTools 进行可视化调试。文中所有原理讲解均结合本仓库reduxjs/redux的 TypeScript 源码与测试用例给出可验证依据读完你不仅能熟练搭建一个可运行的 Redux store还能从源码层面理解其内部工作机制为后续编写异步逻辑thunk打下基础。Redux Store应用状态的中央枢纽在前序章节 Redux Fundamentals Part 3State、Actions 与 Reducers 中我们定义了状态结构、编写了 action type、实现了 reducer并用combineReducers组装出根 reducer。现在是时候把这些碎片拼装成 Redux 应用的心脏——store。store 把 state、actions 和 reducers 三者联结在一起承担以下职责持有当前应用状态通过store.getState()允许外部读取当前状态通过store.dispatch(action)允许更新状态通过store.subscribe(listener)注册监听回调通过store.subscribe(listener)返回的unsubscribe函数注销监听器。官方 API 文档将 store 精炼地描述为store 持有整个应用的状态树改变其中状态的唯一方式就是 dispatch 一个 action 到 store 上从而触发根 reducer 计算新状态见 docs/api/Store.md。值得强调的一点是store 不是一个类class它只是一个带有若干方法的普通对象。一个 Redux 应用永远只有一个 store。当你需要拆分数据处理逻辑时应当使用 reducer 组合reducer composition创建多个可合并的 slice reducer而不是创建多个 store详见 Part 3 的 reducer 拆分章节。这一点在 createStore API 文档 中同样被列为第一条使用提示不要在一个应用中创建多个 store从本仓库的 TypeScript 源码 src/index.ts 可以看到redux 包对外导出的核心函数正是createStore、legacy_createStore、combineReducers、bindActionCreators、applyMiddleware、compose等store 相关的Dispatch、Unsubscribe、Store、StoreEnhancer等类型也一并导出供类型推断使用。创建第一个 Store每个 Redux store 都对应一个单一的根 reducer 函数。前一步我们用combineReducers创建了根 reducer在示例应用中位于src/reducer.js。现在新建store.js文件导入 redux 核心库的createStoreAPI 与根 reducer然后调用createStore(rootReducer)import { createStore } from redux import rootReducer from ./reducer const store createStore(rootReducer) export default storecreateStore的完整签名是createStore(reducer, preloadedState?, enhancer?)见 docs/api/createStore.md参数类型说明reducerFunction根 reducer 函数接收当前状态树与 action返回下一个状态树preloadedStateany可选初始状态用于 SSR 时从服务端水合或恢复序列化的用户会话enhancerFunction可选store 增强器用于扩展第三方能力如中间件、时间旅行、持久化等createStore 的源码级校验逻辑深入本仓库的 src/createStore.ts可以看到createStore在真正创建 store 之前做了严格的参数校验reducer 必须是函数否则抛出Expected the root reducer to be a function. Instead, received: ...不允许传入多个 enhancer如果preloadedState和enhancer同时是函数或第二个、第三个参数都是函数会抛出错误并提示你应该用compose把它们合并成单一函数支持省略 preloadedState当第二个参数是函数而第三个参数为undefined时源码会自动把第二个参数当作 enhancer 处理enhancer preloadedState; preloadedState undefined这正是文档中没有 preloadedState 时可以直接把 enhancer 作为第二个参数这一技巧的底层实现enhancer 必须是函数否则抛出Expected the enhancer to be a function. Instead, received: ...。当 enhancer 存在时源码执行return enhancer(createStore)(reducer, preloadedState)——enhancer 接收原始createStore并返回一个增强版的 store 创建器这就是 enhancer 包装机制的起点。关于 createStore 的弃用说明需要特别说明的是自 Redux 4.2.0 起核心库已将createStore标记为deprecated仅在类型层面显示删除线不会产生任何运行时错误或警告官方强烈建议新代码使用 Redux Toolkit 的configureStore它包装了createStore并提供了更友好的默认配置详见 docs/api/createStore.md 与 migrating-to-modern-redux.mdx。createStore本身不会被移除如果你不想看到 IDE 中的删除线提示可以改用等价导出的legacy_createStore源码见 src/createStore.ts它只是简单转发到createStore。本文档按官方教程的脉络继续使用createStore来讲解核心原理这完全适用于学习场景。加载初始状态preloadedStatecreateStore的第二个参数preloadedState允许你在 store 创建时注入初始数据。常见场景包括读取服务端 HTML 页面中内联的数据、从localStorage恢复上次的持久化状态。例如import { createStore } from redux import rootReducer from ./reducer let preloadedState const persistedTodosString localStorage.getItem(todos) if (persistedTodosString) { preloadedState { todos: JSON.parse(persistedTodosString) } } const store createStore(rootReducer, preloadedState)使用preloadedState时有几个关键约束见 docs/api/createStore.md如果你用combineReducers生成根 reducerpreloadedState必须是与combineReducers键结构一致的普通对象否则各 slice reducer 拿不到对应的初始切片否则你可以传入任何你的 reducer 能理解的数据创建 store 时 Redux 会向 reducer派发一个内部占位 actionINIT来填充初始状态树你不需要也不应该直接处理这个 action只需保证 reducer 在 state 为undefined时能返回合适的初始状态即可。派发 Action让状态动起来store 创建好后即使没有 UI我们也能立刻验证整个更新逻辑。首先建议把src/features/todos/todosSlice.js中initialState里的示例 todo 清空为空数组这样控制台输出更清晰。然后在src/index.js中依次调用 store 的三个方法// Omit existing React imports import store from ./store // Log the initial state console.log(Initial state: , store.getState()) // {todos: [....], filters: {status, colors}} // Every time the state changes, log it // Note that subscribe() returns a function for unregistering the listener const unsubscribe store.subscribe(() console.log(State after dispatch: , store.getState()) ) // Now, dispatch some actions store.dispatch({ type: todos/todoAdded, payload: Learn about actions }) store.dispatch({ type: todos/todoAdded, payload: Learn about reducers }) store.dispatch({ type: todos/todoAdded, payload: Learn about stores }) store.dispatch({ type: todos/todoToggled, payload: 0 }) store.dispatch({ type: todos/todoToggled, payload: 1 }) store.dispatch({ type: filters/statusFilterChanged, payload: Active }) store.dispatch({ type: filters/colorFilterChanged, payload: { color: red, changeType: added } }) // Stop listening to state updates unsubscribe() // Dispatch one more action to see what happens store.dispatch({ type: todos/todoAdded, payload: Try creating a store }) // Omit existing React rendering logic每次调用store.dispatch(action)时会发生以下事情store 调用rootReducer(state, action)根 reducer 内部可能继续调用各个 slice reducer如todosReducer(state.todos, action)store 保存新的 state 值store 依次调用所有订阅的监听回调监听器若持有 store 引用此时可调用store.getState()读取最新状态。运行上面的代码控制台会逐条打印出 dispatch 后的状态变化注意最后一次 action 没有任何输出——因为我们已经调用了unsubscribe()移除了监听器。在写任何 UI 之前我们就已经验证了应用的行为这为后续开发建立了信心。dispatch 在源码中如何工作对照 src/createStore.ts 中的dispatch实现可以看到核心循环非常简洁try { isDispatching true currentState currentReducer(currentState, action) } finally { isDispatching false } const listeners (currentListeners nextListeners) listeners.forEach(listener { listener() }) return action同时源码在派发前做了三道防御性校验这些校验逻辑在 test/createStore.spec.ts 中均有对应测试覆盖action 必须是纯对象通过isPlainObject检查否则抛出Actions must be plain objects...并提示你可能需要中间件如redux-thunk来处理函数类型的 actionaction.type 不能是undefined提示你可能拼错了 action type 字符串常量action.type 必须是字符串推荐用可序列化的字符串而非 Symbol 作为 typereducer 执行期间禁止再次 dispatch通过isDispatching标志位保证否则抛出Reducers may not dispatch actions.——reducer 是纯函数只能返回新状态绝不能有副作用。dispatch的返回值是同一个 action 对象方便起见文档 docs/api/Store.md 进一步补充如果使用自定义中间件包装了 dispatch它可能返回其他值例如一个可 await 的 Promise。subscribe 的细节与陷阱subscribe(listener)注册的监听器会在每次 dispatch 之后被调用无论状态是否真的变化你可以在回调里调用getState()读取最新状态。需要注意见 docs/api/Store.md 与源码注释订阅列表在每次 dispatch 前被快照如果在监听器执行期间进行 subscribe/unsubscribe不会影响正在进行的这一次 dispatch但下一次 dispatch 会使用新的快照监听器不应期待看到所有状态变化嵌套 dispatch 时状态可能已被多次更新监听器中可以 dispatch订阅是在根 reducer 返回新状态之后才被调用的但不加条件地 dispatch 会陷入无限循环注销监听的方式是调用subscribe返回的函数。源码中订阅列表使用Mapnumber, ListenerCallback存储并通过ensureCanMutateNextListeners()做浅拷贝快照src/createStore.ts这正是文档所述快照机制的实现细节。此外 store 还暴露了replaceReducer(nextReducer)用于代码分割或热重载与observable()Observable 互操作接口两个高级方法test/createStore.spec.ts 中exposes the public API测试确认了 store 对象恰好暴露subscribe、dispatch、getState、replaceReducer四个公开方法。顺手为 reducer 写测试因为 reducer 是纯函数测试它们非常直接给定示例state与action断言返回值是否符合预期import todosReducer from ./todosSlice test(Toggles a todo based on id, () { const initialState [{ id: 0, text: Test text, completed: false }] const action { type: todos/todoToggled, payload: 0 } const result todosReducer(initialState, action) expect(result[0].completed).toBe(true) })深入 Store 内部25 行迷你实现理解 store 最快的方式是亲手拆开它看看。下面是一个约 25 行的可工作迷你版 Redux storefunction createStore(reducer, preloadedState) { let state preloadedState const listeners [] function getState() { return state } function subscribe(listener) { listeners.push(listener) return function unsubscribe() { const index listeners.indexOf(listener) listeners.splice(index, 1) } } function dispatch(action) { state reducer(state, action) listeners.forEach(listener listener()) } dispatch({ type: redux/INIT }) return { dispatch, subscribe, getState } }这个小实现足以替换你应用中正在使用的真实createStore不妨亲自试试。真实的 src/createStore.ts 实现更长更复杂但多出的部分主要是注释、警告信息、参数校验与边界情况处理核心逻辑与迷你版一一对应store 内部持有当前state值与reducer函数getState返回当前状态值subscribe维护一个监听器数组并返回移除该监听器的函数dispatch调用 reducer、保存新状态、然后执行所有监听器store 启动时会派发一个初始化 action让所有 reducer 返回各自的初始状态store 的公开 API 就是一个{dispatch, subscribe, getState}对象。关于启动时的初始化 action真实源码中它是带随机后缀的redux/INIT见 src/utils/actionTypes.tsINIT: redux/INIT randomString()还有REPLACE与PROBE_UNKNOWN_ACTION两个内部保留类型以保证不会被业务代码意外匹配。getState 不会保护你小心意外突变特别强调一点getState只是把当前的state值原样返回。这意味着默认情况下没有任何机制阻止你意外地突变当前状态下面这段代码不会报错但它是错误的const state store.getState() // ❌ Dont do this - it mutates the current state! state.filters.status Active换句话说store 在getState()时不会复制状态返回的就是根 reducer 返回的那个引用本身store 也不会做任何额外防护来阻止突变——无论是在 reducer 内部还是外部你都可能突变状态必须时刻警惕。一个常见的意外突变来源是数组排序array.sort()会就地修改原数组。如果写成const sortedTodos state.todos.sort()就会无意中改掉 store 里的真实状态。在 Part 8: Modern Redux 中你会看到 Redux Toolkit 如何帮助避免 reducer 内的突变并在 reducer 之外检测并警告意外突变。配置 StoreStore Enhancer除了rootReducer和preloadedStatecreateStore还接受第三个参数用于定制 store 的能力。这个参数就是store enhancer——一种特殊的createStore版本在原始 store 外包一层新逻辑。增强后的 store 可以提供自己的dispatch、getState、subscribe版本来改变 store 行为。本教程不深入 enhancer 的实现细节重点看如何使用。示例项目在src/exampleAddons/enhancers.js中提供了两个小 enhancersayHiOnDispatch每次派发 action 时向控制台打印Hi!includeMeaningOfLife每次调用getState()时向返回的状态里添加字段meaningOfLife: 42。使用单个 Enhancer先使用sayHiOnDispatch导入它并传给createStoreimport { createStore } from redux import rootReducer from ./reducer import { sayHiOnDispatch } from ./exampleAddons/enhancers const store createStore(rootReducer, undefined, sayHiOnDispatch) export default store这里没有preloadedState所以第二个参数传undefined。然后派发一个 actionimport store from ./store console.log(Dispatching action) store.dispatch({ type: todos/todoAdded, payload: Learn about actions }) console.log(Dispatch complete)控制台会看到Hi!出现在两句日志之间原理是sayHiOnDispatch用自己定制的dispatch包装了原始store.dispatch。我们调用的store.dispatch()实际是包装函数它先调用原始 dispatch再打印Hi。用 compose 合并多个 Enhancer现在再加第二个 enhancerincludeMeaningOfLife——但问题来了createStore第三个参数只接受一个 enhancer怎样同时传入两个答案是 Redux 核心自带的compose函数API 见 docs/api/compose.md它能把多个 enhancer 合并成一个import { createStore, compose } from redux import rootReducer from ./reducer import { sayHiOnDispatch, includeMeaningOfLife } from ./exampleAddons/enhancers const composedEnhancer compose(sayHiOnDispatch, includeMeaningOfLife) const store createStore(rootReducer, undefined, composedEnhancer) export default store使用效果import store from ./store store.dispatch({ type: todos/todoAdded, payload: Learn about actions }) // log: Hi! console.log(State after dispatch: , store.getState()) // log: {todos: [...], filters: {status, colors}, meaningOfLife: 42}可以看到两个 enhancer 同时生效sayHiOnDispatch改变了dispatch的行为includeMeaningOfLife改变了getState的行为。几乎所有的 Redux 应用在搭建 store 时都会包含至少一个 enhancer这是非常强大的扩展手段。从 src/compose.ts 源码看compose(...funcs)的实现是funcs.reduce((a, b) (...args) a(b(...args)))——即从右向左组合单参数函数compose(f, g, h)等价于(...args) f(g(h(...args)))传入 0 个函数时返回恒等函数传入 1 个函数时直接返回该函数。这也解释了为什么组合顺序会影响增强效果最右侧的 enhancer 最先执行最靠近原始 store。技巧省略 preloadedState如果你没有任何preloadedState要传可以直接把 enhancer 作为第二个参数const store createStore(rootReducer, storeEnhancer)如前面源码分析所述createStore会自动识别第二个参数是函数的情况并把它当作 enhancersrc/createStore.ts。中间件Middleware定制 dispatch 的首选方式Enhancer 很强大因为它能覆写 store 的任意方法。但大多数时候我们只需要定制dispatch的行为。Redux 用一类特殊的扩展——middleware中间件——来解决这个问题。如果你用过 Express 或 Koa对 middleware 一定不陌生在这些框架里中间件是插在框架收到请求与框架生成响应之间的代码可用来添加 CORS 头、日志、压缩等能力并且可以链式组合多个独立的第三方中间件。Redux 中间件解决的问题不同但概念类似Redux middleware 提供了派发 action与action 到达 reducer之间的第三方扩展点。人们用它做日志、崩溃上报、异步 API 通信、路由等。与 reducer 不同middleware 内部允许有副作用包括 setTimeout 和各种异步逻辑。使用中间件applyMiddlewareRedux 中间件其实是在一个内置的特殊 store enhancer——applyMiddleware之上实现的。既然已经会添加 enhancer现在直接使用它并挂载三个示例中间件来自项目的src/exampleAddons/middlewareimport { createStore, applyMiddleware } from redux import rootReducer from ./reducer import { print1, print2, print3 } from ./exampleAddons/middleware const middlewareEnhancer applyMiddleware(print1, print2, print3) // Pass enhancer as the second arg, since theres no preloadedState const store createStore(rootReducer, middlewareEnhancer) export default store派发一个 action 试试import store from ./store store.dispatch({ type: todos/todoAdded, payload: Learn about actions }) // log: 1 // log: 2 // log: 3控制台输出中间件在 store 的dispatch方法周围形成一条管道。调用store.dispatch(action)时实际调用的是管道中第一个中间件。该中间件可以随意处理 action通常会像 reducer 一样检查 action.type 是否是自己关心的类型不感兴趣就把它传给管道中的下一个中间件。在这个例子里action 依次经过print1中间件对外表现为store.dispatchprint2中间件print3中间件原始的store.dispatchstore 内部的根 reducer由于这些都是函数调用它们从调用栈返回的顺序相反print1最先执行、最后完成。applyMiddleware 的源码实现src/applyMiddleware.ts 的实现非常精炼值得仔细阅读return createStore (reducer, preloadedState) { const store createStore(reducer, preloadedState) let dispatch () { throw new Error(Dispatching while constructing your middleware is not allowed. ...) } const middlewareAPI { getState: store.getState, dispatch: (action, ...args) dispatch(action, ...args) } const chain middlewares.map(middleware middleware(middlewareAPI)) dispatch composetypeof dispatch(...chain)(store.dispatch) return { ...store, dispatch } }要点如下每个中间件都接收{ getState, dispatch }这个middlewareAPI注意这里的dispatch是惰性包装确保中间件构造期间不能 dispatch否则会抛错中间件链通过compose(...chain)从右向左组合最终包裹住原始的store.dispatch返回值是一个以新dispatch覆盖原方法的新 store 对象。这也印证了 API 文档 docs/api/applyMiddleware.md 中的定义中间件的签名是({ getState, dispatch }) next action。编写自定义中间件Redux 中间件是三层嵌套函数。先用 ES5 的function关键字写清楚结构// Middleware written as ES5 functions // Outer function: function exampleMiddleware(storeAPI) { return function wrapDispatch(next) { return function handleAction(action) { // Do anything here: pass the action onwards with next(action), // or restart the pipeline with storeAPI.dispatch(action) // Can also use storeAPI.getState() here return next(action) } } }拆解这三层函数及其参数exampleMiddleware外层这就是中间件本身由applyMiddleware调用接收包含{dispatch, getState}的storeAPI对象与 store 上的dispatch、getState是同一个函数。如果在这里调用storeAPI.dispatch(action)action 会重新从管道起点开始。该函数只被调用一次。wrapDispatch中层接收参数next——即管道中的下一个中间件。如果这是最后一个中间件next就是原始的store.dispatch。调用next(action)把 action 传给下一个中间件。该函数也只被调用一次。handleAction内层接收当前action作为参数每次派发 action 时都会被调用。你可以随意命名但按someCustomMiddleware/wrapDispatch/handleAction来命名有助于记忆各自职责。因为都是普通函数也可以用 ES2015 箭头函数写成更短的隐式返回形式对不熟悉箭头函数的读者可能略难读const anotherExampleMiddleware storeAPI next action { // Do something in here, when each action is dispatched return next(action) }你的第一个自定义中间件logger假设想给应用加日志控制台打印每个被派发的 action以及 reducer 处理完后的新状态const loggerMiddleware storeAPI next action { console.log(dispatching, action) let result next(action) console.log(next state, storeAPI.getState()) return result }每当 action 被派发handleAction前半段先执行打印dispatching把 action 交给next可能是另一个中间件也可能是真实的store.dispatch最终 reducer 运行、状态更新next返回此时调用storeAPI.getState()就能看到新状态最后返回next中间件的result。中间件可以改写 dispatch 的返回值任何中间件都可以返回任意值而管道中第一个中间件的返回值就是store.dispatch()的返回值。例如const alwaysReturnHelloMiddleware storeAPI next action { const originalResult next(action) // Ignore the original result, return something else return Hello! } const middlewareEnhancer applyMiddleware(alwaysReturnHelloMiddleware) const store createStore(rootReducer, middlewareEnhancer) const dispatchResult store.dispatch({ type: some/action }) console.log(dispatchResult) // log: Hello!中间件与异步逻辑中间件常常针对特定 action 做出响应并且可以运行异步逻辑。例如看到todos/todoAdded类型的 action 时延迟 1 秒打印 payloadconst delayedMessageMiddleware storeAPI next action { if (action.type todos/todoAdded) { setTimeout(() { console.log(Added a new todo: , action.payload) }, 1000) } return next(action) }中间件的典型用途看到派发的 action 时中间件几乎可以做任何事向控制台打印日志设置定时器发起异步 API 调用修改 action暂停 action甚至完全拦截它……以及你能想到的任何其他事特别是中间件被设计为承载副作用逻辑同时中间件可以修改dispatch使其接受非纯对象 action例如函数、Promise、Observable。这两点将在 Part 6: Async Logic 中详细展开——届时你会看到redux-thunk正是通过中间件让 dispatch 接受函数thunk的。Redux DevTools可视化调试Redux 的设计初衷之一就是让状态在何时、何地、为何、如何发生变化变得易于理解。为此 Redux 从架构上支持Redux DevTools——一个展示 action 派发历史、action 内容以及每次派发后状态变化的浏览器插件。安装扩展Redux DevTools UI 以浏览器扩展的形式提供Chrome 与 Firefox 均有。安装后打开浏览器开发者工具会看到一个新的 Redux 标签页——不过现在它还不会工作因为 store 还没有与之建立连接。接入 storecomposeWithDevToolsDevTools 需要一个特定的 store enhancer。官方扩展文档中的步骤略显繁琐好在redux-devtools-extension这个 npm 包封装了复杂的部分它导出一个专用的composeWithDevTools函数可替代原生的composeimport { createStore, applyMiddleware } from redux import { composeWithDevTools } from redux-devtools-extension import rootReducer from ./reducer import { print1, print2, print3 } from ./exampleAddons/middleware const composedEnhancer composeWithDevTools( // EXAMPLE: Add whatever middleware you actually want to use here applyMiddleware(print1, print2, print3) // other store enhancers if any ) const store createStore(rootReducer, composedEnhancer) export default store确保index.js在导入 store 后仍会派发 action。然后打开浏览器开发者工具里的 Redux DevTools 标签页你应该能看到类似下面的界面左侧是已派发 action 的列表点击任意一个右侧面板提供多个标签页Action该 action 对象的完整内容Statereducer 运行之后的整个 Redux 状态Diff上一个状态与当前状态之间的差异Trace若启用追溯到最初调用store.dispatch()的代码行的函数调用栈。下面是派发了 add todo action 后 State 与 Diff 标签页的展示效果这些工具能极大地帮助我们调试应用、精确理解 store 内部正在发生的事情。关于中间件与 DevTools 的组合顺序这里有一个值得注意的实践细节见 docs/api/applyMiddleware.md如果同时使用其他 enhancer应把applyMiddleware放在组合链的前面即作为composeWithDevTools的参数先传入因为中间件可能是异步的——否则 DevTools 将看不到由 Promise 类中间件产生的原始 action。小结Store 是每个 Redux 应用的中心枢纽。它持有状态、通过 reducer 处理 action并且可以被扩展以添加额外行为。本部分要点回顾Redux 应用永远只有一个 store通过createStoreAPI 创建新版推荐使用 Redux Toolkit 的configureStore每个 store 对应一个单一的根 reducer 函数store 有三个主要方法getState返回当前状态dispatch把 action 交给 reducer 以更新状态subscribe注册一个每次派发 action 后都会运行的监听回调并返回用于注销的函数Store enhancer 让我们在创建时定制 storeEnhancer 包装 store 并可以覆写其方法createStore只接受一个 enhancer 参数多个 enhancer 可用compose合并Middleware 是定制 store 的主要方式通过applyMiddlewareenhancer 添加以三层嵌套函数的形式编写storeAPI next action {...}每次派发 action 时都会运行内部允许副作用定时器、异步 API 等Redux DevTools 让你看到应用随时间的变化浏览器安装 DevTools 扩展通过composeWithDevTools为 store 添加 DevTools enhancer可以查看派发的 action 与状态随时间的演变含 Diff 与 Trace下一步现在我们已经有了一个可以运行 reducer、并在派发 action 时更新状态的 store。不过任何应用都需要用户界面来展示数据、让用户执行有意义的操作。在 Part 5: UI 与 React 中我们将看到 Redux store 如何与 UI 协作尤其是 Redux 如何与 React 一起工作。【免费下载链接】reduxA JS library for predictable global state management项目地址: https://gitcode.com/gh_mirrors/re/redux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价