如何让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):
- 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; } }
- ActivityCard.scss
@import './Card.scss'; .activity_card { @extend .cn_app_card; width: 200px; // 覆盖默认宽度 }
- 修改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> ); }
- 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特性实现样式复用,同时避免样式冲突。
示例:
- 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; }
- ActivityCard.module.scss
.card { composes: card from './Card.module.scss'; width: 200px; // 覆盖宽度 }
- 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> ); }
- 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实践。
示例:
- 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; `;
- 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> ); }
- ActivityCard.styled.js:
import styled from 'styled-components'; import { CardContainer } from '../card/Card.styled'; export const ActivityCardContainer = styled(CardContainer)` width: 200px; // 覆盖宽度 `;
- 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
相关产品推荐
相关产品推荐

