资讯动态

React-Redux `connect()` 完全指南:API 参数详解与源码级原理

发布时间:2026/9/20 23:32:35 来源:尧图企业网站定制
React-Reduxconnect()完全指南API 参数详解与源码级原理【免费下载链接】react-reduxOfficial React bindings for Redux项目地址: https://gitcode.com/gh_mirrors/re/react-reduxconnect()是 React-Redux 中用于将 React 组件连接到 Redux store 的高阶组件Higher-Order ComponentHOC。本指南以 React-Redux 7.0 的官方connectAPI 文档为主体逐项拆解mapStateToProps、mapDispatchToProps、mergeProps与options四个可选参数的语义、默认值与典型用法并结合本仓库 src/components/connect.tsx 与 src/connect 目录下的实际实现从源码层面解释 arity 检测、记忆化 selector、bindActionCreators绑定、订阅与重渲染判定等底层机制。读完本文你将能熟练写出高性能、行为可控的connect()连接组件并理解其性能优化的每一道关卡。Overviewconnect()是什么connect()函数将一个 React 组件连接到 Redux store它向被连接的组件提供它所需的 store 数据片段以及用于向 store dispatch action 的函数它不会修改传入的组件类而是返回一个新的、已连接的组件类用于包裹你传入的组件。function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)其中mapStateToProps处理 Redux store 的statemapDispatchToProps处理dispatch。state与dispatch会分别作为第一个参数传入对应的函数。mapStateToProps与mapDispatchToProps的返回值在内部被称为stateProps与dispatchProps。如果定义了mergeProps它们会作为第一、第二个参数传入第三个参数是ownProps合并后的结果统称为mergedProps最终注入到被连接的组件中。在 React-Redux 7.0 中connect的签名定义可以在 src/components/connect.tsx 中看到函数接收四个可选参数并通过mapStateToPropsFactory、mapDispatchToPropsFactory、mergePropsFactory三个工厂完成参数预处理见 src/components/connect.tsx。connect()参数总览connect接受四个参数全部可选按惯例命名如下参数类型作用mapStateToPropsFunction从 store state 中提取数据注入 propsmapDispatchToPropsFunction \| Object将 action creator 绑定到dispatch后注入 propsmergePropsFunction自定义stateProps、dispatchProps、ownProps的合并规则optionsObject自定义 context、相等性比较与 ref 转发行为mapStateToProps?: (state, ownProps?) Object如果指定了mapStateToProps函数新的包装组件会订阅 Redux store 更新。这意味着每次 store 更新时mapStateToProps都会被调用。它的返回值必须是普通对象plain object该对象会被合并进被包裹组件的 props。如果不想订阅 store 更新请在mapStateToProps位置传入null或undefined。参数mapStateToProps最多接受两个参数state: ObjectownProps?: Object函数声明参数的数量即 arity会影响它的调用时机也决定了它是否会接收到ownProps。state如果mapStateToProps声明为只接收一个参数那么每当 store 状态变化时它都会被调用且只接收 store stateconst mapStateToProps (state) ({ todos: state.todos })ownProps如果mapStateToProps声明为接收两个参数它会在 store 状态变化或包装组件收到新 props 时基于浅比较判断被调用。第一个参数是 store state第二个参数是包装组件的 propsconst mapStateToProps (state, ownProps) ({ todo: state.todos[ownProps.id], })返回值mapStateToProps应返回一个对象即stateProps它会作为 props 合并到被连接组件如果定义了mergeProps则作为第一个参数传入mergeProps。mapStateToProps的返回值决定被连接组件是否会重渲染详细讨论见 docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md。更多推荐用法参见 docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md。你可以将mapStateToProps定义为工厂函数返回函数而非对象。此时返回的函数会被当作真正的mapStateToProps在后续调用中使用详见下文 Factory Functions。源码视角mapStateToProps的订阅行为从源码看是否订阅 store 由shouldHandleStateChanges Boolean(mapStateToProps)决定见 src/components/connect.tsx。当mapStateToProps为null/undefined时shouldHandleStateChanges为false连接组件不会订阅 store也就不会因 store 更新而重渲染——这正是connect()(TodoApp)只注入dispatch、不监听 store 的底层原因。mapDispatchToProps?: Object | (dispatch, ownProps?) Object第二个参数可以是对象、函数或者完全不传。默认情况下不传第二个参数组件会收到dispatch// do not pass mapDispatchToProps connect()(MyComponent) connect(mapState)(MyComponent) connect(mapState, null, mergeProps, options)(MyComponent)如果以函数形式定义mapDispatchToProps它最多接收两个参数。参数dispatch: FunctionownProps?: Objectdispatch如果mapDispatchToProps声明为单参数函数它会收到 store 的dispatchconst mapDispatchToProps (dispatch) { return { // dispatching plain actions increment: () dispatch({ type: INCREMENT }), decrement: () dispatch({ type: DECREMENT }), reset: () dispatch({ type: RESET }), } }ownProps如果声明为双参数函数第一个参数是dispatch第二个参数是传给包装组件的 props并且每当连接组件收到新 props 时都会重新调用// binds on component re-rendering ;button onClick{() this.props.toggleTodo(this.props.todoId)} / // binds on props change const mapDispatchToProps (dispatch, ownProps) { toggleTodo: () dispatch(toggleTodo(ownProps.todoId)) }mapDispatchToProps声明参数的数量决定它是否接收ownProps详见 The Arity of mapToProps Functions。返回值mapDispatchToProps应返回一个对象每个字段都应是函数调用该函数即向 store dispatch 一个 action。返回值被视为dispatchProps合并为被连接组件的 props如果定义了mergeProps则作为第二个参数传入const createMyAction () ({ type: MY_ACTION }) const mapDispatchToProps (dispatch, ownProps) { const boundActions bindActionCreators({ createMyAction }, dispatch) return { dispatchPlainObject: () dispatch({ type: MY_ACTION }), dispatchActionCreatedByActionCreator: () dispatch(createMyAction()), ...boundActions, // you may return dispatch here dispatch, } }推荐用法参见 docs/using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md。对象简写形式Object Shorthand FormmapDispatchToProps可以是对象其中每个字段是一个 action creatorimport { addTodo, deleteTodo, toggleTodo } from ./actionCreators const mapDispatchToProps { addTodo, deleteTodo, toggleTodo, } export default connect(null, mapDispatchToProps)(TodoApp)此时 React-Redux 内部使用bindActionCreators将 store 的dispatch绑定到每个 action creator 上结果即为dispatchProps直接合并到被连接组件或作为第二个参数传给mergeProps// internally, React-Redux calls bindActionCreators // to bind the action creators to the dispatch of your store bindActionCreators(mapDispatchToProps, dispatch)对象简写形式的详细讨论见 docs/using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md。源码视角三种形态的分派src/connect/mapDispatchToProps.ts 中mapDispatchToPropsFactory按形态分派对象形态通过wrapMapToPropsConstant包裹一个常量化函数内部调用bindActionCreators(mapDispatchToProps, dispatch)且dependsOnOwnProps false对象不会依赖 props缺省null/undefined同样走wrapMapToPropsConstant返回{ dispatch }这就是“不传第二个参数就注入dispatch”的实现来源函数形态通过wrapMapToPropsFunc包裹交由 arity 检测决定是否传ownProps。仓库自带的bindActionCreators实现位于 src/utils/bindActionCreators.ts遍历对象的每个字段凡是函数类型就包一层(...args) dispatch(actionCreator(...args))。注意它只绑定函数字段非函数字段会被静默跳过。mergeProps?: (stateProps, dispatchProps, ownProps) Object如果指定了mergeProps它决定最终注入到被包裹组件的 props。如果不提供被包裹组件默认收到{ ...ownProps, ...stateProps, ...dispatchProps }。参数mergeProps最多声明三个参数依次是mapStateToProps()、mapDispatchToProps()的结果和包装组件的propsstatePropsdispatchPropsownProps你返回的普通对象中的字段会作为被包裹组件的 props。典型用途包括根据 props 选取 state 的某一片段或把 action creator 绑定到 props 中的某个变量。返回值mergeProps的返回值被称为mergedProps其字段作为被包裹组件的 props。注意在mergeProps中创建新值会导致重渲染建议对字段做记忆化以避免不必要的重渲染。源码视角默认合并与相等性过滤src/connect/mergeProps.ts 定义了默认实现defaultMergeProps即按{ ...ownProps, ...stateProps, ...dispatchProps }展开展开顺序意味着 stateProps 覆盖 ownProps、dispatchProps 覆盖前两者。同时wrapMergePropsFuncsrc/connect/mergeProps.ts会在后续调用中先用areMergedPropsEqual比较新旧mergedProps相等则保留旧引用——这是避免重复渲染的关键一步。options?: Object{ context?: Object, pure?: boolean, areStatesEqual?: Function, areOwnPropsEqual?: Function, areStatePropsEqual?: Function, areMergedPropsEqual?: Function, forwardRef?: boolean, }context: Object注意该参数仅在 v6.0 及以上版本受支持。React-Redux v6 起允许你提供一个自定义 context 实例供 React-Redux 使用。你需要把该 context 实例同时传给Provider /和被连接组件传给被连接组件有两种方式作为options.context字段或在渲染时作为 prop 传入// const MyContext React.createContext(); connect(mapStateToProps, mapDispatchToProps, null, { context: MyContext })( MyComponent, )从源码看context的默认值是 React-Redux 自带的ReactReduxContextsrc/components/connect.tsx。在 ConnectFunction 中组件会优先使用props.context若提供了合法的 Context Consumer否则退回options.context指定的实例再通过React.useContext(ContextToUse)取出 store 与订阅信息。pure: boolean默认值true假定被包裹组件是纯组件除 props 和所选中的 store 状态外不依赖任何输入或状态。当options.pure为true时connect会执行若干相等性检查用于避免不必要的mapStateToProps、mapDispatchToProps、mergeProps调用最终避免不必要的render。这些检查包括areStatesEqual、areOwnPropsEqual、areStatePropsEqual与areMergedPropsEqual。默认值在 99% 的场景下都够用但你可以出于性能或其他原因用自定义实现覆盖它们下文给出示例。关于 7.0 的版本差异需要说明的是在本仓库当前源码对应 8.x 开发主线中pure选项已被移除——connect现在始终是纯/记忆化组件。若传入puresrc/components/connect.tsx 会输出一条弃用警告The pure option has been removed. connect is now always a pure/memoized component。本关联文档version-7.0仍将pure作为默认true的选项列出反映的是 7.0 版本的行为在使用 7.0 时默认即为纯组件行为无需显式开启。areStatesEqual: (next: Object, prev: Object) boolean默认值strictEqual: (next, prev) prev next在 pure 模式下比较传入的 store state 与其之前的值。示例 1const areStatesEqual (next, prev) prev.entities.todos next.entities.todos如果你的mapStateToProps计算代价高昂且只关心 state 的一小片片段可以覆盖areStatesEqual。上面的示例会忽略该片段之外的所有 state 变化。示例 2如果你有直接修改 store state 的不纯 reducer你可能希望areStatesEqual始终返回falseconst areStatesEqual () false这会根据你的mapStateToProps函数影响其余相等性检查。areOwnPropsEqual: (next: Object, prev: Object) boolean默认值shallowEqual: (objA, objB) boolean当两个对象的每个字段都相等时返回true在 pure 模式下比较传入的 props 与其之前的值。你可以覆盖areOwnPropsEqual作为传入 props 的白名单同时你也需要让mapStateToProps、mapDispatchToProps和mergeProps同样只关注这些白名单 props。areStatePropsEqual: (next: Object, prev: Object) boolean类型function默认值shallowEqual在 pure 模式下比较mapStateToProps的结果与其之前的值。areMergedPropsEqual: (next: Object, prev: Object) boolean默认值shallowEqual在 pure 模式下比较mergeProps的结果与其之前的值。如果你的mapStateToProps使用了一个记忆化 selector且该 selector 只在相关 prop 变化时才返回新对象你可以把areStatePropsEqual覆盖为strictEqual。这会带来非常轻微的性能提升因为每次调用mapStateToProps时都省去了对单个 props 的额外相等性检查。如果你的 selector 会产生复杂 props如嵌套对象、新数组等你可以把areMergedPropsEqual覆盖为deepEqual实现——深度比较可能比重渲染更快。forwardRef: boolean注意该参数仅在 v6.0 及以上版本受支持。如果向connect传入{ forwardRef: true }为连接包装组件添加 ref 时将实际返回被包裹组件的实例。源码视角相等性检查的实际作用点在 src/connect/selectorFactory.ts 的pureFinalPropsSelectorFactory中四道检查按如下流程协作handleSubsequentCalls先分别用areOwnPropsEqual与areStatesEqual注意 7.0 的areStatesEqual签名已扩展为接收nextOwnProps、prevOwnProps见 src/components/connect.tsx 的类型定义判定 props 与 state 是否变化二者都变则走handleNewPropsAndNewState重算stateProps、按需重算dispatchProps、重算mergedProps仅 props 变则走handleNewProps且只在mapStateToProps.dependsOnOwnProps为真时才重算stateProps仅 state 变则走handleNewState先算出新的stateProps并用areStatePropsEqual比较只有变化了才重算mergedProps若新旧 state 与 props 都相等直接返回缓存的mergedProps连mergeProps都不调用。这套只在必要时重算的短路逻辑加上mergeProps层的areMergedPropsEqual过滤与 src/components/connect.tsx 对ConnectFunction的React.memo包裹共同构成了connect的性能护城河。shallowEqual的具体实现位于 src/utils/shallowEqual.ts它先用Object.is语义比较并对0/-0、NaN做了正确处理再比较键集合长度与每个字段的值。connect()Returns返回一个 HOCconnect()的返回值是一个包装函数它接收你的组件返回一个注入了额外 props 的包装组件import { login, logout } from ./actionCreators const mapState (state) state.user const mapDispatch { login, logout } // first call: returns a hoc that you can use to wrap any component const connectUser connect(mapState, mapDispatch) // second call: returns the wrapper component with mergedProps // you may use the hoc to enable different components to get the same behavior const ConnectedUserLogin connectUser(Login) const ConnectedUserProfile connectUser(Profile)大多数情况下包装函数会被立即调用而不保存到临时变量import { login, logout } from ./actionCreators const mapState (state) state.user const mapDispatch { login, logout } // call connect to generate the wrapper function, and immediately call // the wrapper function to generate the final wrapper component. export default connect(mapState, mapDispatch)(Login)从源码看connect(mapState, mapDispatch)返回的是wrapWithConnectsrc/components/connect.tsx它校验传入的组件是合法 React 元素类型生成Connect(ComponentName)形式的displayName最终通过hoistStatics把被包裹组件的静态属性和 ref若开启forwardRef提升到包装组件上src/components/connect.tsx。示例用法合集connect非常灵活以下是官方文档给出的典型调用方式只注入dispatch不监听 storeexport default connect()(TodoApp)注入全部 action creatorsaddTodo、completeTodo……不订阅 storeimport * as actionCreators from ./actionCreators export default connect(null, actionCreators)(TodoApp)注入dispatch和全局 state 的每个字段不要这样做这会让TodoApp在每次 state 变化后都重渲染摧毁所有性能优化。 更好的做法是在视图层级中让多个组件各自做更细粒度的connect()每个只监听 state 中与自己相关的片段。// dont do this! export default connect((state) state)(TodoApp)注入dispatch和todosfunction mapStateToProps(state) { return { todos: state.todos } } export default connect(mapStateToProps)(TodoApp)注入todos和全部 action creatorsimport * as actionCreators from ./actionCreators function mapStateToProps(state) { return { todos: state.todos } } export default connect(mapStateToProps, actionCreators)(TodoApp)注入todos并把全部 action creators 作为actions注入import * as actionCreators from ./actionCreators import { bindActionCreators } from redux function mapStateToProps(state) { return { todos: state.todos } } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators(actionCreators, dispatch) } } export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)注入todos和指定 action creatoraddTodoimport { addTodo } from ./actionCreators import { bindActionCreators } from redux function mapStateToProps(state) { return { todos: state.todos } } function mapDispatchToProps(dispatch) { return bindActionCreators({ addTodo }, dispatch) } export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)注入todos和指定 action creatorsaddTodo、deleteTodo使用简写语法import { addTodo, deleteTodo } from ./actionCreators function mapStateToProps(state) { return { todos: state.todos } } const mapDispatchToProps { addTodo, deleteTodo, } export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)注入todos并把todoActionCreators作为todoActions、counterActionCreators作为counterActions注入import * as todoActionCreators from ./todoActionCreators import * as counterActionCreators from ./counterActionCreators import { bindActionCreators } from redux function mapStateToProps(state) { return { todos: state.todos } } function mapDispatchToProps(dispatch) { return { todoActions: bindActionCreators(todoActionCreators, dispatch), counterActions: bindActionCreators(counterActionCreators, dispatch), } } export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)注入todos并把两组 action creators 合并为actionsimport * as todoActionCreators from ./todoActionCreators import * as counterActionCreators from ./counterActionCreators import { bindActionCreators } from redux function mapStateToProps(state) { return { todos: state.todos } } function mapDispatchToProps(dispatch) { return { actions: bindActionCreators( { ...todoActionCreators, ...counterActionCreators }, dispatch, ), } } export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)注入todos及两组 action creators全部直接作为 propsimport * as todoActionCreators from ./todoActionCreators import * as counterActionCreators from ./counterActionCreators import { bindActionCreators } from redux function mapStateToProps(state) { return { todos: state.todos } } function mapDispatchToProps(dispatch) { return bindActionCreators( { ...todoActionCreators, ...counterActionCreators }, dispatch, ) } export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)根据 props 注入特定用户的todosimport * as actionCreators from ./actionCreators function mapStateToProps(state, ownProps) { return { todos: state.todos[ownProps.userId] } } export default connect(mapStateToProps)(TodoApp)根据 props 注入特定用户的todos并把props.userId注入 actionimport * as actionCreators from ./actionCreators function mapStateToProps(state) { return { todos: state.todos } } function mergeProps(stateProps, dispatchProps, ownProps) { return Object.assign({}, ownProps, { todos: stateProps.todos[ownProps.userId], addTodo: (text) dispatchProps.addTodo(ownProps.userId, text), }) } export default connect(mapStateToProps, actionCreators, mergeProps)(TodoApp)Notes两个关键行为细节The Arity ofmapToPropsFunctionsarity 对ownProps的影响mapStateToProps与mapDispatchToProps声明参数的数量决定它们是否接收ownProps。注意如果函数的正式定义中只有一个必填参数即函数length为 1ownProps不会作为第二个参数传入。例如下面定义的函数不会收到ownPropsfunction mapStateToProps(state) { console.log(state) // state console.log(arguments[1]) // undefined } const mapStateToProps (state, ownProps {}) { console.log(state) // state console.log(ownProps) // {} }没有必填参数或声明了两个参数的函数会收到ownPropsconst mapStateToProps (state, ownProps) { console.log(state) // state console.log(ownProps) // ownProps } function mapStateToProps() { console.log(arguments[0]) // state console.log(arguments[1]) // ownProps } const mapStateToProps (...args) { console.log(args[0]) // state console.log(args[1]) // ownProps }源码视角dependsOnOwnProps的判定arity 检测的实际实现位于 src/connect/wrapMapToProps.tsfunction getDependsOnOwnProps(mapToProps: MapToProps) { return mapToProps.dependsOnOwnProps ? Boolean(mapToProps.dependsOnOwnProps) : mapToProps.length ! 1 }核心规则是函数length ! 1即认为依赖ownProps。因此(state) ...length 为 1→ 不依赖ownProps不传(state, ownProps) ...length 为 2→ 依赖ownProps(...args) ...length 为 0→ 依赖ownProps因为函数通过arguments/剩余参数取参无法如实报告 length(state, ownProps {}) ...默认参数使 length 为 1→ 按规则不传ownProps此时会使用默认参数值{}这与文档中第二个参数为 undefined 时使用默认值的行为一致。这一判定同时驱动了两处行为wrapMapToPropsFunc生成的代理决定调用时是否传入ownPropssrc/connect/wrapMapToProps.ts以及selectorFactory决定 props 变化时是否需要重算stateProps/dispatchProps。Factory Functions工厂函数如果mapStateToProps或mapDispatchToProps返回的是一个函数那么组件实例化时该外层函数会被调用一次其返回值将作为真正的mapStateToProps/mapDispatchToProps在后续调用中使用。工厂函数常与记忆化 selector配合使用让你能在闭包内创建组件实例专属的 selectorconst makeUniqueSelectorInstance () createSelector([selectItems, selectItemId], (items, itemId) items[itemId]) const makeMapState (state) { const selectItemForThisComponent makeUniqueSelectorInstance() return function realMapState(state, ownProps) { const item selectItemForThisComponent(state, ownProps.itemId) return { item } } } export default connect(makeMapState)(SomeComponent)源码视角工厂检测的第一次调用src/connect/wrapMapToProps.ts 的detectFactoryAndVerify在首次调用时做了三件事记录真实mapToProps与dependsOnOwnProps调用一次并检查返回值若返回的是函数则将其替换为真正的mapToProps并再次调用即完成工厂展开在生产环境之外用verifyPlainObject校验返回结果是普通对象避免开发者写出返回非法值的mapToProps。深入原理一次 store 更新如何驱动连接组件重渲染结合 src/components/connect.tsx可以梳理出连接组件完整的订阅链路订阅建立ConnectFunction通过React.useContext(ContextToUse)获取 store 与最近的祖先订阅useMemo创建Subscription实例src/components/connect.tsx若 store 来自 propsdidStoreComeFromProps即传入了带getState/dispatch的storeprop则直接订阅该 store否则订阅来自 context 的祖先订阅。更新检测store 派发 action 后checkForUpdates被触发它调用childPropsSelector即selectorFactory生成的记忆化 selector计算出新的 child props若与lastChildProps相同则只级联通知嵌套订阅否则保存新值并通过useSyncExternalStore的订阅回调触发 React 重渲染src/components/connect.tsx。渲染短路渲染阶段使用actualChildPropsSelectorsrc/components/connect.tsx若这次渲染源于 store 更新且包装 props 未变直接复用已算好的 child props不再重复计算否则用最新 state 重算。renderedWrappedComponent与renderedChild均经useMemo记忆化若引用未变 React 会跳过子树渲染src/components/connect.tsx。Context 透传订阅了 store 的连接组件会把自己的subscription写入 ContextoverriddenContextValue保证嵌套的已连接后代在祖先更新完成之前不会提前重渲染src/components/connect.tsx。这套机制也解释了官方文档中的性能建议connect((state) state)(TodoApp)之所以被标记为不要这样做是因为它会让组件订阅整个 store任何一次 state 变化都会触发重算与重渲染绕过了细粒度订阅带来的全部优化空间。相关资源官方connect文档当前主线版本docs/api/connect.md官方 hooks APIReact-Redux 8.x 推荐替代方案docs/api/hooks.md核心实现src/components/connect.tsx、src/connect/selectorFactory.ts、src/connect/mapStateToProps.ts、src/connect/mapDispatchToProps.ts、src/connect/mergeProps.ts辅助工具src/connect/wrapMapToProps.ts、src/utils/bindActionCreators.ts、src/utils/shallowEqual.ts、src/utils/Subscription.ts使用指南docs/using-react-redux/connect-extracting-data-with-mapStateToProps.md、docs/using-react-redux/connect-dispatching-actions-with-mapDispatchToProps.md历史版本行为差异可查阅仓库 website/versioned_docs/version-5.x/api/connect.md 与 website/versioned_docs/version-6.x/api/connect.md本文档即 website/versioned_docs/version-7.0/api/connect.md 的展开。【免费下载链接】react-reduxOfficial React bindings for Redux项目地址: https://gitcode.com/gh_mirrors/re/react-redux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

免费获取报价