TodoMVC React-Redux 示例深度解析:Redux 单向数据流与 Webpack 工程化实践
【免费下载链接】todomvcHelping you select a JavaScript framework - Todo apps for React.js, Angular, Vue and many more项目地址: https://gitcode.com/gh_mirrors/to/todomvc
本文基于 TodoMVC 仓库中的 examples/react-redux/README.md 展开,系统讲解该示例如何用 React + Redux + React-Redux 三件套实现标准 TodoMVC 应用,并结合 store 定义、组件实现 与 Webpack 构建配置 还原其数据流、组件连接关系与构建流程,读完可掌握 Redux Toolkit 的 slice/selectors 组织方式与 TodoMVC 官方应用的工程化构建链路。
应用定位:React、Redux 与 React-Redux 的分工
官方 README 对该示例的定位是:这是一个使用 React 结合 Redux 实现的 TodoMVC 应用,三个核心依赖各有明确职责:
- React:用于创建用户界面的 JavaScript 库;
- Redux:负责集中管理应用状态(centralize state);
- React-Redux:让 Redux 与 React 的组件模型协作,把 store 接入组件树。
从 examples/react-redux/package.json 可以看到当前仓库锁定的实际依赖版本:react与react-dom为 19.x,react-redux为 9.x,状态管理基于@reduxjs/toolkit2.x,并引入了react-router-dom7.x 用于过滤路由,todomvc-app-css与todomvc-common提供官方统一的界面样式与测试工具。
数据流设计:Redux 视角下的 MVC 映射
README 的“Implementation details”部分给出了这段实现的核心设计哲学:Redux 模式类似于 MVC 模式,主要区别在于 Redux 是单向(unidirectional)数据流。其完整流程为:
- Redux 用actions发出一次变更请求;
- reducers捕获 action 并更新中央 store;
- store 中的状态更新后,View收到新状态并向用户呈现变化。
README 进一步给出了 Redux 与经典 MVC 的逐项对应关系,这是理解该示例代码结构的关键:
| 角色 | Redux 实现 | MVC 中的角色 |
|---|---|---|
| Model | Redux store | 维护应用的数据与行为 |
| View | React UI 组件 | 在界面中展示模型 |
| Controller | React connected components + Redux reducers | 作为视图与模型之间的接口 |
Store 层:用 createSlice 集中定义状态与动作
按上述映射,"Model" 的全部实现集中在 examples/react-redux/src/store.js。该文件用 Redux Toolkit 的createSlice定义了一个名为todos的 slice,初始状态是空数组[],并包含 6 个 reducer(源码 L3-L36):
| Action | 行为 | 源码细节 |
|---|---|---|
addTodo | 向数组尾部追加新 todo | 使用prepare回调在派发动作时生成{ id: nanoid(), text, completed: false }载荷(L7-L14),用nanoid生成唯一 id |
deleteTodo | 按 id 过滤删除 | state.filter((todo) => todo.id !== action.payload) |
editTodo | 更新指定 todo 的文本 | 先find找到目标再改写text |
toggleTodo | 翻转单个 todo 的完成状态 | todo.completed = !todo.completed |
toggleAll | 全部完成则全部取消,否则全部完成 | 通过state.every((todo) => todo.completed)判断当前是否"已全部完成"再批量翻转 |
clearCompleted | 清除所有已完成的 todo | state.filter((todo) => !todo.completed) |
这些 action 在 L38 被统一解构导出,供各组件dispatch调用。store 本身由configureStore创建,todos作为唯一顶层 reducer(L40-L42),印证了 README 所说的"中央 store"设计。
派生状态:过滤器与 selectors
README 强调状态更新后视图能反映变化,TodoMVC 的过滤(All / Active / Completed)正是派生状态的典型场景。store.js 中没有把过滤结果写入 state,而是用 selector 即时计算(L44-L55):
FILTERS是路径到过滤函数的映射:"/"与"/all"返回全部、"/active"只留未完成项、"/completed"只留已完成项;selectVisibleTodos(pathname)返回一个接收state的 selector,根据当前路由路径应用对应过滤函数;selectCompletedCount统计已完成数量,selectTodos直接读取state.todos。
这种"原始状态 + 派生 selector"的组织方式,使得过滤逻辑完全由路由路径驱动,组件侧无需维护额外的 UI 状态。
组件层:Provider 挂载与 connected components
入口:把 store 注入组件树
examples/react-redux/src/index.js 是应用入口(L11-L17):
createRoot(document.getElementById("root")).render( <Provider store={store}> <HashRouter> <App /> </HashRouter> </Provider> );这里体现了两个关键决策:
Provider将 store 注入整棵组件树,任何子组件都可通过useDispatch/useSelector访问状态——这就是 README 中"React-Redux 与 React 组件模型协作"的具体落点;HashRouter使用哈希路由(#/、#/active、#/completed),与 public/index.html 中静态页面的定位方式一致,无需服务器端路由配置。
入口同时引入todomvc-app-css/index.css与todomvc-common/base.css,保证界面遵循 TodoMVC 官方视觉规范。
App 与页面骨架
app.jsx 结构极简,仅由<Header />与<Main />两个区块组成,对应 TodoMVC 规范的输入区与主体区;<section class="todoapp" id="root">作为挂载点声明在 public/index.html 中,并带有data-framework="react-redux"标记。
Header:派发动作的起点
header.jsx 展示了"发出 action"一侧的典型写法:通过useDispatch()获取 dispatch 函数,在输入框保存回调里调用dispatch(addTodo(text)),并对空文本做拦截。数据流由此启动:组件 → action → reducer → store。
Main:消费状态的 connected component
main.jsx 是"View 接收新状态"的典型代表,它同时消费状态(useSelector)与发出动作(useDispatch):
useSelector(selectTodos)取全量列表用于计算activeCount与空状态判断;useSelector(selectVisibleTodos(location.pathname))配合useLocation(),让可见列表随路由路径实时切换——过滤器链接点击后 URL 变化,selector 重算,列表随之更新,这正是 README 所述"store 更新 → view 呈现变化"闭环的过滤场景实现;- "Toggle All" 复选框的
checked由completedCount === todos.length派生,onChange派发toggleAll()。
Item 与 TextInput:局部状态与 Redux 状态的边界
item.jsx 展示了状态划分的细节:"是否处于编辑态"属于组件私有状态(useState管理editing),而 todo 本身的数据变更全部走 Redux(toggleTodo/editTodo/deleteTodo)。编辑保存时的规则值得注意:文本为空则dispatch(deleteTodo(todo.id)),否则dispatch(editTodo({ id, text }))。此外该组件用memo包裹,配合不可变更新减少列表项重渲染。
footer.jsx 消费父组件传入的计数 props 与location.pathname,渲染剩余项数、三个过滤链接(#/、#/active、#/completed)以及"Clear completed"按钮(派发clearCompleted())。
text-input.jsx 是唯一不直接接触 Redux 的组件,它通过onSave/onCancel回调把职责交给调用方,处理 Enter 提交、Escape 取消(编辑态)与失焦保存等行为,是一个纯 UI 的可复用控件。
存储方案:纯内存数组
README 明确指出:"存储方案使用一个内存数据对象,实现一个简单数组来保存 todos。" 源码完全印证了这一点:
- store.js 的
initialState: []就是唯一的初始状态,没有任何 localStorage 读写或网络同步逻辑; - 每次 reducer 执行后数组被替换或改写,状态只存在于 store 生命周期内,刷新页面即清空。
选择纯内存存储是刻意的取舍——TodoMVC 示例的核心目标是演示框架的状态管理模式,而非持久化方案。若要在真实项目中扩展,可在此基础上接入持久化中间件或 Toolkit 的configureStore序列化能力,但当前仓库实现中并不包含这部分。
构建流程:Webpack 三段式配置
README 的"Build steps"说明:构建静态文件使用 webpack,它会压缩并优化输出文件,并把所有必要文件拷贝到dist目录。仓库中这由三个配置文件协作完成:
公共配置:入口、模板与转译
webpack.common.js 定义了基础链路:
- 入口:
src/index.js(L5-L7); - HTML 模板:
HtmlWebpackPlugin以public/index.html为模板生成最终页面(L9-L12); - 输出:
dist/[name].bundle.js,并开启clean: true保证每次构建前清空输出目录(L14-L18); - 转译:
babel-loader处理js/jsx文件,配置@babel/preset-env(targets: defaults)与@babel/preset-react(runtime: "automatic",即无需手动import React的新 JSX 运行时)(L24-L36); - 静态资源:图片类走
asset/resource类型处理(L38-L40)。
开发配置:热更新与内联源码映射
webpack.dev.js 通过webpack-merge合并公共配置后追加:
mode: "development"、devtool: "inline-source-map",便于调试;- CSS 走
style-loader+css-loader(以 JS 注入样式,支持热更新); devServer.static: "./dist"指定静态服务目录。
生产配置:压缩、抽离与拷贝
webpack.prod.js 对应 README 中"minifies and optimizes"的描述:
MiniCssExtractPlugin把 CSS 从 JS 中抽离为独立的[name].css文件;CopyPlugin将node_modules/todomvc-common/base.js拷贝到输出根目录(L17-L21)——这正是 public/index.html 中<script src="./base.js">的来源,用于 TodoMVC 测试工具识别当前框架;optimization.minimize开启压缩,minimizer 为CssMinimizerPlugin与TerserPlugin,分别负责 CSS 与 JS 的压缩;devtool: "source-map"生成独立映射文件。
运行要求与本地预览
环境要求
README 给出的最低环境要求为:
Node (min version: 18.13.0) NPM (min version: 8.19.3)唯一前提是安装 Node,以便安装依赖并运行脚本启动本地服务器。需要补充一点:package.json 的engines字段当前声明为node >=20.19.0、npm >=10.0.0(L6-L9),比 README 写的下限更严格;按 npm 的 engines 校验行为,建议在 Node 20+ 环境下运行,以避免与锁定依赖(如 React 19、Webpack 5.106)出现兼容性问题。
npm scripts 一览
package.json 定义了三个脚本(L10-L14):
| 脚本 | 命令 | 作用 |
|---|---|---|
npm run build | webpack --config webpack.prod.js | 走生产配置,产出压缩后的dist静态文件 |
npm run dev | webpack serve --open --config webpack.dev.js | 启动 webpack-dev-server 并自动打开浏览器 |
npm run serve | http-server ./dist -p 7002 -c-1 --cors | 用 http-server 以 7002 端口、禁用缓存(-c-1)、开启 CORS 的方式静态托管构建产物 |
本地预览步骤
README 给出的标准流程:
terminal: 1. npm install 2. npm run dev browser: 1. http://localhost:7001/即在examples/react-redux目录下安装依赖后启动开发服务器,浏览器访问本地地址即可查看应用。其中dev脚本使用webpack serve --open会自动打开页面;注意 webpack.dev.js 的devServer配置只显式指定了static: "./dist"目录、未显式写死端口,实际访问端口以启动时 dev-server 控制台输出或项目约定为准,README 记录的入口地址为http://localhost:7001/。构建后如需静态托管,可运行npm run serve直接服务dist目录。
小结
这个示例完整地演示了 README 所述的设计原则:Redux 单向数据流替代了传统 MVC 的双向耦合——store.js 以 slice + selectors 承担 Model,header.jsx、main.jsx、item.jsx、footer.jsx 作为 connected components 承担 Controller 与 View 的混合职责;存储采用纯内存数组,刻意剥离持久化复杂度;构建侧则用 webpack 三段式配置(common/dev/prod)实现了开发期热更新与生产期压缩抽离的分离。对学习者而言,它可以作为理解 Redux Toolkit slice 组织、selector 派生状态、React-Redux hooks 连接方式以及 TodoMVC 规范 UI 结构的一个小而完整的参照实现。
【免费下载链接】todomvcHelping you select a JavaScript framework - Todo apps for React.js, Angular, Vue and many more项目地址: https://gitcode.com/gh_mirrors/to/todomvc
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考