You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用含useState的自定义React UI库组件时出现TypeError求助

问题:React UI库在Next.js应用中使用useState时抛出TypeError错误

我基于React、Sass、Storybook、TypeScript和Rollup搭建了个人UI库,用来统一应用组件规范。之前一切正常,但使用包含useState Hook的组件时,应用抛出错误:

TypeError: Cannot read properties of null (reading 'useState')

主前端应用基于Next.js、Axios、Yup、Formik和TypeScript构建,不含useState的组件(比如Button)能正常使用,但所有带useState的组件都会触发这个错误。

我试过调整peerDependencies、修改Rollup的external配置等操作,都没解决,而且Rollup打包过程没有报错。相关配置与代码如下:

库的package.json

{"name": "@lucasReghini/library","publishConfig": {"registry": "https://npm.pkg.github.com/lucasReghini"},"version": "0.0.11","description": "react ui library","license": "MIT","scripts": {"rollup": "rollup -c","storybook": "start-storybook -p 6006","build-storybook": "build-storybook"},"author": "Lucas Reghini Magalhães","devDependencies": {"@babel/core": "^7.18.10","@rollup/plugin-commonjs": "^22.0.2","@rollup/plugin-image": "^2.1.1","@rollup/plugin-node-resolve": "^13.3.0","@rollup/plugin-typescript": "^8.3.4","@storybook/addon-actions": "^6.5.10","@storybook/addon-essentials": "^6.5.10","@storybook/addon-interactions": "^6.5.10","@storybook/addon-links": "^6.5.10","@storybook/builder-webpack5": "^6.5.10","@storybook/manager-webpack5": "^6.5.10","@storybook/preset-scss": "^1.0.3","@storybook/react": "^6.5.10","@storybook/testing-library": "^0.0.13","babel-loader": "^8.2.5","classnames": "^2.3.1","css-loader": "^6.7.1","postcss": "^8.4.16","rollup": "^2.78.1","rollup-plugin-dts": "^4.2.2","rollup-plugin-postcss": "^4.0.2","sass": "^1.54.5","sass-loader": "^13.0.2","style-loader": "^3.3.1","tslib": "^2.4.0","typescript": "^4.7.4","@types/react": ">=18","react": ">=18","react-dom": ">=18"},"main": "dist/cjs/index.js","module": "dist/esm/index.js","files": ["dist"],"types": "dist/index.d.ts","dependencies": {"classnames": "^2.3.1"},"peerDependencies": {"@types/react": ">=18","react": ">=18","react-dom": ">=18"}}

库的rollup.config.js

import resolve from "@rollup/plugin-node-resolve";import commonjs from "@rollup/plugin-commonjs";import typescript from "@rollup/plugin-typescript";import dts from "rollup-plugin-dts";import postcss from "rollup-plugin-postcss";import image from '@rollup/plugin-image';

const packageJson = require("./package.json");

export default [{
    input: "src/index.ts",
    output: [{
        file: packageJson.main,
        format: "cjs",
        sourcemap: true,
    },{
        file: packageJson.module,
        format: "esm",
        sourcemap: true,
    },],
    plugins: [resolve(),commonjs(),typescript({ tsconfig: "./tsconfig.json" }),postcss(),image()],
},{
    input: "dist/esm/index.d.ts",
    output: [{ file: "dist/index.d.ts", format: "esm" }],
    plugins: [dts()],
    external: [/\.(css|less|scss)$/],
},];

库的tsconfig.json

{"compilerOptions": {"target": "es5","esModuleInterop": true,"forceConsistentCasingInFileNames": true,"strict": true,"skipLibCheck": true,"jsx": "react","module": "ESNext","declaration": true,"declarationDir": "types","sourceMap": true,"outDir": "dist","moduleResolution": "node","allowSyntheticDefaultImports": true,"emitDeclarationOnly": true,"rootDir": "src"}}

报错的Input组件代码

import React, { ChangeEventHandler, useState } from 'react';import './Input.scss'import classNames from 'classnames'import { inputTypes } from '../../enums/presets';import { eyesClosed, eyesOpen } from '../../icons'

