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

如何让React可复用组件的默认样式与自定义样式兼容?

问题

我正在开发React应用,尝试打造类似Bootstrap、AntDesign的可复用组件。首先开发Card组件,计划将其加入GitLab的企业全局组件库commun_lib,实现跨应用复用。

当前实现方式如下:

Card.jsx

import React from "react";
import {defaultStyle} from "./Card.style"

export default function Card({ title, content, style = defaultStyle }) {
  return (
    <div style={style.cn_app_card}>
      {title ? (
        <div style={style.cn_app_card_header}>
          <span style={style.cn_app_cardTitle}>{title}</span>
        </div>
      ) : (
        <></>
      )}
      <div style={style.cn_app_cardContainer}>{content}</div>
    </div>
  );
}

Card.style.js

export var defaultStyle = {
  cn_app_card: {
    width: 500,
    height: 508,
    backgroundColor: "blue",
  },

  cn_app_card_header: {
    backgroundColor: "green",
  },

  cn_app_cardTitle: {
    color: "green",
    lineHeight: "16px",
    fontSize: "13px",
    marginLeft: "10px",
  },

  cn_app_cardContainer: {
    padding: "6px 10px 6px 10px",
    display: "flex",
    flexDirection: "column",
    boxSizing: "content-box",
    justifyContent: "center",
  }
};

ActivityCard.jsx

import React from "react";
import Card from "../../../../shared/components/card/Card";
import { ActivityCard_style } from "./ActivityCard.style";

export default function ActivityCard({ title, indexOfPeriod }) {
// ...其他逻辑
const cardTitle = title;
const cardContent = /* 内容组件 */;

return <Card title={cardTitle} content={cardContent} style={ActivityCard_style}></Card>;
}

ActivityCard.style.js

export var ActivityCard_style = {
  cn_app_card: {
    width: 200,
    height: 508,
  },

  cn_app_card_header: {
    backgroundColor: "green",
  },

  cn_app_cardTitle: {
    color: "green",
    marginLeft: "10px",
  },

  cn_app_cardContainer: {
    padding: "6px 10px 6px 10px",
  },
};

原本期望ActivityCard能继承Card的defaultStyle并叠加自定义样式,但实际只有自定义样式中重写的属性生效,未重写的默认样式丢失,需重复定义所有属性,不符合复用目标。请问如何实现低冗余的可复用组件样式方案?


解决方案

方案1:手动合并默认样式与自定义样式

在Card组件内部将传入的自定义样式和默认样式做深层合并,而非直接替换。可以用ES6扩展运算符实现简易合并,或用lodash的merge方法处理复杂场景。

修改Card.jsx:

import React from "react";
import { defaultStyle } from "./Card.style";

// 自定义深层合并函数(无需第三方库)
const mergeStyles = (defaultStyles, customStyles) => {
  const merged = {...defaultStyles};
  Object.keys(customStyles).forEach(key => {
    if (typeof customStyles[key] === 'object' && customStyles[key] !== null) {
      merged[key] = {...defaultStyles[key], ...customStyles[key]};
    } else {
      merged[key] = customStyles[key];
    }
  });
  return merged;
};

export default function Card({ title, content, style = {} }) {
  // 合并样式:默认样式为基础,自定义样式覆盖对应属性
  const mergedStyle = mergeStyles(defaultStyle, style);

  return (
    <div style={mergedStyle.cn_app_card}>
      {title ? (
        <div style={mergedStyle.cn_app_card_header}>
          <span style={mergedStyle.cn_app_cardTitle}>{title}</span>
        </div>
      ) : null}
      <div style={mergedStyle.cn_app_cardContainer}>{content}</div>
    </div>
  );
}

简化ActivityCard.style.js:

export var ActivityCard_style = {
  cn_app_card: {
    width: 200, // 仅保留需要修改的属性,其余继承默认样式
  },
};

方案2:使用CSS预处理器(Sass/Less)

放弃内联样式,利用预处理器的继承、混合功能实现样式复用,更适合大型组件库维护。

示例(Sass):

  1. Card.scss
.cn_app_card {
  width: 500px;
  height: 508px;
  background-color: blue;

  &_header {
    background-color: green;
  }

  &_cardTitle {
    color: green;
    line-height: 16px;
    font-size: 13px;
    margin-left: 10px;
  }

  &_cardContainer {
    padding: 6px 10px;
    display: flex;
    flex-direction: column;
    box-sizing: content-box;
    justify-content: center;
  }
}
  1. ActivityCard.scss
