使用Rollup打包RTKQ库时出现react-redux Provider上下文缺失错误
错误信息
运行时抛出错误:
could not find react-redux context value; please ensure the component is wrapped in a <Provider>
该错误仅在库通过Rollup打包后出现,源码直接使用或未打包发布时正常工作。
涉事库情况
基于RTKQ封装的API集合,导出apiSlice供消费者集成到自身Redux Store中。
Rollup配置
const resolve = require('@rollup/plugin-node-resolve'); const commonjs = require('@rollup/plugin-commonjs'); const peerDepsExternal = require('rollup-plugin-peer-deps-external'); const babel = require('@rollup/plugin-babel'); const packageJson = require('./package.json'); module.exports = { input: 'src/index.js', external: [/@babel\/runtime/], output: [ { file: packageJson.module, format: 'es', sourcemap: true, } ], plugins: [ peerDepsExternal(), resolve(), babel({ exclude: 'node_modules/**', babelHelpers: 'runtime', }), commonjs({ strictRequires: true, }), ], };
package.json
{ "name": "some-name", "version": "1.1.2", "main": "dist/esm/index.js", "files": [ "dist" ], "dependencies": { "@babel/runtime": "^7.20.6", "@faker-js/faker": "^7.6.0", "@reduxjs/toolkit": "^1.8.0", "luxon": "^3.1.0", "np": "^7.6.2", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.2", "reactjs-localstorage": "^1.0.1" }, "scripts": { "lint": "eslint .", "lint:fix": "eslint --fix .", "prepare": "npm run co:login", "prepublishOnly": "npm run build && np --no-publish --no-yarn --no-cleanup", "build": "rollup --config", "start": "webpack serve", "devBuild": "webpack --mode=development", "test": "echo \"No tests yet\"" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "devDependencies": { "@babel/core": "^7.20.5", "@babel/plugin-transform-runtime": "^7.19.6", "@babel/preset-env": "^7.20.2", "@rollup/plugin-babel": "^6.0.3", "@rollup/plugin-commonjs": "^24.0.0", "@rollup/plugin-node-resolve": "^15.0.1", "eslint": "^8.17.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.0.0", "eslint-plugin-react": "^7.30.0", "eslint-plugin-storybook": "^0.5.12", "rollup": "^3.7.5", "rollup-plugin-peer-deps-external": "^2.2.4", "webpack": "^5.70.0", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.11.1" } }
.babelrc
{ "presets": ["@babel/preset-env"], "plugins": ["@babel/plugin-transform-runtime"] }
库核心逻辑:通过RTKQ的createApi()创建apiSlice,导出该切片及所有定义的API端点。
消费者情况
消费者为使用Storybook测试的组件库,其preview.js核心代码如下:
import { createTheme, CssBaseline, responsiveFontSizes, ThemeProvider, } from '@mui/material'; import { SnackbarProvider } from 'notistack'; import { MemoryRouter } from 'react-router'; import { Provider } from 'react-redux'; import { configureStore } from '@reduxjs/toolkit'; import { apiSlice} from '@my_company/the_lib_with_the_error'; import { useDispatch } from 'react-redux'; import { useEffect } from 'react'; import { initialize as initializeMSW, mswDecorator } from 'msw-storybook-addon'; import { ConfirmProvider } from 'material-ui-confirm'; // 测试用Store const store = configureStore({ reducer: { [apiSlice.reducerPath]: apiSlice.reducer, }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(apiSlice.middleware), devTools: { trace: true }, }); let theme = createTheme({/** 主题配置 */}); theme = responsiveFontSizes(theme); initializeMSW({ waitUntilReady: false }); const SetupApi = () => { const dispatch = useDispatch(); useEffect(() => { dispatch(apiSlice.util.resetApiState()); }, [dispatch]); return null; } const stdLayout = (Story) => { store.dispatch(apiSlice.util.resetApiState()); return ( <> <Provider store={store}> <ThemeProvider theme={theme}> <SnackbarProvider maxSnack={3} autoHideDuration={3000} anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }} > <ConfirmProvider> <> <CssBaseline /> <SetupApi /> <MemoryRouter initialEntries={['/']}> <Story /> </MemoryRouter> </> </ConfirmProvider> </SnackbarProvider> </ThemeProvider> </Provider> </> ); };
解决思路
1. 修复依赖重复问题(核心)
当前库的dependencies中包含react、react-redux、@reduxjs/toolkit,这会导致打包时这些依赖被打包进库文件,消费者安装后会出现两份依赖实例:一份来自库的打包产物,一份来自消费者自身的node_modules。React-Redux的上下文是基于实例的,两份实例无法共享上下文,从而抛出找不到Provider的错误。
解决方案:
- 将
react、react-dom、react-redux、@reduxjs/toolkit移到peerDependencies中,明确要求消费者提供这些依赖:"peerDependencies": { "@reduxjs/toolkit": "^1.8.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.0.2" }, "dependencies": { "@babel/runtime": "^7.20.6", "@faker-js/faker": "^7.6.0", "luxon": "^3.1.0", "reactjs-localstorage": "^1.0.1" // 移除上述四个peer依赖 } - 将
np移到devDependencies中(它是发布工具,不需要打包进产物)。
2. 确保Rollup正确排除Peer依赖
rollup-plugin-peer-deps-external插件会自动识别peerDependencies并将其排除在打包之外,无需手动在external字段添加。检查打包后的产物,确认没有包含react-redux或@reduxjs/toolkit的代码。
3. 清理消费者端依赖缓存
消费者执行以下操作,确保依赖实例唯一:
- 删除
node_modules文件夹 - 删除
package-lock.json(或yarn.lock) - 重新执行
npm install
4. 验证组件渲染时机
确保Storybook中所有使用RTKQ Hooks(如useQuery、useMutation)的组件,都在<Provider>的包裹范围内。避免在组件顶层(渲染前)调用需要上下文的方法,所有RTKQ相关逻辑应放到组件内部或useEffect中执行。
5. 检查Babel配置
当前Babel仅配置了@babel/preset-env,如果库中使用了React语法(如JSX),需添加@babel/preset-react:
{ "presets": ["@babel/preset-env", "@babel/preset-react"], "plugins": ["@babel/plugin-transform-runtime"] }
并在devDependencies中安装该预设:
npm install @babel/preset-react --save-dev
内容的提问来源于stack exchange,提问作者zetain

