打包不含React的React组件库时Jest测试遇ReferenceError求助
嘿,我来帮你拆解下遇到的问题,再分享些组件库开发的实用思路~
一、先搞定ReferenceError: React is not defined的问题
这个错误的核心原因是:JSX转译后本质是调用React.createElement,但你的组件库没打包React,测试环境里也没提供全局的React,同时组件里没手动导入React,就会触发这个报错。给你两个靠谱的解决办法:
方案1:让Babel自动处理React导入(推荐,适配React 17+)
从React 17开始,官方支持JSX自动导入,不需要在每个组件里写import React from 'react'。你只需要在Babel配置里修改@babel/plugin-transform-react-jsx的runtime设置:
// .babelrc 或者 babel.config.json { "presets": [ // 你的其他preset,比如@babel/preset-react ], "plugins": [ ["@babel/plugin-transform-react-jsx", { "runtime": "automatic" }] ] }
这样Babel会自动帮你导入JSX需要的React相关函数,不管是组件代码还是测试代码,都不用手动加React的import了,从根源解决未定义问题。
方案2:在Jest全局配置里注入React(适配React 16及以下)
如果你还在用React 16或更早版本,或者不想改Babel配置,可以在Jest的setup文件里把React挂到全局对象上:
// jest.setup.js import React from 'react'; // 全局挂载React,让测试环境里的JSX能找到它 global.React = React; // 你的enzyme原有配置 import { configure } from 'enzyme' import Adapter from 'enzyme-adapter-react-16' configure({ adapter: new Adapter() })
二、解决测试断言的对比问题
你之前遇到的预期<form />,实际收到ShallowWrapper {}错误,是因为enzyme的shallow()返回的是一个Wrapper对象,不是原生的React元素,不能直接用toEqual和<form />对比。正确的做法有两种:
方法1:用快照测试(你已经尝试的方案,完善下)
配合enzyme-to-json把Wrapper转成可序列化的对象,再用快照断言:
import React from 'react' import { shallow } from 'enzyme' import { Form } from './form.js' import toJson from 'enzyme-to-json' describe('Test forms', () => { test('render correctly', () => { const wrapper = shallow(<Form />); // 生成/对比快照 expect(toJson(wrapper)).toMatchSnapshot(); }) })
方法2:直接断言Wrapper的属性/结构
如果不想用快照,也可以直接断言Wrapper的类型:
test('render correctly', () => { const wrapper = shallow(<Form />); // 断言渲染的是form元素 expect(wrapper.is('form')).toBe(true); })
三、React组件库的更优构建方案
手动配置Webpack+Babel确实有点繁琐,现在有很多更省心的工具可以替代,推荐几个主流选项:
Vite + Vitest:
Vite是当下最火的构建工具,针对库开发有专门的build.lib配置,默认支持React,打包速度极快。测试可以用Vitest(和Jest语法兼容,但执行速度更快),配置几乎零成本。只需要在vite.config.js里简单设置:import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], build: { lib: { entry: 'src/index.js', name: 'YourComponentLib', fileName: (format) => `your-lib.${format}.js` }, rollupOptions: { // 把React设为外部依赖,不打包进库 external: ['react', 'react-dom'], output: { // 为外部依赖提供全局变量(如果用户用UMD格式) globals: { react: 'React', 'react-dom': 'ReactDOM' } } } } })Rollup:
Rollup是专为库打包设计的工具,比Webpack更轻量,能生成更干净的代码。配合@rollup/plugin-babel、@rollup/plugin-node-resolve等插件,很容易产出ES/CJS等多种格式的包,支持按需加载。Storybook:
不管用哪种构建工具,都建议搭配Storybook,它能帮你快速搭建组件的预览、文档环境,方便开发和测试组件的各种状态,是组件库开发的标配工具。
另外,别忘了把React、React-dom设为peerDependencies,在package.json里配置:
{ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }
这样用户安装你的库时,会复用自己项目里的React版本,避免重复引入,和你最初的思路一致~
内容的提问来源于stack exchange,提问作者Dillan Wilding

