前言
antd 5.0 正式发布已经有一段时间了,发布当天,一心二用的看完直播。很喜欢整个设计,有种简约快乐工作的感觉,某些功能设计初衷也给了我一些启发。
antd 5.0 提供的主题定制挺酷炫的,加之我最近对「CSS-in-JS」很感兴趣。于是迫不及待的打开了它的源码,准备研究一番 。
我大部分情况下都是通过碎片化的时间来研究技术,所以时间合理配置和任务合理分块,一直是我常采用的方式。加上对源码阅读的经验不足,所以此次的阅读之旅,我将详细记录阅读前的思考、阅读规划以及收获,并将破冰心得总结之后分享出来。
文章速读
阅读文章,可以有以下收获:
当我想阅读一份源码
我源码阅读的经验较少,大部分时候阅读源码的目的是寻找文档上没有写的参数或者API。
之前有过几个完整的阅读经验,但是源码项目相对简单,阅读过程快速且顺畅。
@ant-design/cssinjs 的源码,由于其文档内容较少,很多参数单纯靠代码内容无法确定其准确的结构,所以在进一步的研究之前,我进行了一场深思。
阅读前的思考
我认真思考了几个问题:
- 我阅读这个源码的目标是什么?
- 我要把所有功能梳理的一清二楚吗?
- 遇到复杂的源码,没有完整的时间,我应该怎么合理做规划?
- 明确方案之后,我具体应该怎么去实现?
- 源码阅读完之后呢?仅仅写篇文章,还是真的应用到工作中?
阅读源码的目标
对于 @ant-design/cssinjs,我的目标很明确,搞懂它是如何实现动态主题的。
功能梳理全还是精?
如果我不明确这个问题,我有可能读的时候会分散,看到哪,读到哪。
如果我要梳理完全,可能会无形中给自己很多压力。
如果我只看重点功能,那我怎么确定哪些是重点功能呢?
如下为功能梳理的全部情况流程图
无论是全部读完,还是挑重点功能阅读,都推荐进行功能细化,将事情拆分成多个小项。
如何规划
我习惯分类归纳学习经验,按照技术类型区分。这次源码阅读虽然有之前的经验,但是之前的源码整体难度不是很高。后面可能还会阅读内容更为复杂的开源项目,现阶段还是一个积累经验的过程。
这次是一个很好的尝试,后面我会归纳出一套完整的经验。
怎么开始
我本来尝试从测试用例开始,发现运行之后不是我想要的结果,我想到其实还有一条路可以试试,那就是 demo。
通过 demo 既可以查看效果,又可以打印各种参数。
从 demo 开始
前期的准备工作就绪,正式开启阅读之旅。
混合主题
默认主题样式
// docs/examples/components/theme.tsx
// 默认主题样式
const defaultDesignToken: DesignToken = {
primaryColor: '#1890ff',
textColor: '#333333',
reverseTextColor: '#FFFFFF',
componentBackgroundColor: '#FFFFFF',
borderRadius: 2,
borderColor: 'black',
borderWidth: 1,
};
这个默认主题样式,通过变量名大致能猜出来各自代表什么。但是具体应用在哪些元素上,还需要通过使用设置才能确定。
按钮组件
// docs/examples/components/Button.tsx
// 按钮组件
import React from 'react';
import classNames from 'classnames';
import { useToken } from './theme';
import type { DerivativeToken } from './theme';
import { useStyleRegister } from '../../../src/';
import type { CSSInterpolation, CSSObject } from '../../../src/';
// 通用框架
const genSharedButtonStyle = (
prefixCls: string,
token: DerivativeToken,
): CSSInterpolation => ({
[`.${prefixCls}`]: {
borderColor: token.borderColor, // 边框颜色
borderWidth: token.borderWidth, // 边框宽度
borderRadius: token.borderRadius, // 边框圆角
cursor: 'pointer',
transition: 'background 0.3s',
},
});
// 实心底色样式
// 返回数组,第一个元素是通用样式,第二个元素是自定义样式,需要调用者传入
const genSolidButtonStyle = (
prefixCls: string,
token: DerivativeToken,
postFn: () => CSSObject,
): CSSInterpolation => [
genSharedButtonStyle(prefixCls, token),
{
[`.${prefixCls}`]: {
...postFn(),
},
},
];
// 默认样式
const genDefaultButtonStyle = (
prefixCls: string,
token: DerivativeToken,
): CSSInterpolation => {
genSolidButtonStyle(prefixCls, token, () => ({
backgroundColor: token.componentBackgroundColor, // 默认样式的背景颜色
color: token.textColor, // 默认样式的字体颜色
'&:hover': {
borderColor: token.primaryColor, // 默认样式的经过时边框颜色
color: token.primaryColor, // 默认样式的经过时字体颜色
},
}));
};
// 主色样式
const genPrimaryButtonStyle = (
prefixCls: string,
token: DerivativeToken,
): CSSInterpolation =>
genSolidButtonStyle(prefixCls, token, () => ({
backgroundColor: token.primaryColor, // 主色样式的背景颜色
border: `${token.borderWidth}px solid ${token.primaryColor}`, // 主色样式的边框样式
color: token.reverseTextColor, // 主色样式的字体样式
'&:hover': {
backgroundColor: token.primaryColorDisabled, // 主色样式的经过时背景颜色
},
}));
// 透明按钮
const genGhostButtonStyle = (
prefixCls: string,
token: DerivativeToken,
): CSSInterpolation => [
genSharedButtonStyle(prefixCls, token),
{
[`.${prefixCls}`]: {
backgroundColor: 'transparent', // 透明样式的背景颜色
color: token.primaryColor, // 透明样式的字体颜色
border: `${token.borderWidth}px solid ${token.primaryColor}`, // 透明样式的边框颜色
'&:hover': {
borderColor: token.primaryColor, // 透明样式的经过时背景颜色
color: token.primaryColor,
},
},
},
];
interface ButtonProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'type'> {
type?: 'primary' | 'ghost' | 'dashed' | 'link' | 'text' | 'default';
}
const Button = ({ className, type, ...restProps }: ButtonProps) => {
const prefixCls = 'ant-btn';
// 【自定义】制造样式
const [theme, token, hashId] = useToken();
// default 添加默认样式选择器后可以省很多冲突解决问题
const defaultCls = `${prefixCls}-default`;
const primaryCls = `${prefixCls}-primary`;
const ghostCls = `${prefixCls}-ghost`;
// 全局注册,内部会做缓存优化
// 目前是三种类型的样式:默认样式、主色样式、透明样式
const wrapSSR = useStyleRegister(
{ theme, token, hashId, path: [prefixCls] },
() => [
genDefaultButtonStyle(defaultCls, token),
genPrimaryButtonStyle(primaryCls, token),
genGhostButtonStyle(ghostCls, token),
],
);
const typeCls =
(
{
primary: primaryCls,
ghost: ghostCls,
} as any
)[type as any] || defaultCls;
return wrapSSR(
<button
className={classNames(prefixCls, typeCls, hashId, className)}
{...restProps}
/>,
);
};
export default Button;
按钮组件中,主要包含三种主题,样式做了通用设置。从目前的代码看 genGhostButtonStyle 的设置其实和另外两个是一样的。
而通用样式时的取值来自 useToken,useToken则是采用组件树间进行数据传递的方式。
组件间传递样式
// docs/examples/components/theme.tsx
// 创建一个 Context 对象 ThemeContext
export const ThemeContext = React.createContext(createTheme(derivative));
// 创建一个 Context 对象 DesignTokenContext
export const DesignTokenContext = React.createContext<{
token?: Partial<DesignToken>;
hashed?: string | boolean;
}>({
token: defaultDesignToken,
});
/**
* 创建默认样式,并缓存 token
* @returns 包含 theme, token, hashed 的数组对象
*/
export function useToken(): [Theme<any, any>, DerivativeToken, string] {
// 订阅 DesignTokenContext
// 将此处的 token 重命名为 rootDesignToken,并设置默认值 defaultDesignToken
const { token: rootDesignToken = defaultDesignToken, hashed } =
React.useContext(DesignTokenContext);
// 订阅 ThemeContext
const theme = React.useContext(ThemeContext);
// 将 theme 派生的 token 缓存为全局共享 token
// 实际 token 的取值
const [token, hashId] = useCacheToken<DerivativeToken, DesignToken>(
theme,
[defaultDesignToken, rootDesignToken],
{
salt: typeof hashed === 'string' ? hashed : '',
},
);
return [theme, token, hashed ? hashId : ''];
}
这里有一处处理需要注意
标签:5.0,const,样式,prefixCls,酷炫,theme,ant,token,import From: https://blog.51cto.com/u_15838863/6467240