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

React/Vite构建UI组件库时ThemeProvider失效问题求助

Datepicker组件构建后报错:Cannot read 'white' of undefined

基于React和Vite开发UI组件库,引入Datepicker组件后执行npm run build出现错误:Cannot read property 'white' of undefined,原因是样式中访问theme.colors.white时theme为undefined,推测styled-components的<ThemeProvider>未生效,开发环境正常,仅构建后出现问题。

相关代码

Datepicker.tsx

import { useState } from "react";
import InputMask from "react-input-mask";
import { setDefaultOptions } from "date-fns";
import { ThemeProvider } from "styled-components";
import theme from "../../styles/theme";
import GlobalStyles from "../../styles/global";

import {
  DatepickerInputProps,
  ViewModeEnum,
  DateObject,
} from "./Datepicker.types";
import { DatepickerContent } from "./elements/DatepickerContent";
import { DatepickerHelper } from "./Datepicker.helper";

import * as S from "./Datepicker.styles";

export function Datepicker({
  variant = "light",
  isDisabled,
  placeholder,
  isRange = false,
  config,
}: DatepickerInputProps) {
  const initialState = isRange
    ? { startDate: new Date(), endDate: null }
    : new Date();
  const maskValue = DatepickerHelper.getMaskValue(isRange, config?.dateFormat);

  const [selectedDate, setSelectedDate] = useState<Date | DateObject>(
    initialState
  );
  const [activeDate, setActiveDate] = useState(new Date());
  const [dateValue, setDateValue] = useState("");
  const [isOpen, setIsOpen] = useState(false);
  const [viewMode, setViewMode] = useState(ViewModeEnum.DAYS);
  const [error, setError] = useState(false);

  const locale = config?.locale || "en-US";
  const language = DatepickerHelper.getLanguage(locale);
  const errorMessage =
    config?.inputStyles?.errorConfig?.errorText || "Invalid Date";

  setDefaultOptions({ locale: language });

  const handleOpenCalendar = () => {
    setViewMode(ViewModeEnum.DAYS);

    let parsedDate;

    if (isRange) {
      parsedDate = DatepickerHelper.stringToRangeDateOrNewDate(
        dateValue,
        config?.dateFormat
      );
      setActiveDate(parsedDate.startDate);
    } else {
      parsedDate = DatepickerHelper.stringToDateOrNewDate(
        dateValue,
        config?.dateFormat
      );
      setActiveDate(parsedDate);
    }

    setSelectedDate(parsedDate);
    setIsOpen(!isOpen);
  };

  return (
    <ThemeProvider theme={theme}>
      <GlobalStyles />

      <S.Wrapper
        isDisabled={isDisabled}
        config={config}
        variant={variant}
        error={error}
      >
        <S.InputWrapper>
          <InputMask
            mask={maskValue}
            maskChar=""
            disabled={isDisabled}
            value={dateValue}
            onBlur={() =>
              DatepickerHelper.checkIfDateIsValid(
                dateValue,
                isRange,
                setError,
                config?.dateFormat
              )
            }
            onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
              setDateValue(e.target.value)
            }
          >
            {/* @ts-expect-error external lib */}
            {(inputProps) => (
              <S.Input
                type="text"
                placeholder={placeholder}
                disabled={isDisabled}
                {...inputProps}
              />
            )}
          </InputMask>

          <S.DatepickerWrapper
            onBlur={() => {
              setIsOpen(false);
              DatepickerHelper.checkIfDateIsValid(
                dateValue,
                isRange,
                setError,
                config?.dateFormat
              );
            }}
            disabled={isDisabled}
          >
            <S.Icon
              className="fi fi-rr-calendar"
              onClick={() => handleOpenCalendar()}
            />
            {isOpen && (
              <DatepickerContent
                config={config}
                variant={variant}
                viewMode={viewMode}
                activeDate={activeDate}
                selectedDate={selectedDate}
                dateValue={dateValue}
                isRange={isRange}
                onChangeViewMode={setViewMode}
                onChangeActiveDate={setActiveDate}
                onChangeSelectedDate={setSelectedDate}
                onChangeDateValue={setDateValue}
                onOpen={setIsOpen}
                onError={setError}
              />
            )}
          </S.DatepickerWrapper>
        </S.InputWrapper>
        {error && (
          <S.Error>
            <S.ErrorIcon className="fi fi-rr-exclamation" />
            <span>{errorMessage}</span>
          </S.Error>
        )}
      </S.Wrapper>
    </ThemeProvider>
  );
}

