拓冰建站拓冰建站
首页 / 资讯中心 / 正文

react-redux Provider 组件完全指南:store 注入、自定义 Context 与源码级原理解析

react-redux Provider 组件完全指南store 注入、自定义 Context 与源码级原理解析【免费下载链接】react-reduxOfficial React bindings for Redux项目地址: https://gitcode.com/gh_mirrors/re/react-reduxProvider是 React Redux 应用中最顶层的桥接组件它通过 React Context 机制将唯一的 Reduxstore注入到整个组件树中使得任意嵌套的connect()组件与 Hooks API 都能访问到同一个 store 实例。本指南基于 react-redux 仓库中 version-7.1 的 Provider 文档结合 Provider 源码、Context 实现与测试用例完整讲解Provider的 Props、典型用法、底层订阅机制与常见坑点帮助你正确搭建 React Redux 应用的根组件并理解其工作原理。Provider的核心职责Provider /的作用非常单一而关键让 Reduxstore对任何被connect()包裹的嵌套组件可见。在 React Redux 应用中任何一个 React 组件都可以被连接到 store因此绝大多数应用都会在组件树的最顶层渲染一个Provider并把整个应用的组件树作为它的子节点Provider store{store} App / /Provider这里有一个必须遵守的约束通常情况下连接了 store 的组件connected component必须嵌套在Provider内部才能正常工作。如果某个connect()组件没有被Provider包裹它在渲染时会抛出运行时错误。Props 详解Provider接收的 props 定义在源码的ProviderProps接口中见 src/components/Provider.tsxversion-7.1 文档中明确说明的常用 props 如下Prop类型说明storeRedux Store应用中唯一的 Redux store 实例必填childrenReactElement组件层级树的根节点contextReact Context 实例可选自定义 Context传入后必须给所有 connected 组件提供同一个 Context 实例store唯一的 Redux storestore是应用中唯一的 Redux store。它由createStore()或 Redux Toolkit 的configureStore()创建通常在整个应用生命周期内只创建一次。在 Provider 源码中Provider 会在挂载时缓存store.getState()的初始快照并基于 store 创建一个内部subscription对象来监听状态变化。children组件树根节点children是位于组件层级最顶层的根组件。与旧版不同当前实现不强制要求只有一个子元素——测试用例should not enforce a single child见 test/components/Provider.spec.tsx验证了 Provider 可以安全地渲染多个子元素甚至不渲染任何子元素也不会抛错。context自定义 Context 实例默认情况下React Redux 内部使用它自己创建的 Context。但你也可以传入一个自定义的 Context 实例const MyContext React.createContext(null) Provider store{store} context{MyContext} App / /Provider一旦使用自定义 context你必须给所有 connected 组件提供同一个 context 实例。否则会在运行时得到如下错误Invariant ViolationCould not find store in the context of Connect(MyComponent). Either wrap the root component in aProvider, or pass a custom React context provider toProviderand the corresponding React context consumer to Connect(Todo) in connect options.从源码看src/components/Provider.tsxProvider 通过const Context context || ReactReduxContext选择要使用的 Context然后用Context.Provider value{contextValue}包裹 children。这个错误的根因是connect与 Provider 各自持有了不同的 Context 引用导致 store 没有进入 connected 组件所能读取的那个 Context。通过ReactReduxContext直接访问 store注意为了访问 store你并不需要提供自定义 context。React Redux 会导出它默认使用的 Context 实例你可以直接用它读取 storeimport { ReactReduxContext } from react-redux // in your connected component render() { return ( ReactReduxContext.Consumer {({ store }) { // do something with the store here }} /ReactReduxContext.Consumer ) }该导出定义于 src/components/Context.tsReact Redux 使用Symbol.for(react-redux-context)作为全局键在globalThis上缓存按React.createContext区分的一份 Context 映射并通过getContext()惰性创建默认 Context非生产环境下还会为其设置displayName ReactRedux。ReactReduxContext从 src/exports.ts 对外导出。在函数组件中使用useStore/useReduxContext除了ReactReduxContext.Consumer这种基于渲染属性的写法函数组件还可以使用 React Redux 提供的 Hooks 获取 storeimport { useStore } from react-redux const MyComponent () { const store useStore() return div{store.getState().someValue}/div }useStore与useReduxContext都通过React.useContext(ReactReduxContext)读取 Provider 注入的值当组件不在Provider内时开发模式下会抛出 could not find react-redux context value; please ensure the component is wrapped in aProvider 的错误。示例用法原生 React 示例下面的例子中App /是根级组件位于组件层级的最顶端import React from react import ReactDOM from react-dom import { Provider } from react-redux import { App } from ./App import createStore from ./createReduxStore const store createStore() ReactDOM.render( Provider store{store} App / /Provider, document.getElementById(root), )与 React Router 配合使用Provider也可以直接包裹路由组件。下面的例子把Provider放在Router外层确保所有路由下的组件都能访问到同一个 storeimport React from react import ReactDOM from react-dom import { Provider } from react-redux import { Router, Route } from react-router-dom import { App } from ./App import { Foo } from ./Foo import { Bar } from ./Bar import createStore from ./createReduxStore const store createStore() ReactDOM.render( Provider store{store} Router history{history} Route exact path/ component{App} / Route path/foo component{Foo} / Route path/bar component{Bar} / /Router /Provider, document.getElementById(root), )源码原理Provider 如何把 store 注入组件树理解 Provider 的源码有助于你排查 context 相关的疑难问题。核心逻辑集中在 src/components/Provider.tsx主要分三步1. 构建 context 值useMemoProvider 通过React.useMemo缓存contextValue依赖项为[store, serverState]const contextValue React.useMemo(() { const subscription createSubscription(store) const baseContextValue { store, subscription, getServerState: serverState ? () serverState : undefined, } if (process.env.NODE_ENV production) { return baseContextValue } else { const { identityFunctionCheck once, stabilityCheck once } providerProps return Object.assign(baseContextValue, { stabilityCheck, identityFunctionCheck, }) } }, [store, serverState])可见注入到 Context 的值结构为{ store, subscription, getServerState, stabilityCheck, identityFunctionCheck }这正是 ReactReduxContextValue 接口所声明的形状。useSelector、useStore等 Hook 正是从该对象中解构出 store 与 subscription。2. 建立订阅useIsomorphicLayoutEffectProvider 挂载后会把subscription.onStateChange指向subscription.notifyNestedSubs并调用trySubscribe()订阅 storeuseIsomorphicLayoutEffect(() { const { subscription } contextValue subscription.onStateChange subscription.notifyNestedSubs subscription.trySubscribe() if (previousState ! store.getState()) { subscription.notifyNestedSubs() } return () { subscription.tryUnsubscribe() subscription.onStateChange undefined } }, [contextValue, previousState])卸载时则调用tryUnsubscribe()取消订阅并清理回调。测试用例should unsubscribe before unmounting见 test/components/Provider.spec.tsx验证了 Provider 卸载时一定会执行 store 的 unsubscribe。3. 嵌套订阅机制createSubscription实现了一套支持嵌套的订阅机制Provider 的 subscription 相当于根订阅直接调用store.subscribe而每个 connected 组件通过addNestedSub挂到父级 subscription 上。这样当 store 状态变化时祖先组件先于后代组件重渲染从而保证mapStateToProps中读取的状态始终一致。测试should pass state consistently to mapState见 test/components/Provider.spec.tsx专门验证了这一行为。4. 运行时 Props 变化处理Provider 支持在运行时更换 store。测试accepts new store in props见 test/components/Provider.spec.tsx展示了切换 store 后组件重新从新 store 读取状态且旧 store 的分发不再触发重渲染。测试should handle store and children change in the same render见 test/components/Provider.spec.tsx则验证了 store 与 children 同时变化时行为正确。5. 开发模式检查当前版本新增能力在当前仓库源码中Provider 还支持两个仅用于开发模式的 PropsstabilityCheck选择器稳定性检查频率identityFunctionCheck选择器恒等函数检查频率两者取值均为never | once | always默认once定义见 src/hooks/useSelector.ts。它们会被写入 contextValue供useSelector在开发模式下检测选择器返回新引用导致不必要重渲染以及选择器直接返回根状态这两类问题检查逻辑见 src/hooks/useSelector.ts。说明serverState、stabilityCheck、identityFunctionCheck等 Props 是在 v8.0/v8.1/v9.0 之后逐步加入的。version-7.1 文档本身只覆盖store、children、context三个 Props上述扩展属于当前仓库源码更新版本的实现细节供需要升级的读者参考。常见问题与排查建议Could not find store in the context of Connect(...)connected 组件没有被Provider包裹或者自定义 context 实例在 Provider 与 connect 之间不一致。检查根组件是否位于Provider store{store}内部。store 状态更新后组件不重渲染确认store是通过 Redux 的dispatch更新而不是直接修改 state 对象同时确认Provider使用的是同一个 store 实例。嵌套ProviderReact Redux 支持嵌套 Provider内层 Provider 的 store 优先于外层测试should handle subscriptions correctly when there is nested Providers见 test/components/Provider.spec.tsx验证了内层 connected 组件只响应内层 store 的更新。配合React.StrictMode测试works in StrictMode without warnings见 test/components/Provider.spec.tsx确认 Provider 在 React 16.3 的 StrictMode 下不会产生警告。延伸阅读关于如何用 Hooks 消费 Provider 注入的 store参见 hooks 文档 与 useSelector 源码关于connect()如何读取 Provider 提供的 store参见 connect 文档 与 connect 源码完整的环境搭建与入门流程参见 快速开始指南【免费下载链接】react-reduxOfficial React bindings for Redux项目地址: https://gitcode.com/gh_mirrors/re/react-redux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门