如何在React UI库中通过Props结合SCSS为组件设置样式
基于React+Storybook+TypeScript+Rollup的UI组件库样式控制方案
背景概述
我用React + Storybook + TypeScript + Rollup构建了一个UI组件库,要求前端不可修改组件代码,所有组件变体必须在库内定义,仅通过传递props使用。为追求高性能、低冗余,不使用styled-components、Material UI、Bootstrap这类UI库。
前端使用示例
<Button size='small' color='primary'> Test </Button>
当前Button组件代码
import React, { MouseEventHandler } from 'react'; import './Button.scss' export interface ButtonProps { children: React.ReactNode, color: 'primary' | 'secondary', disabled?: boolean, size?: 'small' | 'large', onClick?: MouseEventHandler<HTMLButtonElement> } const Button: React.FC<ButtonProps> = ({size, color, disabled, children, onClick, ...props}) => { return ( <button type="button"> {children} </button> ) } export default Button
1. 不使用styled-components,通过props控制组件样式(SCSS实现)
核心思路是根据props动态生成CSS类名,在SCSS中针对不同类名编写样式变体。
步骤1:组件中拼接类名
修改Button组件,根据传入的size、color、disabled属性生成对应的类名:
const Button: React.FC<ButtonProps> = ({size = 'large', color = 'primary', disabled = false, children, onClick, ...props}) => { // 基础类名 + 变体类名,过滤空值后拼接 const className = [ 'ui-button', size ? `ui-button--size-${size}` : '', color ? `ui-button--color-${color}` : '', disabled ? 'ui-button--disabled' : '' ].filter(Boolean).join(' '); return ( <button type="button" className={className} disabled={disabled} onClick={onClick} {...props} > {children} </button> ) }
步骤2:SCSS中编写样式变体
采用BEM命名规范编写Button.scss,避免样式冲突:
// 基础样式 .ui-button { border: none; border-radius: 4px; cursor: pointer; padding: 8px 16px; transition: background-color 0.2s ease; // 尺寸变体 &--size-small { padding: 4px 8px; font-size: 14px; } &--size-large { padding: 12px 24px; font-size: 18px; } // 颜色变体 &--color-primary { background-color: #165DFF; color: #FFFFFF; &:hover:not(&--disabled) { background-color: #0D47A1; } } &--color-secondary { background-color: #F5F7FA; color: #1D2129; border: 1px solid #E5E6EB; &:hover:not(&--disabled) { background-color: #E5E6EB; } } // 禁用状态 &--disabled { opacity: 0.6; cursor: not-allowed; } }
2. TypeScript枚举的合理性及实现
合理性分析
使用TS枚举非常合理,核心优势:
- 统一管理变体值,避免拼写错误(比如把
small写成sm) - 提升IDE代码提示性,自动补全枚举值
- 便于后续扩展(比如新增
medium尺寸)
实现方法
- 在组件目录下创建
Button.enums.ts(或统一放在src/enums/目录):
// Button.enums.ts export enum ButtonSize { Small = 'small', Large = 'large' } export enum ButtonColor { Primary = 'primary', Secondary = 'secondary' }
- 修改
ButtonProps接口,用枚举替代字面量类型:
import { ButtonSize, ButtonColor } from './Button.enums'; export interface ButtonProps { children: React.ReactNode, color: ButtonColor, disabled?: boolean, size?: ButtonSize, onClick?: MouseEventHandler<HTMLButtonElement> }
- 前端使用时直接导入枚举:
import { Button, ButtonSize, ButtonColor } from 'your-ui-library'; <Button size={ButtonSize.Small} color={ButtonColor.Primary}> Test </Button>
3. SCSS的最佳结构设计
结合你的项目结构(src/下含components/、stories/,components/下按组件分目录),推荐以下SCSS结构:
src/ ├── styles/ # 全局样式目录 │ ├── variables.scss # 全局变量(颜色、字号、间距等) │ ├── reset.scss # 全局样式重置 │ └── mixins.scss # 全局混合宏(响应式、清除浮动等) └── components/ ├── Button/ │ ├── Button.tsx │ ├── Button.scss # 组件专属样式 │ └── Button.enums.ts └── ...其他组件
结构说明
全局样式层:
variables.scss:存放所有全局共享变量,比如主题色、字号基准值reset.scss:统一浏览器默认样式,比如设置box-sizing: border-box、清除默认margin/paddingmixins.scss:封装可复用SCSS逻辑,比如@mixin responsive($breakpoint)实现响应式布局
组件样式层:
- 每个组件的SCSS文件仅负责该组件样式,通过BEM命名避免冲突
- 组件样式中导入全局变量,保持样式统一:
// Button.scss @import '../../styles/variables.scss'; .ui-button { &--color-primary { background-color: $color-primary; color: $color-white; } }
4. 创建包含全局变量的全局样式
步骤1:定义全局变量
src/styles/variables.scss:
// 主题颜色 $color-primary: #165DFF; $color-secondary: #F5F7FA; $color-white: #FFFFFF; $color-text: #1D2129; // 字号系统 $font-size-xs: 12px; $font-size-sm: 14px; $font-size-base: 16px; $font-size-lg: 18px; $font-size-xl: 20px; // 间距基准 $spacing-xs: 4px; $spacing-sm: 8px; $spacing-base: 16px; $spacing-lg: 24px; $spacing-xl: 32px;
步骤2:全局样式注入
- 创建
src/styles/index.scss,导入所有全局样式:
@import './reset.scss'; @import './variables.scss'; @import './mixins.scss';
- 在库的入口文件(比如
src/index.ts)中导入全局样式:
import './styles/index.scss'; export { default as Button } from './components/Button/Button'; // 导出其他组件...
- 前端使用时,用户导入库的入口文件即可自动加载全局样式;也可单独引入:
import 'your-ui-library/dist/styles/index.css';
内容的提问来源于stack exchange,提问作者Luck dev
相关产品推荐
相关产品推荐