package.json

{
  "name": "react-lib-ui",
  "private": false,
  "version": "0.0.0",
  "type": "module",
  "main": "dist/main.js",
  "types": "dist/main.d.ts",
  "files": [
    "dist"
  ],
  "scripts": {
    "dev": "vite",
    "build": "tsc --p ./tsconfig-build.json && vite build",
    "prepublishOnly": "npm run build",
    "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
    "preview": "vite preview"
  },
  "peerDependencies": {
    "react": ">= 16.8.0",
    "react-dom": ">= 16.8.0"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "@flaticon/flaticon-uicons": "^3.1.0",
    "date-fns": "^3.3.1",
    "react-input-mask": "^2.0.4",
    "styled-components": "^6.1.8"
  },
  "devDependencies": {
    "@types/node": "^20.11.19",
    "@types/react": "^18.2.55",
    "@types/react-input-mask": "^3.0.5",
    "@typescript-eslint/eslint-plugin": "^6.21.0",
    "@typescript-eslint/parser": "^6.21.0",
    "@vitejs/plugin-react": "^4.2.1",
    "eslint": "^8.56.0",
    "eslint-plugin-react-hooks": "^4.6.0",
    "eslint-plugin-react-refresh": "^0.4.5",
    "glob": "^10.3.10",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "typescript": "^5.2.2",
    "vite": "^5.1.0",
    "vite-plugin-dts": "^3.7.2",
    "vite-plugin-lib-inject-css": "^1.3.0"
  },
  "sideEffects": [
    "**/*.css"
  ]
}

调用组件的App.tsx

import "./App.css";
import { Datepicker } from "../";

function App() {
  return (
    <>
      <Datepicker />
    </>
  );
}

export default App;

报错情况

执行npm run build后引入组件,出现Cannot read property 'white' of undefined错误,指向样式中theme.colors.white的访问,说明<ThemeProvider>未成功注入主题对象。

解决方案

1. 调整styled-components依赖位置

将styled-components从dependencies移至peerDependencies,避免组件库与项目重复安装导致版本冲突:

"peerDependencies": {
  "react": ">= 16.8.0",
  "react-dom": ">= 16.8.0",
  "styled-components": ">= 6.0.0"
},
"dependencies": {
  "@flaticon/flaticon-uicons": "^3.1.0",
  "date-fns": "^3.3.1",
  "react-input-mask": "^2.0.4"
},

2. 配置Vite支持styled-components

在vite.config.ts中添加babel插件支持styled-components的编译,确保主题正确注入:

import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import injectCss from 'vite-plugin-lib-inject-css';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-styled-components'],
      },
    }),
    dts(),
    injectCss(),
  ],
  build: {
    lib: {
      entry: 'src/main.ts',
      name: 'ReactLibUI',
      formats: ['es', 'umd'],
      fileName: (format) => `main.${format}.js`,
    },
    rollupOptions: {
      external: ['react', 'react-dom', 'styled-components'],
      output: {
        globals: {
          react: 'React',
          'react-dom': 'ReactDOM',
          'styled-components': 'styled',
        },
      },
    },
  },
});

3. 检查主题文件导出

确保theme.ts是默认导出,避免导入时获取到undefined:

// src/styles/theme.ts
export default {
  colors: {
    white: '#ffffff',
    // 其他颜色配置
  },
  // 其他主题属性
};

4. 确保子组件在ThemeProvider范围内

检查DatepickerContent内部的styled组件是否被ThemeProvider正确包裹,避免子组件脱离主题上下文。

5. 禁用Tree Shaking对主题文件的影响

在package.json的sideEffects中添加主题文件路径,防止构建时被Tree Shaking移除:

"sideEffects": [
  "**/*.css",
  "src/styles/theme.ts"
]

执行以上步骤后重新构建组件库,问题应该可以解决。

内容的提问来源于stack exchange,提问作者Herbert Cabus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:25:54