export interface InputProps {
    id?: string,
    label?: string,
    error?:boolean,
    message?: string,
    disabled?: boolean,
    autoComplete?: boolean,
    isPassword?: boolean,
    types: inputTypes,
    onChange?: ChangeEventHandler<HTMLInputElement>
}


const Input: React.FC<InputProps> = ({id, disabled, label, message, error, onChange, autoComplete, isPassword, types, ...props}) => {
    const [isView, setIsView] = useState(false)
    
    const classNameInput = classNames({
        form__input : true,
        'error': error,
        'font-16': true,
    });

    const type = isPassword ? isView ? 'text' : 'password'  : types 

    return (
        <div className='form'>
            <input onChange={onChange} className={classNameInput} type={type} id={id || 'ipt'} autoComplete={autoComplete ?'' : 'off'}  placeholder=" "/>
            <label className='form__label' >{label}</label>
            {isPassword && <img className='icon' src={isView ? eyesClosed : eyesOpen} onClick={() => setIsView(e => !e)}/>}
            {error && <span className='message'>{message}</span>}
        </div>
    )
} 

export default Input

主应用的package.json

{"name": "main-frontend","version": "0.1.0","private": true,"scripts": {"dev": "next dev","build": "next build","start": "next start","lint": "next lint"},"dependencies": {"@lucasReghini/library": "^0.0.11","@next/bundle-analyzer": "^12.2.5","@reduxjs/toolkit": "^1.8.5","axios": "^0.27.2","babel-plugin-inline-react-svg": "^2.0.1","formik": "^2.2.9","next": "12.2.5","next-images": "^1.8.4","react": "18.2.0","react-dom": "18.2.0","react-redux": "^8.0.2","yup": "^0.32.11"},"devDependencies": {"@types/node": "18.7.13","@types/react": "18.0.17","@types/react-dom": "18.0.6","eslint": "8.22.0","eslint-config-next": "12.2.5","sass": "^1.54.5","typescript": "4.7.4"}}

请问有人遇到过此类问题吗?是否与库基于React、主应用基于Next.js有关?


解决方案

这个错误的核心原因是UI库打包时将React代码一同打包进了产物中,导致主应用中出现两个不同的React实例:一个是主应用自身的React,另一个是UI库打包进去的React。Hook只能在同一个React实例的组件树中正常工作,多实例会导致Hook调用失败。

修复步骤:

  1. 调整Rollup配置,将React相关依赖标记为外部依赖
    修改rollup.config.js,在第一个打包配置中添加external字段,明确排除React、ReactDOM:

    export default [{
        input: "src/index.ts",
        output: [{
            file: packageJson.main,
            format: "cjs",
            sourcemap: true,
        },{
            file: packageJson.module,
            format: "esm",
            sourcemap: true,
        },],
        // 添加这行,将React相关依赖设为外部
        external: ["react", "react-dom"],
        plugins: [resolve(),commonjs(),typescript({ tsconfig: "./tsconfig.json" }),postcss(),image()],
    },{
        input: "dist/esm/index.d.ts",
        output: [{ file: "dist/index.d.ts", format: "esm" }],
        plugins: [dts()],
        external: [/\.(css|less|scss)$/],
    },];
    
  2. 确保UI库的React依赖仅存在于devDependencies
    你的package.json已经将React、ReactDOM放在devDependencies中,这部分是对的,保持即可——peerDependencies声明了对React版本的要求,devDependencies仅用于开发时本地构建,不会被打包进产物。

  3. 重新打包并发布UI库
    执行npm run rollup重新打包,然后重新发布到你的私有仓库。

  4. 主应用重新安装依赖
    在主应用中执行npm install @lucasReghini/library@latest,确保安装的是修复后的版本。

额外验证:

可以检查UI库打包后的dist/cjs/index.js或dist/esm/index.js,搜索useState或React,如果没有看到React的源码被打包进去,而是以require("react")或import React from 'react'的形式存在,说明配置生效了。

这个问题和Next.js本身无关,本质是UI库打包时没有正确隔离React依赖导致的多实例冲突。


内容的提问来源于stack exchange,提问作者Luck dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 12:10:37