@import './Card.scss';

.activity_card {
  @extend .cn_app_card;
  width: 200px; // 覆盖默认宽度
}
  1. 修改Card组件使用className:
import React from "react";
import "./Card.scss";

export default function Card({ title, content, className = '' }) {
  const cardClass = `cn_app_card ${className}`;
  return (
    <div className={cardClass}>
      {title ? (
        <div className="cn_app_card_header">
          <span className="cn_app_cardTitle">{title}</span>
        </div>
      ) : null}
      <div className="cn_app_cardContainer">{content}</div>
    </div>
  );
}
  1. ActivityCard使用自定义className:
import React from "react";
import Card from "../../../../shared/components/card/Card";
import "./ActivityCard.scss";

export default function ActivityCard({ title, indexOfPeriod }) {
  // ...逻辑
  return <Card title={cardTitle} content={cardContent} className="activity_card"></Card>;
}

方案3:使用CSS Modules

通过CSS Modules的composes特性实现样式复用,同时避免样式冲突。

示例:

  1. Card.module.scss
.card {
  width: 500px;
  height: 508px;
  background-color: blue;
}

.header {
  background-color: green;
}

.cardTitle {
  color: green;
  line-height: 16px;
  font-size: 13px;
  margin-left: 10px;
}

.cardContainer {
  padding: 6px 10px;
  display: flex;
  flex-direction: column;
  box-sizing: content-box;
  justify-content: center;
}
  1. ActivityCard.module.scss
.card {
  composes: card from './Card.module.scss';
  width: 200px; // 覆盖宽度
}
  1. Card组件:
import React from "react";
import styles from "./Card.module.scss";

export default function Card({ title, content, customStyles = {} }) {
  return (
    <div className={`${styles.card} ${customStyles.card || ''}`}>
      {title ? (
        <div className={`${styles.header} ${customStyles.header || ''}`}>
          <span className={`${styles.cardTitle} ${customStyles.cardTitle || ''}`}>{title}</span>
        </div>
      ) : null}
      <div className={`${styles.cardContainer} ${customStyles.cardContainer || ''}`}>{content}</div>
    </div>
  );
}
  1. ActivityCard:
import React from "react";
import Card from "../../../../shared/components/card/Card";
import customStyles from "./ActivityCard.module.scss";

export default function ActivityCard({ title, indexOfPeriod }) {
  // ...逻辑
  return <Card title={cardTitle} content={cardContent} customStyles={customStyles}></Card>;
}

方案4:使用styled-components(CSS-in-JS)

利用styled-components的组件继承特性,实现样式复用与扩展,适合React项目的CSS-in-JS实践。

示例:

  1. Card.styled.js
import styled from 'styled-components';

export const CardContainer = styled.div`
  width: 500px;
  height: 508px;
  background-color: blue;
`;

export const CardHeader = styled.div`
  background-color: green;
`;

export const CardTitle = styled.span`
  color: green;
  line-height: 16px;
  font-size: 13px;
  margin-left: 10px;
`;

export const CardContent = styled.div`
  padding: 6px 10px;
  display: flex;
  flex-direction: column;
  box-sizing: content-box;
  justify-content: center;
`;
  1. Card组件:
import React from "react";
import { CardContainer, CardHeader, CardTitle, CardContent } from "./Card.styled";

export default function Card({ title, content, StyledContainer = CardContainer, StyledHeader = CardHeader, StyledTitle = CardTitle, StyledContent = CardContent }) {
  return (
    <StyledContainer>
      {title ? (
        <StyledHeader>
          <StyledTitle>{title}</StyledTitle>
        </StyledHeader>
      ) : null}
      <StyledContent>{content}</StyledContent>
    </StyledContainer>
  );
}
  1. ActivityCard.styled.js:
import styled from 'styled-components';
import { CardContainer } from '../card/Card.styled';

export const ActivityCardContainer = styled(CardContainer)`
  width: 200px; // 覆盖宽度
`;
  1. ActivityCard:
import React from "react";
import Card from "../../../../shared/components/card/Card";
import { ActivityCardContainer } from "./ActivityCard.styled";

export default function ActivityCard({ title, indexOfPeriod }) {
  // ...逻辑
  return <Card title={cardTitle} content={cardContent} StyledContainer={ActivityCardContainer}></Card>;
}

内容的提问来源于stack exchange,提问作者Aya Abdessalem

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 11:20:37