5、connect 的魔法:React-Redux 是如何让组件无感连接 store 的?

111 阅读3分钟

✍️ 模拟 Dan Abramov 视角 + React-Redux 作者 Mark Erikson 的底层解构

🎯 主线:从 Context 到高阶组件,揭秘 Redux 与 React 的桥梁

🧠 关键词:connect、useSelector、性能优化、Context 隔离、订阅模型


🧩 起点问题:组件如何感知 Redux 状态?

Redux 提供了状态管理容器没错,但你不可能手动去:

store.subscribe(...)
store.getState()

每次组件更新都去比对全量 state?那性能爆炸。

所以我们要解决两个关键点:

  1. 如何让组件「自动感知」状态变化?
  2. 如何让组件「只更新自己关注的那一小块」?

这就是我们写出 react-redux 的初衷。


🧱 第一代实现:connect(mapStateToProps)

使用方式:

const mapState = state => ({
  count: state.counter.value
})

const mapDispatch = {
  increment
}

export default connect(mapState, mapDispatch)(MyComponent)

背后的本质是一个 高阶组件(HOC)

function connect(mapStateToProps, mapDispatchToProps) {
  return function wrapWithConnect(WrappedComponent) {
    return class ConnectedComponent extends React.Component {
      // 订阅 store、取数据、props 合成
    }
  }
}

它会在组件挂载时订阅 Redux,并在状态变更时通过 setState() 触发更新。


🌐 关键依赖:React Context 传递 store

在 Redux 应用的根组件中,你会看到这样一段:

<Provider store={store}>
  <App />
</Provider>

这一步非常重要,它将 Redux store 通过 React Context 向下传递。

实现原理(简化):

const StoreContext = React.createContext()

function Provider({ store, children }) {
  return (
    <StoreContext.Provider value={store}>
      {children}
    </StoreContext.Provider>
  )
}

所以,connect 本质上是:

  1. 拿到 store(来自 Context);
  2. 调用 store.getState() + mapStateToProps
  3. 自动调用 store.subscribe() 注册回调;
  4. dispatch 后触发组件重新 render。

🔥 核心挑战一:如何避免所有组件都更新?

Redux 是单状态树,状态变动后如果每个组件都订阅 store.getState() 就全体更新,性能灾难。

所以我们实现了「选择性订阅」。

每个 connect 组件只订阅自己那一部分 state,一旦对应 slice 发生变化就触发 shouldComponentUpdate()

用 shallowEqual 实现最小更新:

const shouldUpdate = !shallowEqual(prevProps, nextProps)

这个机制,让 Redux 拥有了性能上的杀手锏:局部响应式


💡 connect 的架构演进(v6+)

在 v6+ 版本中,connect 的内部实现升级为 Context 分片隔离 + 函数组件兼容 + 性能进一步优化

  • 拆分了每个组件的订阅链;
  • 引入 React.memo()useContextSelector 优化更新;
  • 更好支持 React.StrictMode 和 Concurrent 模式;
  • 自动区分 mapStateToPropsfactory function(更灵活)。

💡 为什么后来有了 useSelector 和 useDispatch?

我们设计 connect 的初衷是支持 class 组件,而函数组件崛起后,我们推出了新的 Hook API:

const value = useSelector(state => state.counter.value)
const dispatch = useDispatch()

这比 connect 更简洁、类型推导更清晰、逻辑更加聚合。

但背后其实还是一样的订阅模型。


🧪 小实验:你可以自己实现一个极简 connect

function connect(mapState, mapDispatch) {
  return function (WrappedComponent) {
    return function Connected() {
      const store = useContext(StoreContext)
      const [state, setState] = useState(() => mapState(store.getState()))
      
      useEffect(() => {
        const unsubscribe = store.subscribe(() => {
          const newState = mapState(store.getState())
          setState(newState)
        })
        return unsubscribe
      }, [])

      const dispatchProps = typeof mapDispatch === 'function'
        ? mapDispatch(store.dispatch)
        : bindActionCreators(mapDispatch, store.dispatch)

      return <WrappedComponent {...state} {...dispatchProps} />
    }
  }
}

⚖️ connect vs useSelector:我们如何选择?

场景推荐方式
老项目、Class 组件connect
新项目、函数组件为主useSelector
大型团队模块化结构connect(清晰组织 map)
想享受极致简洁 + 类型推导Redux Toolkit + Hooks

🔚 总结:connect 是 React 与 Redux 的“翻译官”

Redux 是一个“后端思想”的状态容器,而 React 是“组件驱动”的视图系统。

connect 是这两者之间最重要的桥梁:

  • 负责连接:store ↔️ 组件;
  • 管理更新:局部订阅、最小渲染;
  • 保持解耦:不污染组件逻辑,只注入 props。

在底层,它是性能优化的守门员;在架构上,它是前后端数据流的交汇点。


⏭️ 下一篇预告

我们将在下一篇《Redux 最佳实践:构建可维护、可扩展的状态架构》中,从团队协作视角出发,探讨:

如何组织 reducer? 如何模块化 store? 如何在大型项目中优雅使用 Redux Toolkit?