React Native同组件用connect与store报错的解决方案咨询
解决React Redux的"Invariant Violation: Could not find 'store' in context"错误
你遇到的这个错误本质是组件层级顺序搞反了:你用connect()包裹了App组件,但App组件内部才渲染<Provider store={store}>。Redux的connect需要从组件树的上层上下文获取store,而现在被connect的App处于Provider的外部,自然找不到store。
修复步骤:拆分组件层级
我们需要把「提供store的Provider」和「需要连接store的业务组件」分开,让业务组件处于Provider的内部。
拆分原App组件:把原来的
App组件拆成两个部分:- 一个根组件(比如叫
App),负责渲染Provider并传入store; - 原来的业务逻辑部分(改名为
AppContent),作为Provider的子组件,这时候再用connect包裹它就能正常获取store了。
- 一个根组件(比如叫
修改代码实现:
下面是修改后的完整代码,关键改动已标注:
/* eslint-disable global-require */ import React, { Component } from 'react' import { Provider, connect } from 'react-redux' import { BackHandler } from 'react-native' import AppContainer from './eXpand/Components/Navigation'; import { AppLoading } from 'expo' import * as Font from 'expo-font' import { Ionicons } from '@expo/vector-icons' import { cacheAssets, cacheFonts } from './eXpand/Helpers/Defaults/AssetsCaching' import store from '~/Store/store' import registerForPushNotificationsAsync from './eXpand/Components/Services/notifications'; // Local Import import { setUserGsm } from '~/Store/actions'; // 1. 把原来的App组件改名为AppContent,暂时去掉connect class AppContent extends Component { _isMounted = false; constructor(props) { super(props) this.state = { isReady: false, GSM: null } } async registerForPush() { const { status: existingStatus } = await Permissions.getAsync( Permissions.NOTIFICATIONS ); let finalStatus = existingStatus; if (existingStatus !== 'granted') { const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS); finalStatus = status; } if (finalStatus !== 'granted') { return; } let gsm = await Notifications.getExpoPushTokenAsync(); if (this._isMounted) { this.setState({ GSM: gsm }); // 修正逻辑:获取到GSM后再调用action,避免传入null this.props.setUserGsm(gsm); } } async componentDidMount() { this._isMounted = true; if (this._isMounted) { this.registerForPush() } BackHandler.addEventListener('hardwareBackPress', this.handleBackButton); await Font.loadAsync({ Roboto: require('native-base/Fonts/Roboto.ttf'), Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'), SFUIDisplayBlack: require('^/Fonts/SFUIDisplay-Black.otf'), SFUIDisplayBold: require('^/Fonts/SFUIDisplay-Bold.otf'), SFUIDisplayHeavy: require('^/Fonts/SFUIDisplay-Heavy.otf'), SFUIDisplayLight: require('^/Fonts/SFUIDisplay-Light.otf'), SFUIDisplayMedium: require('^/Fonts/SFUIDisplay-Medium.otf'), SFUIDisplaySemibold: require('^/Fonts/SFUIDisplay-Semibold.otf'), SFUIDisplayThin: require('^/Fonts/SFUIDisplay-Thin.otf'), SFUIDisplayUltralight: require('^/Fonts/SFUIDisplay-Ultralight.otf'), MYRIADPROBOLD: require('^/Fonts/MyriadPro-Bold.otf'), MyriadProBlackSemiCn: require('^/Fonts/MyriadPro-BlackSemiCn.otf'), MyriadProBoldSemiExtended: require('^/Fonts/MyriadPro-BoldSemiExtended.ttf'), ...Ionicons.font, PTSansRegular: require('^/Fonts/PTSans-Regular.ttf'), PTSansBold: require('^/Fonts/PTSans-Bold.ttf'), PTSansItalic: require('^/Fonts/PTSans-Italic.ttf'), }) this.setState({ isReady: true }) } componentWillUnmount() { BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton); this._isMounted = false; } handleBackButton() { return true; } async _loadAssetsAsync() { const imageAssets = cacheAssets([require('^/Logos/expanded.png')]) const fontAssets = cacheFonts({ Roboto: require('native-base/Fonts/Roboto.ttf'), Roboto_medium: require('native-base/Fonts/Roboto_medium.ttf'), SFUIDisplayBlack: require('^/Fonts/SFUIDisplay-Black.otf'), SFUIDisplayBold: require('^/Fonts/SFUIDisplay-Bold.otf'), SFUIDisplayHeavy: require('^/Fonts/SFUIDisplay-Heavy.otf'), SFUIDisplayLight: require('^/Fonts/SFUIDisplay-Light.otf'), SFUIDisplayMedium: require('^/Fonts/SFUIDisplay-Medium.otf'), SFUIDisplaySemibold: require('^/Fonts/SFUIDisplay-Semibold.otf'), SFUIDisplayThin: require('^/Fonts/SFUIDisplay-Thin.otf'), SFUIDisplayUltralight: require('^/Fonts/SFUIDisplay-Ultralight.otf'), MYRIADPROBOLD: require('^/Fonts/MyriadPro-Bold.otf'), MyriadProBlackSemiCn: require('^/Fonts/MyriadPro-BlackSemiCn.otf'), MyriadProBoldSemiExtended: require('^/Fonts/MyriadPro-BoldSemiExtended.ttf'), PTSansRegular: require('^/Fonts/PTSans-Regular.ttf'), PTSansBold: require('^/Fonts/PTSans-Bold.ttf'), PTSansItalic: require('^/Fonts/PTSans-Italic.ttf'), }) await Promise.all([imageAssets, fontAssets]) } render() { if (!this.state.isReady) { return ( <AppLoading startAsync={this._loadAssetsAsync} onFinish={() => this.setState({ isReady: true })} /> ) } return <AppContainer /> } } // 2. 给AppContent添加connect,现在它处于Provider内部可以正常获取store const mapStateToProps = (state) => ({ GSM: state.GSM }); const mapDispatchToProps = (dispatch) => ({ setUserGsm: (GSM) => { dispatch(setUserGsm(GSM)); } }); const ConnectedAppContent = connect( mapStateToProps, mapDispatchToProps, )(AppContent); // 3. 创建新的根组件,渲染Provider并包裹ConnectedAppContent class App extends Component { render() { return ( <Provider store={store}> <ConnectedAppContent /> </Provider> ) } } export default App;
额外注意点
- 我还修正了一个逻辑问题:原来你在
componentDidMount里直接调用this.props.setUserGsm(this.state.GSM),但registerForPush是异步的,这时候GSM还是null。现在改成在获取到gsm后直接调用this.props.setUserGsm(gsm),确保传入正确的token。 - 确保你的
store导入路径正确,~/Store/store的别名配置没问题。
内容的提问来源于stack exchange,提问作者Khaled Boussoffara
相关产品推荐
相关产品推荐

