前言
书接上回,上次定义了组件库整体的色值和less变量, 本次聚焦于实现组件的样式与变体,首先就需要根据组件的类型、状态等属性,去计算得出组件的类名,然后在less中通过这些变体,去计算出对应类名下组件的样式。两者相结合最终形成组件的样式。
Utils(工具函数)
在组件库的设计中,引入工具模块,为全局的组件提供通用的方法。在这里以独立的文件夹的形式存在。
在packages/ui/src目录下新建一个utils文件夹。
在utils下创建一个统一的出口文件index.ts,通过这种方式为工具函数的引用提供一个统一的出口,也减少在其它组件位置多行的文件导入代码。
warning
utils下新建一个warging.ts,warning主要是实现对报错的封装,统一声明报错的信息, 例如前缀为 mini-ui/ui , 标识错误发生库。
export function warning(
condition: boolean,
message: string,
...options: unknown[]
) {
if(!console || !condition)
return
console.error(`
[@mini-ui/ui]: ${message}
`, ...options)
}
classNames
实现classNames组件之前,先观察classNames原函数。
classNames函数,接收不固定数量的参数, 并且对 string number array object null undefined的类型,进行针对性的处理。下面是函数完成的主要内容。
- 不固定数量的参数可以通过es6中的rest参数,用来获取函数的多余参数。
- 参数类型为string或者number, 作为class的一部分进行拼接。
- 参数类型为array, 就需要递归处理数组中的每一项。
- 参数类型为对象时,首先需要获取到对象的key值,如果key值映射的value值为正,则将key值作为class的一部分进行拼接。如果key映射的value值为负,则不加入class。
- 如果参数非真值, 直接跳过。
import {isArray, isNumber, isObject, isString} from "./is";
import {warning} from "./warning";
type ClassNamesParams = string | number | Record<string, unknown> | Array<ClassNamesParams> | null | undefined
export function classNames (...params: Array<ClassNamesParams>) {
const result: Array<string | number> = []
for (const cls of params) {
if(!cls) {
// 非真值,直接跳过处理
continue
}
if(isString(cls) || isNumber(cls)) {
// string 或者number直接使用
result.push(cls)
}else if(isArray(cls)) {
// 递归处理数组中的每一项
result.push(classNames(...cls))
} else if (isObject(cls)) {
// 遍历object中的每一项,判断value的值,来决定是否跳过key值
for (const key in cls) {
if(cls[key]){
result.push(key)
}
}
} else {
warning(true, 'classname must be string | array | object')
}
}
// 利用Set去重
return [...new Set(result)].join(' ')
}
getPrefix
传入组件的名称,通过getPrefix函数,生成统一的前缀+组件名的内容。
export function getPrefix(componentName: string, prefix?: string){
prefix = prefix || "mini"
return `${prefix}-${componentName}`
}
在packages/ui/src/utils/index中增加导出语句。
export * from "./warning"
export * from "./getPrefix"
export * from "./classNames"
export * from "./is"
utils工具集结构
Button组件
完成了准备工作之后,来逐步的实现Button组件的核心功能以及主要特性, 以下代码为示例代码,大部分的重复代码就省去,主要是跑通整体的流程。
类型与状态
- 状态决定了按钮的主色调,例如:默认状态、成功状态、警告状态、危险状态。
- 类型则进一步的细化按钮的视觉呈现效果,主要分为三种视觉效果:
- 实心按钮
- 描边按钮
- 文本按钮
- 通过这种方式,在不同的状态下,为各种类型的按钮呈现出主色的不同色阶,从而实现丰富多样且一致性强的按钮样式。
实现步骤
- 定义状态和类型的类型枚举,作为组件的props。
- Button文件夹下的types.ts, 声明Button按钮的props类型。
import {CSSProperties, ReactNode} from "react"; export interface ButtonProps { /** * @desc 组件的几种变体形式 * */ type?: "primary" | "default" | "secondary" | "dashed" | "outline" | "text"; /** * @zh * 按钮的状态 * */ status?: "success" | "warning" | "danger" | "default"; className?: string; style?: CSSProperties; children?: ReactNode; } - 根据状态和类型计算样式的逻辑.
- 获取到button组件的前缀名称,保证统一命名规范,避免样式冲突。
- 将状态、类型相关枚举通过模版字符串的方式拼接放入classNames函数中。
import {ButtonProps} from "./interface"; import { getPrefix, classNames as cls } from "../../utils/"; export const Button = ({ status = 'default', type = 'default', className, children, ...rest }: ButtonProps) => { const prefix = getPrefix("btn") const classNames = cls( prefix, `${prefix}-${type}`, `${prefix}-status-${status}`, `${prefix}-size-${size}`, className ) return <button className={classNames} {...rest} > {children} </button> } - 将样式应用到按钮组件,明确按钮组件样式的覆盖关系
- 设计less变量,在变量中包含状态、类型的枚举值,方便下面通过混入的方式快速实现。
- 通过less中的mixins混入的方式,将按钮组件的状态和类型作为变量使用,提高样式的复用性。
// 示例代码 @import url("./token.less"); @btn-prefix: ~'@{prefix}-btn'; .btn-type(@type) { .@{btn-prefix}-@{type}:not(.@{btn-prefix}-disabled) { background-color: ~'@{btn-@{type}-color-bg}'; color: ~'@{btn-@{type}-color-text}'; border: @border-1 @border-solid ~'@{btn-@{type}-color-border}'; &:hover { color: ~'@{btn-@{type}-color-text}'; background-color: ~'@{btn-@{type}-color-bg_hover}'; } &:active { color: ~'@{btn-@{type}-color-text_active}'; background-color: ~'@{btn-@{type}-color-bg_active}'; } } } .btn-size(@size) { .@{btn-prefix}-size-@{size} { height: ~'@{btn-size-@{size}-height}'; } } .btn-type(primary); .btn-type(secondary); .btn-type(outline); .btn-type(dashed); .btn-type(text); .btn-size(mini); .btn-size(small); .btn-size(default); .btn-size(large); .@{btn-prefix} { display: flex; justify-content: center; align-items: center; border-radius: @btn-border-radius; box-sizing: border-box; padding: 0 @btn-padding ; } - 在DEMO中增加展示的不同按钮的类型和状态的tsx文件。
- 在website中的button文档中,引入这个文件,最终展示具体的效果。
# 按钮组件
## 组件的主要变体
<code src="../../../../demo/button/basic-button.tsx" />
## 组件的不同状态
<code src="../../../../demo/button/status-button.tsx" />
- 最终效果
仓库地址
未完待续
下面的一章中,会补全组件的主要功能点, 例如: 指定Button的元素类型, loading状态等