使用vite-plugin-ssr时首次渲染出现React为null、无法读取useState的错误排查求助
使用vite-plugin-ssr时首次渲染出现React为null、无法读取useState的错误排查求助
我现在遇到一个棘手的问题:我正在使用一个通过Vite打包的自定义组件库(打包时已排除React),确认项目里只有一个React实例且React已正确定义,但在使用vite-plugin-ssr(VPS)进行首次渲染时,出现了如下错误。如果我把组件库中的钩子在项目内重新定义,错误就会消失。
react-dom.development.js:20662 Uncaught Error: Cannot read properties of null (reading 'useState') at updateDehydratedSuspenseComponent (react-dom.development.js:20662:17) at updateSuspenseComponent (react-dom.development.js:20362:16) at beginWork (react-dom.development.js:21624:14) at beginWork$1 (react-dom.development.js:27426:14) at performUnitOfWork (react-dom.development.js:26557:12) at workLoopSync (react-dom.development.js:26466:5) at renderRootSync (react-dom.development.js:26434:7) at performConcurrentWorkOnRoot (react-dom.development.js:25738:74) at workLoop (scheduler.development.js:266:34) at flushWork (scheduler.development.js:239:14)
我知道这类错误通常由以下情况导致,但都已经排查过:
- 版本不匹配:确认组件库和前端项目都使用React 18.2.0
- 未声明peer依赖:组件库已正确将react和react-dom列为peer dependency
- 多React实例问题:我在前端项目中给React对象挂载了测试属性,在组件库中能正常读取到该属性,说明是同一个React实例
// 项目中 React.test = 'test' // 组件库中 console.log(React.test) // 输出'test'
我怀疑是组件库的打包配置有问题,导致无法被VPS正确消费?毕竟在不使用VPS的项目里完全没有这个问题。以下是相关配置:
组件库的Vite配置(vite.config.ts)
/// <reference types="vitest" /> import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; import path from 'path'; import dts from 'vite-plugin-dts'; // https://vitejs.dev/config/ export default defineConfig({ build: { lib: { entry: path.resolve(__dirname, 'src/index.ts'), formats: ['es', 'cjs'], name: '@workdigtital/component-library-react', fileName: (format) => `index.${format}.js` }, rollupOptions: { external: ['react', 'react-dom'], output: { globals: { react: 'React', 'react-dom': 'ReactDOM' }, exports: 'named' } } }, plugins: [react(), dts({ insertTypesEntry: true })], test: { globals: true, environment: 'jsdom', setupFiles: ['./src/tests/setup.ts'], exclude: ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**.stories**', '.storybook/**', '**.types**'], coverage: { all: true, exclude: ['**/*.stories.tsx', '.storybook/**'], provider: 'c8', reporter: ['cobertura', 'html'] } }, resolve: { alias: { '@': path.resolve(__dirname, './src'), '@flaticon': path.resolve(__dirname, './node_modules/@flaticon') } } });
前端项目的Vite配置(vite.config.ts)
/// <reference types="vitest" /> import react from '@vitejs/plugin-react'; import autoprefixer from 'autoprefixer'; import { defineConfig } from 'vite'; import circleDependency from 'vite-plugin-circular-dependency'; import ssr from 'vite-plugin-ssr/plugin'; import path from 'path'; console.log(process.env.IS_STORYBOOK ? 'Vite uses plugin set for storybook' : 'Vite uses plugin set for app'); const plugins = process.env.IS_STORYBOOK ? [(react(), circleDependency({}))] : [ (react(), circleDependency({})), ssr({ prerender: true }) ]; // https://vitejs.dev/config/ export default defineConfig({ plugins, test: { globals: true, environment: 'jsdom', setupFiles: ['./src/tests/setup.ts'], exclude: ['**/node_modules/**', '**/dist/**', '**/coverage/**', '**.stories**', '**.types**'], coverage: { all: true, provider: 'c8', reporter: ['cobertura', 'html'] } }, css: { postcss: { plugins: [autoprefixer()] } }, resolve: { alias: { '#': path.resolve(__dirname, './src'), '#components': path.resolve(__dirname, './src/UI/components'), '#layouts': path.resolve(__dirname, './src/UI/layouts'), '#pages': path.resolve(__dirname, './src/pages'), '#providers': path.resolve(__dirname, './src/providers'), '#widgets': path.resolve(__dirname, './src/UI/widgets') } }, ssr: { noExternal: ['primereact'] }, build: { sourcemap: 'inline' } });
附录:组件库中的useWindowSize钩子
function useWindowSize(): TWindowSize { console.log('useWindowSize'); console.log('useWindowSize: React', React); console.log('useWindowSize: useState', useState); console.log('useWindowSize: useState', useEffect); const [windowSize, setWindowSize] = useState<TWindowSize>({ width: 1600, height: 1200 }); useEffect(() => { function handleResize() { console.log('handleResize', React); setWindowSize({ width: window.innerWidth, height: window.innerHeight }); } window.addEventListener('resize', handleResize); handleResize(); return () => window.removeEventListener('resize', handleResize); }, []); return windowSize; } export default useWindowSize;
希望大家能给我一些可以进一步排查的方向,谢谢!
备注:内容来源于stack exchange,提问作者Snake_py
相关产品推荐
相关产品推荐

