前端【免费下载链接】react-reduxOfficial React bindings for Redux项目地址https://gitcode.com/gh_mirrors/re/react-redux点击查看免费下载本篇基础教程以 React Redux v6.x 为背景通过构建一个完整的 Todo List待办事项应用逐步演示 React Redux 的两大核心 API——Provider /与connect()——的真实用法如何把 Redux store 提供给整个组件树如何用mapStateToProps从 store 中读取数据、用mapDispatchToProps向 store 派发 action以及connect的四种常见调用形态各自的行为差异。读完本文你将具备独立搭建一个“Redux store React Redux 绑定 容器组件”完整链路的能力并能理解其底层实现原理。目标应用一个 Todo List 应用为了演示 React Redux 的实际使用方式我们以经典的 Todo List 应用为例一步步把它搭起来。应用支持新增待办、切换待办完成状态、按all/completed/incomplete三种过滤器查看列表。React UI 组件结构应用由以下几个 UI 组件构成TodoApp应用的入口组件负责渲染页头、AddTodo、TodoList和VisibilityFilters。AddTodo允许用户输入待办内容并在点击 “Add Todo” 按钮时把它加入列表使用受控输入框在onChange时更新本地 state点击按钮时派发dispatch由 React Redux 注入的 action把待办写入 store。TodoList负责渲染待办列表当选中某个VisibilityFilters过滤器时渲染过滤后的待办列表。Todo渲染单条待办渲染待办内容已完成completed的待办用删除线标示点击时派发切换完成状态的 action。VisibilityFilters渲染一组过滤器all、completed、incomplete点击切换过滤条件通过父组件传入的activeFilterprop 得知当前选中的过滤器选中的过滤器以下划线样式突出显示派发setFilteraction 更新当前过滤器。constants存放应用的常量数据。index把整个应用渲染到 DOM 中。Redux Store 设计应用的红部分Redux 部分按照 Redux 官方文档推荐的模式组织Store 结构todos归一化normalized的待办 reducer包含一个byIds映射存所有待办和一个allIds数组存所有待办的 id 顺序。visibilityFilters一个简单字符串值为all、completed或incomplete。Action CreatorsaddTodo(content)接收一个字符串content返回ADD_TODOaction其payload包含自增的id与content。toggleTodo(id)接收一个数字id返回TOGGLE_TODOaction其payload只包含id。setFilter(filter)接收一个字符串filter返回SET_FILTERaction其payload就是filter本身。Reducerstodosreducer收到ADD_TODO时把id追加到allIds并在byIds中写入该待办收到TOGGLE_TODO时切换对应待办的completed字段。visibilityFiltersreducer收到SET_FILTER时把自身的 store 切片更新为 payload 中的新过滤器。Action Types用一个actionTypes.js文件集中存放所有 action type 常量以便复用。SelectorsgetTodoList从todos中返回allIds列表。getTodoById根据id在 store 中找到对应待办。getTodos稍复杂它取出allIds中的每个id在byIds中找到对应待办返回最终的待办数组。getTodosByVisibilityFilter根据当前的可见性过滤器筛选待办。UI 组件与未连接的 Redux store 准备就绪后接下来就是用 React Redux 把 store 与组件连接起来。提供 Store用Provider /包裹应用首先需要让store在整个应用中可用。做法是用 React Redux 提供的Provider /API 包裹应用根组件// index.js import React from react import ReactDOM from react-dom import TodoApp from ./TodoApp import { Provider } from react-redux import store from ./redux/store const rootElement document.getElementById(root) ReactDOM.render( Provider store{store} TodoApp / /Provider, rootElement, )注意这里的TodoApp /被Provider /包裹且store作为 prop 传入。Provider 的源码实现从源码看Provider /本质上是一个 Context Provider。在 src/components/Provider.tsx 中Provider接收store、children、context自定义 Context、serverState等 props并通过React.useMemo构造一个contextValueconst contextValue React.useMemo(() { const subscription createSubscription(store) const baseContextValue { store, subscription, getServerState: serverState ? () serverState : undefined, } // 开发模式下还会附加 stabilityCheck / identityFunctionCheck 等配置 ... }, [store, serverState]) const Context context || ReactReduxContext return Context.Provider value{contextValue}{children}/Context.Provider关键点contextValue中不仅包含store还包含一个subscription订阅对象见 src/utils/Subscription.ts。Provider挂载时通过useIsomorphicLayoutEffect执行subscription.trySubscribe()并监听 store 变化在 store 状态与首屏渲染不一致时触发notifyNestedSubs()通知嵌套订阅者从而驱动被connect包裹的组件更新。默认情况下 React Redux 使用自己内部创建的ReactReduxContext见 src/components/Context.ts。你不需要提供自定义 Context 就能访问 storeReact Redux 也把该 Context 实例导出方便在组件中直接读取import { ReactReduxContext } from react-redux // 在已连接的组件中 render() { return ( ReactReduxContext.Consumer {({ store }) { // 在这里使用 store }} /ReactReduxContext.Consumer ) }连接组件认识connect()React Redux 提供connect函数用于从 Redux store 中读取值并在 store 更新时重新读取。connect接收两个参数均可选mapStateToProps每次 store 状态变化时被调用。它接收整个 store 状态返回该组件所需数据的对象。mapDispatchToProps可以是函数也可以是对象。如果是函数它将在组件创建时被调用一次接收dispatch作为参数并返回一个由使用dispatch派发 action 的函数组成的对象。如果是包含 action creators 的对象每个 action creator 都会被转换为一个 prop 函数调用时自动派发对应 action。注意官方推荐使用这种“对象简写形式”object shorthand form。通常connect是这样调用的const mapStateToProps (state, ownProps) ({ // ... 从 state以及可选的 ownProps计算出的数据 }) const mapDispatchToProps { // ... 通常是 action creators 组成的对象 } // connect 返回一个新函数该函数接收要包裹的组件 const connectToStore connect(mapStateToProps, mapDispatchToProps) // 该函数返回连接后的包装组件 const ConnectedComponent connectToStore(Component) // 通常一步完成 connect(mapStateToProps, mapDispatchToProps)(Component)connect不修改传入的组件类而是返回一个新的、包裹传入组件的“已连接组件”connected component。从 src/components/connect.tsx 的源码可以看到connect内部构造了一个名为Connect(${wrappedComponentName})的包装组件例如Connect(AddTodo)它通过React.useContext(ContextToUse)获取 store 与祖先订阅再通过React.useSyncExternalStore订阅 store 更新并把计算出的 props 传给被包裹组件。实战一连接AddTodo让组件能够派发 actionAddTodo /需要触发 store 的变化来新增待办因此它需要能够向 storedispatchaction。先看addTodoaction creator// redux/actions.js import { ADD_TODO } from ./actionTypes let nextTodoId 0 export const addTodo (content) ({ type: ADD_TODO, payload: { id: nextTodoId, content, }, }) // ... 其他 action把它传给connect后组件会以 prop 的形式收到它调用时即自动派发 action// components/AddTodo.js // ... 其他 import import { connect } from react-redux import { addTodo } from ../redux/actions class AddTodo extends React.Component { // ... 组件实现 } export default connect(null, { addTodo })(AddTodo)注意AddTodo /现在被一个叫Connect(AddTodo) /的父组件包裹同时AddTodo /多了一个 propaddTodoaction。再实现handleAddTodo函数让它派发addTodoaction 并清空输入框// components/AddTodo.js import React from react import { connect } from react-redux import { addTodo } from ../redux/actions class AddTodo extends React.Component { // ... handleAddTodo () { // 派发添加待办的 action this.props.addTodo(this.state.input) // 把 state 重置为空字符串 this.setState({ input: }) } render() { return ( div input onChange{(e) this.updateInput(e.target.value)} value{this.state.input} / button classNameadd-todo onClick{this.handleAddTodo} Add Todo /button /div ) } } export default connect(null, { addTodo })(AddTodo)现在AddTodo /已经连接到 store添加待办时会派发 action 改变 store。此时界面上还看不到效果因为其他组件尚未连接。如果装有 Redux DevTools 扩展可以观察到 action 被派发、store 随之更新的过程。对象简写形式在源码中如何工作connect(null, { addTodo })(AddTodo)使用的是对象简写形式。在 src/connect/mapDispatchToProps.ts 中mapDispatchToPropsFactory检测到mapDispatchToProps是对象时会走wrapMapToPropsConstant分支调用bindActionCreators(mapDispatchToProps, dispatch)return mapDispatchToProps typeof mapDispatchToProps object ? wrapMapToPropsConstant((dispatch) bindActionCreators(mapDispatchToProps, dispatch), ) : ...而 src/utils/bindActionCreators.ts 的实现就是为每个 action creator 生成一个“调用即 dispatch”的包装函数boundActionCreators[key] (...args) dispatch(actionCreator(...args))如果mapDispatchToProps是函数则走wrapMapToPropsFunc分支见 src/connect/wrapMapToProps.ts在组件创建时调用一次并把返回值作为真正的mapDispatchToProps用于后续调用。实战二连接TodoList用mapStateToProps读取数据TodoList /负责渲染待办列表因此需要从 store 读取数据。我们通过给connect传mapStateToProps参数来实现——它是一个描述“需要 store 中哪部分数据”的函数。Todo /组件把单条待办作为 props。这些数据来自todos的byIds字段但我们还需要allIds字段来得知要渲染哪些待办、以什么顺序渲染。mapStateToProps可以这样写// components/TodoList.js // ... 其他 import import { connect } from react-redux; const TodoList // ... UI 组件实现 const mapStateToProps state { const { byIds, allIds } state.todos || {}; const todos allIds allIds.length ? allIds.map(id (byIds ? { ...byIds[id], id } : null)) : null; return { todos }; }; export default connect(mapStateToProps)(TodoList);恰好我们已经有一个 selector 做了同样的事直接引入并复用即可// redux/selectors.js export const getTodosState (store) store.todos export const getTodoList (store) getTodosState(store) ? getTodosState(store).allIds : [] export const getTodoById (store, id) getTodosState(store) ? { ...getTodosState(store).byIds[id], id } : {} export const getTodos (store) getTodoList(store).map((id) getTodoById(store, id))// components/TodoList.js // ... 其他 import import { connect } from react-redux; import { getTodos } from ../redux/selectors; const TodoList // ... UI 组件实现 export default connect(state ({ todos: getTodos(state) }))(TodoList);官方建议把复杂的查找或数据计算封装进 selector 函数。此外还可以引入 Reselect 编写“记忆化memoized”的 selector跳过不必要的工作以优化性能详见 Redux 官方文档中关于计算派生数据Computing Derived Data的章节以及《Idiomatic Redux: Using Reselect Selectors for Encapsulation and Performance》一文对“为何以及如何使用 selector 函数”的说明。现在TodoList /已连接到 store它会收到待办列表、遍历它们并把每条待办传给Todo /组件渲染到屏幕上。试着添加一条待办它应该立刻出现在列表中。mapStateToProps 在源码中如何被调用从 src/connect/mapStateToProps.ts 可以看到mapStateToPropsFactory只接受函数或空值为空时返回() ({})常量选择器为函数时用wrapMapToPropsFunc包装。包装器见 src/connect/wrapMapToProps.ts会通过getDependsOnOwnProps根据函数声明的参数个数判断它是否依赖ownProps函数形参长度为 1 时视为不依赖从而决定后续调用是否传入ownProps。在 src/connect/selectorFactory.ts 的pureFinalPropsSelectorFactory中mapStateToProps的调用时机被精细调度首次调用执行handleFirstCall同时计算stateProps、dispatchProps并合并出mergedProps之后每次 store 或 props 变化先通过areOwnPropsEqual、areStatesEqual判断是哪一类变化仅 props 变化 / 仅 state 变化 / 两者都变再决定是否重新调用mapStateToProps、mapDispatchToProps从而避免无谓的计算与渲染。连接更多组件前connect的四种常见调用方式根据组件类型的不同connect有以下四种最常用的调用方式不订阅 store订阅 store不注入 action creatorsconnect()(Component)connect(mapStateToProps)(Component)注入 action creatorsconnect(null, mapDispatchToProps)(Component)connect(mapStateToProps, mapDispatchToProps)(Component)方式一既不订阅 store也不注入 action creators直接调用connect()而不传任何参数组件将在 store 变化时不重新渲染收到props.dispatch可手动派发 action。// ... Component export default connect()(Component) // 组件会收到 dispatch就像我们的 TodoList / 一样方式二订阅 store不注入 action creators只传mapStateToProps组件将订阅mapStateToProps从 store 中提取的值仅当这些值变化时才重新渲染收到props.dispatch可手动派发 action。// ... Component const mapStateToProps (state) state.partOfState export default connect(mapStateToProps)(Component)方式三不订阅 store注入 action creators只传mapDispatchToProps组件将在 store 变化时不重新渲染把注入的每个 action creator 作为 props 收到调用时自动派发对应 action。import { addTodo } from ./actionCreators // ... Component export default connect(null, { addTodo })(Component)方式四订阅 store注入 action creators同时传mapStateToProps和mapDispatchToProps组件将订阅mapStateToProps提取的值仅当这些值变化时才重新渲染把注入的所有 action creators 作为 props 收到调用时自动派发对应 action。import * as actionCreators from ./actionCreators // ... Component const mapStateToProps (state) state.partOfState export default connect(mapStateToProps, actionCreators)(Component)这四种情况覆盖了connect最基本的用法。更完整的参数说明mergeProps、options、工厂函数、mapToProps函数形参个数的影响等请继续阅读 API 文档 connect()。实战三连接Todo实现完成状态切换如何实现待办完成状态的切换细心的读者可能已经有了答案与连接AddTodo的方式类似把Todo /连接到toggleTodoaction 即可// components/Todo.js // ... 其他 import import { connect } from react-redux; import { toggleTodo } from ../redux/actions; const Todo // ... 组件实现 export default connect( null, { toggleTodo } )(Todo);现在待办可以切换完成状态了。如果已按前文把环境搭好并跟随操作到这里建议先放下教程亲自动手实现这一功能——结果正如你所料。实战四连接VisibilityFilters读取并更新过滤器最后实现VisibilityFilters功能。VisibilityFilters /需要从 store 读取当前激活的过滤器并向 store 派发 action 更新它。因此需要同时传mapStateToProps和mapDispatchToProps。这里的mapStateToProps可以是对visibilityFilter状态的简单访问mapDispatchToProps则包含setFilteraction creator// components/VisibilityFilters.js // ... 其他 import import { connect } from react-redux; import { setFilter } from ../redux/actions; const VisibilityFilters // ... 组件实现 const mapStateToProps state { return { activeFilter: state.visibilityFilter }; }; export default connect( mapStateToProps, { setFilter } )(VisibilityFilters);同时还要更新TodoList /让它按当前激活的过滤器筛选待办。之前传给TodoList /的mapStateToProps只是选取整个待办列表的 selector。我们再写一个按状态筛选待办的 selector// redux/selectors.js // ... 其他 selector export const getTodosByVisibilityFilter (store, visibilityFilter) { const allTodos getTodos(store) switch (visibilityFilter) { case VISIBILITY_FILTERS.COMPLETED: return allTodos.filter((todo) todo.completed) case VISIBILITY_FILTERS.INCOMPLETE: return allTodos.filter((todo) !todo.completed) case VISIBILITY_FILTERS.ALL: default: return allTodos } }借助该 selector 完成连接// components/TodoList.js // ... const mapStateToProps (state) { const { visibilityFilter } state const todos getTodosByVisibilityFilter(state, visibilityFilter) return { todos } } export default connect(mapStateToProps)(TodoList)至此一个使用 React Redux 的简单 Todo 应用就完成了所有组件都已连接连接组件的内部机制一览为了更透彻地理解connect做了什么可以从源码层面梳理一次“store 更新 → 组件重渲染”的完整链路获取 storeConnectFunction通过React.useContext(ContextToUse)从Provider /注入的 Context 中取到store与祖先subscriptionsrc/components/connect.tsx。若既不在 Context 中、也没有通过 props 传入 store开发环境下会抛出Could not find store in the context of ...错误。构造子 props 选择器defaultSelectorFactory(store.dispatch, selectorFactoryOptions)把mapStateToProps、mapDispatchToProps、mergeProps组合成一个统一的 props 选择器src/connect/selectorFactory.ts其默认实现pureFinalPropsSelectorFactory会记忆化合并结果只有state或ownProps发生变化时才重新计算。订阅更新通过createSubscription(store, contextValue.subscription)创建订阅src/utils/Subscription.ts并把checkForUpdates注册为onStateChange。store 更新时checkForUpdates用最新 state 运行 props 选择器若新子 props 与上一次不同则通过React.useSyncExternalStore的订阅回调触发组件重渲染若相同则仅向下游嵌套的已连接组件传播订阅通知。渲染最终把计算出的actualChildProps展开传给被包裹组件若shouldHandleStateChanges为 true还会把自己的subscription放进 Context保证“父连接组件更新完成之后子连接组件才更新”的自上而下订阅顺序。这个机制解释了connect的几种调用方式为何行为不同mapStateToProps是否传入对应shouldHandleStateChanges决定了组件是否订阅 storemapDispatchToProps的形式函数或对象决定了 props 中注入的是手写的 dispatch 包装还是bindActionCreators的产物。结语与延伸阅读通过这个 Todo List 示例你已经掌握了 React Redux 的核心使用模式用Provider store{...}让 store 全局可用用connect(mapStateToProps, mapDispatchToProps)(Component)让组件既能读取 store 数据、又能派发 action并通过 selector 封装派生数据、通过四种connect调用形态控制组件的订阅与注入行为。深入理解Provider的完整 propsstore、context、serverState等可阅读 Provider APIconnect的完整参数说明mergeProps、options中的各比较函数、工厂函数、mapToProps形参个数的影响见 connect() API关于mapStateToProps的推荐用法返回值如何决定是否重渲染等见 使用 mapStateToProps 提取数据 与 使用 mapDispatchToProps 派发 action想了解connect的底层实现细节可直接阅读源码 src/components/connect.tsx、src/components/Provider.tsx、src/connect/selectorFactory.ts 以及测试用例 test/components/connect.spec.tsx、test/components/Provider.spec.tsx。若在开发中遇到问题可以前往 Reactiflux 的 Redux 频道、StackOverflow 的react-redux标签提问或在仓库的 GitHub Issues 中反馈。赞分享前端【免费下载链接】react-reduxOfficial React bindings for Redux项目地址https://gitcode.com/gh_mirrors/re/react-redux点击查看免费下载相关推荐React-Redux 基础实战教程用 connect 与 Provider 构建一个 Todo 应用React Redux 基础实战教程用 connect 与 Provider 构建一个 Todo 应用 本教程以 React ReduxRedux 官方 R前端React Redux 基础实战教程从零构建 Todo 应用并理解 Provider 与 connect 的完整工作原理React Redux 基础实战教程从零构建 Todo 应用并理解 Provider 与 connect 的完整工作原理 本文是 React Redux v7前端React-Redux基础教程构建Todo应用实战指南React Redux基础教程构建Todo应用实战指南 前言 在现代前端开发中状态管理是构建复杂应用的关键环节。React Redux作为React与Red前端上一篇Chunky终极指南如何快速预生成Minecraft区块提升游戏性能下一篇ripgrep ignore crate 深度解析构建尊重 .gitignore 的高速递归目录迭代器创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考