AliasToken(别名变量) 的生成过程
Alias Token 主要用来在生成组件样式时,提供样式值。它可以看成是主题与用户 Token 整个后的产物。
Alias Token 的生成主要是在 useToken 函数中进行处理,因此从 useToken 函数开始分析。
有些概念和函数前面两篇中提到的,会本篇中则会滤过。
Token 的处理
这里简单介绍一下对 Token 的处理过程:
Theme OverrideToken
派生算法 覆盖/扩充属性
DesignToken --------> DerivativeToken -----------> AliasToken
DerivativeToken
就是前面Antd Button 源码分析(一)中的 MapToken(梯度变量)。它可以看成是主题的实例。
Override Token
它表示用户配置的 Token,是这对默认主题的修改。
可通过如下配置 Token。
<ConfigProvider theme={{
colorPrimary: '#00b96b',
borderRadius: 2,}}>
<Button>click</Button>
</ConfigProvider>
之后可通过 DesignTokenContext 中的 override 获取。
const {
override,
} = React.useContext(DesignTokenContext);
useToken 流程分析
主要流程:
- 调用 useCacheToken 生成 Alias Token 并缓存。如果开启 css 变量,会在组件挂载时,插入仅包含css 变量声明的 css 类选择器。
- 返回主题和 Alias Token 等。
一般 DesignTokenContext 中配置的 token 与 override.overrride 相同。
node_modules/antd/es/theme/useToken.js
export default function useToken() {
const {
token: rootDesignToken,
hashed, // 默认为 true
theme,
override,
cssVar
} = React.useContext(DesignTokenContext);
// 如 5.12.5-true
const salt = `${version}-${hashed || ''}`;
// 如果没有在 DesignTokenContext 中配置 theme,
// 则使用默认提供的主题
const mergedTheme = theme || defaultTheme;
// 没有配置 DesignTokenContext 的情况下 defaultSeedToken 和 rootDesignToken 相同,
// 否则 rootDesignToken 会先覆盖 defaultSeedToken 中的样式。
const [token, hashId, realToken] = useCacheToken(mergedTheme, [defaultSeedToken, rootDesignToken], {
salt,
override,
getComputedToken,
// formatToken will not be consumed after 1.15.0 with getComputedToken.
// But token will break if @ant-design/cssinjs is under 1.15.0 without it
formatToken,
cssVar: cssVar && {
prefix: cssVar.prefix, // 作为变量的前缀 如:--ant-
key: cssVar.key,
unitless, // 在它里的变量,不需要单位。
ignore, // 在它里的变量,不进行处理,直接过滤掉。
preserve // 在它里面的变量不需要转换为 css 变量
}
});
return [mergedTheme, realToken, hashed ? hashId : '', token, cssVar];
}
DesignTokenContext 默认初始化
DesignTokenContext 支持对主题和 Token 进行配置。如果要对 token 中使用的值改为 css 变量方式需要配置 cssVar 属性。
DesignTokenContext 默认仅配置如下。
node_modules/antd/es/theme/context.js
// To ensure snapshot stable. We disable hashed in test env.
export const defaultConfig = {
token: defaultSeedToken,
override: {
override: defaultSeedToken // override 用来覆盖 token 中的样式。
},
hashed: true
};
export const DesignTokenContext = /*#__PURE__*/React.createContext(defaultConfig);
seedToken 的初始化
上面 DesignTokenContext 中的 defaultSeedToken 就是 seedToken。 seedToken 会作为基础变量,用来根据主题进行派生。
node_modules/antd/es/theme/themes/seed.js
export const defaultPresetColors = {
blue: '#1677ff',
purple: '#722ED1',
cyan: '#13C2C2',
green: '#52C41A',
magenta: '#EB2F96',
...
};
const seedToken = Object.assign(Object.assign({}, defaultPresetColors), {
// Color
colorPrimary: '#1677ff',
colorSuccess: '#52c41a',
colorWarning: '#faad14',
colorError: '#ff4d4f',
colorInfo: '#1677ff',
colorLink: '',
colorTextBase: '',
colorBgBase: '',
// Font
fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial,
'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji'`,
...
};
默认主题初始化
没有在 DesignTokenContext 中配置主题的情况下会使用默认主题。
下面看默认主题的初始化。
export const defaultTheme = createTheme(defaultDerivative);
缓存中没有,则创建一个新的主题。 node_modules/@ant-design/cssinjs/lib/theme/createTheme.js
/**
* Same as new Theme, but will always return same one if `derivative` not changed.
*/
function createTheme(derivatives) {
var derivativeArr = Array.isArray(derivatives) ? derivatives : [derivatives];
// 检查是否已经生成过主题。
// Create new theme if not exist
if (!cacheThemes.has(derivativeArr)) {
// 创建主题,并缓存。
cacheThemes.set(derivativeArr, new _Theme.default(derivativeArr));
}
// Get theme from cache and return
return cacheThemes.get(derivativeArr);
}
主题主要与派生函数关联。通过派生函数对 Token 进行派生生成 MapToken(梯度变量)。
/**
* Theme with algorithms to derive tokens from design tokens.
* Use `createTheme` first which will help to manage the theme instance cache.
*/
var Theme = exports.default = /*#__PURE__*/function () {
function Theme(derivatives) {
(0, _classCallCheck2.default)(this, Theme);
(0, _defineProperty2.default)(this, "derivatives", void 0);
(0, _defineProperty2.default)(this, "id", void 0);
this.derivatives = Array.isArray(derivatives) ? derivatives : [derivatives];
this.id = uuid;
if (derivatives.length === 0) {
(0, _warning.warning)(derivatives.length > 0, '[Ant Design CSS-in-JS] Theme should have at least one derivative function.');
}
uuid += 1;
}
(0, _createClass2.default)(Theme, [{
key: "getDerivativeToken", // 用来派生 Token
value: function getDerivativeToken(token) {
return this.derivatives.reduce(function (result, derivative) {
return derivative(token, result);
}, undefined);
}
}]);
return Theme;
}();
派生算法会生成的 MapToken(梯度变量) 结果如下。它们都是根据 SeedToken 中的变量生成。
* SeedToken: 定义了其他类型的 token 会以这里定义的颜色作为生成梯度的基础色。
* ColorMapToken:它继承了一堆颜色相关的 Token。Token 主要为几类,如主色,文本色,危险色,错误色,链接色等。
每个 Token 中的颜色主要分为文字,描边,背景几种,每种又有对应深浅色和激活与悬浮态等。
* SizeMapToken:定义了不同的大小。如:XXL,XL,LG,MD,MS,默认,SM,XS,XXS。
* HeightMapToken:内部组件高度 如 多选的内部子项,分为:XS(更小),SM(较小),LG(较高)。
* StyleMapToken:定义了线宽和圆角。线宽是一个单值,圆角分为: XS,SM,LG 和外部圆角。
* FontMapToken: 定义了字体大小和行高,它们有划分为标题和非标题。字体:小号(SM),标准(Stand),大号(LG),超大号(XL)。行高:文本行高,SM,LG。标题类的字体和行高有一到五级。
* CommonMapToken:继承自 StyleMapToken,增加了动效的播放速度。快速,中速,慢速。
node_modules/antd/es/theme/interface/maps/index.d.ts
```js
export interface MapToken extends SeedToken, ColorPalettes, LegacyColorPalettes, ColorMapToken, SizeMapToken, HeightMapToken, StyleMapToken, FontMapToken, CommonMapToken {
}
useCacheToken
- 生成 alisa token。
- 如果开启 css 变量,将 token 中的属性值转换为 css 变量,并生成一个仅包含变量声明的类选择器。
- 将上面结果进行缓存。
- 在组件挂载时,如果开启css 变量则将包含变量声明的类选择器插入到 Head 中。
- 在组件卸载时,删除相关的样式。
开启 css 变量,会如下转换。
例:
aliasToken = {
backGround: #fff,
}
转换为
aliasToken = {
backGround: var(--ant-back-ground),
}
cssStr = ".xxxx{--ant-back-ground: #fff}"
在副作用更新时将插入 cssStr
<style data-rc-order="prependQueue" data-rc-priority="-999" data-css-hash="1s7a99k" data-token-hash="pi6o7t">
cssStr
</style>
缓存 key: 将 token,override token 和 cssVar 三个对象分别偏平化后与 salt 和 theme id 一起作为缓存的 key。
扁平化过程:
- 如果有存在则直接返回。
- 遍历对象的 key。reult+= key,检查 value 的类型
- 如果是主题,则 reult+= 主题 id
- 如果是对象,递归调用
- 不属于 3 和 4 类型,直接拼接 reult+= value
- 参数作为 key,缓存 reult。
node_modules/@ant-design/cssinjs/es/hooks/useCacheToken.js
/**
* Cache theme derivative token as global shared one
* @param theme Theme entity
* @param tokens List of tokens, used for cache. Please do not dynamic generate object directly
* @param option Additional config
* @returns Call Theme.getDerivativeToken(tokenObject) to get token
*/
export default function useCacheToken(theme, tokens) {
var option = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
var _useContext = useContext(StyleContext),
// StyleContext 初始化时会创建一个缓存,并生成一个 id
instanceId = _useContext.cache.instanceId,
container = _useContext.container;
var _option$salt = option.salt,
salt = _option$salt === void 0 ? '' : _option$salt,
_option$override = option.override,
override = _option$override === void 0 ? EMPTY_OVERRIDE : _option$override,
formatToken = option.formatToken,
compute = option.getComputedToken,
cssVar = option.cssVar;
// 将 tokens 数组合并为一个对象。
// Basic - We do basic cache here
var mergedToken = memoResult(function () {
return Object.assign.apply(Object, [{}].concat(_toConsumableArray(tokens)));
}, tokens);
// 将 mergedToken,overrideTokenStr,cssVarStr 扁平化为 string。
var tokenStr = flattenToken(mergedToken);
var overrideTokenStr = flattenToken(override);
var cssVarStr = cssVar ? flattenToken(cssVar) : '';
// 用来生成缓存,并通过副作用,更新和删除。
var cachedToken = useGlobalCache(TOKEN_PREFIX, [salt, theme.id, tokenStr, overrideTokenStr, cssVarStr], function () { // 需要生成缓存时调用
var _cssVar$key;
// 如果没有在外部设置 option.getComputedToken,则调用当前类中的 getComputedToken 函数。
// 合并最终 Token。
var mergedDerivativeToken = compute ? compute(mergedToken, override, theme) : getComputedToken(mergedToken, override, theme, formatToken);
// Replace token value with css variables
var actualToken = _objectSpread({}, mergedDerivativeToken);
var cssVarsStr = '';
if (!!cssVar) {
// 将 token 中的属性值转换为 css 变量
// 即:{backGroundColor: #fff} -> {backGroundColor: var(--ant-back-ground-color)}
var _transformToken = transformToken(mergedDerivativeToken, cssVar.key, {
prefix: cssVar.prefix,
ignore: cssVar.ignore,
unitless: cssVar.unitless,
preserve: cssVar.preserve
});
var _transformToken2 = _slicedToArray(_transformToken, 2);
// 例:{backGroundColor: var(--ant-back-ground-color)}
mergedDerivativeToken = _transformToken2[0];
// 例:".xxxx{--ant-back-ground: #fff}"
cssVarsStr = _transformToken2[1];
}
// 1. 调用 flattenToken 将 mergedDerivativeToken 扁平化为字符串
// 2. .salt_作为前缀 如 "5.12.5-true_"
// 3. 使用 emotion 的 hash 函数生成哈希。
// Optimize for `useStyleRegister` performance
var tokenKey = token2key(mergedDerivativeToken, salt);
mergedDerivativeToken._tokenKey = tokenKey;
// 如果使用 css 变量则 mergedDerivativeToken 与 actualToken 不一致
// 所以再次生成 tokenKey,否则不会重新生成 tokenKey。
actualToken._tokenKey = token2key(actualToken, salt);
// cssVar.key 不存在则使用 tokenKey 作为 themeKey
var themeKey = (_cssVar$key = cssVar === null || cssVar === void 0 ? void 0 : cssVar.key) !== null && _cssVar$key !== void 0 ? _cssVar$key : tokenKey;
mergedDerivativeToken._themeKey = themeKey;
recordCleanToken(themeKey);
// 将 tokenKey 进行哈希后,添加前缀,作为 hashId
// hashPrefix 生产环境下为 'css' 其他环境下为 'css-dev-only-do-not-override'
var hashId = "".concat(hashPrefix, "-").concat(hash(tokenKey));
mergedDerivativeToken._hashId = hashId; // Not used
return [mergedDerivativeToken, hashId, actualToken, cssVarsStr, (cssVar === null || cssVar === void 0 ? void 0 : cssVar.key) || ''];
}, function (cache) { // 组件卸载时调用
// 如果保存在 Map 中的 key 的 value 为 0
// 则查找 style[data-css-hash=cache[0]._themeKey] 元素,
// 删除 __cssinjs_instance__ 属性值为 instanceId 的元素。
// Remove token will remove all related style
cleanTokenStyle(cache[0]._themeKey, instanceId);
}, function (_ref) { // 组件挂载时调用
var _ref2 = _slicedToArray(_ref, 4),
token = _ref2[0],
cssVarsStr = _ref2[3];
if (cssVar && cssVarsStr) { // 如果开启 css 变量,插入或更新,仅包含变量声明的类选择器。
// 查找 'data-css-hash' 属性为指定值的 style 元素。
// 更新或插入 css,并设置 'data-css-hash' 属性为指定值。
var style = updateCSS(cssVarsStr, hash("css-variables-".concat(token._themeKey)), {
mark: ATTR_MARK, // 'data-css-hash'
prepend: 'queue',
attachTo: container,
priority: -999
});
style[CSS_IN_JS_INSTANCE] = instanceId;
// 设置 style 元素的 'data-token-hash' 属性值为 token._themeKey
// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, token._themeKey);
}
});
return cachedToken;
}
生成 Alias Token
由于用户可以修改默认主题,所以这里根据主题生成 Token 之后,还需处理用户配置 Token。
大体过程:
- 根据主题生成派生 Token。
- 处理用户自定义的 Token(全局和组件)。用户配置会覆盖默认配置。
- 生成最终的 Alias Token。
最终生成的 Alias Token 类型:
{
[key in keyof ComponentsToken]?: AliasToken
} & AliasToken
参数 overrideToken
它包含了用户对主题和针对组件的修改。其中 override 属性对应的是针对主题的修改。
类型: node_modules/antd/es/theme/context.d.ts
export type ComponentsToken = {
[key in keyof OverrideToken]?: OverrideToken[key] & {
theme?: Theme<SeedToken, MapToken>;
};
};
export interface DesignTokenProviderProps {
token: Partial<AliasToken>;
theme?: Theme<SeedToken, MapToken>;
components?: ComponentsToken;
/** Just merge `token` & `override` at top to save perf */
override: {
override: Partial<AliasToken>;
} & ComponentsToken;
...
}
node_modules/antd/es/theme/interface/index.d.ts
export type OverrideToken = {
[key in keyof ComponentTokenMap]: Partial<ComponentTokenMap[key]> & Partial<AliasToken>;
};
DesignTokenContext 默认初始化时,overrideToken 被初始化如下:
export declare const defaultConfig: {
token: SeedToken;
override: {
override: SeedToken;
};
hashed: boolean;
};
以上面 useCacheToken 中在处理 Token 时,会使用 option.compute 或当前模块中的 compute 函数进行处理。这里以 option.compute 中指定的函数进行分析。
- 根据主题派生 token
- 调用 formatToken 生成 alias token
- 如果有配置组件 token,则接续处理,结果合并到 2 中的 alias token 。
- 返回 alias token。
node_modules/antd/es/theme/useToken.js
export const getComputedToken = (originToken, overrideToken, theme) => {
// 生成派生 token
const derivativeToken = theme.getDerivativeToken(originToken);
const {
override // AliasToken
} = overrideToken,
// 获取组件 Token。
components = __rest(overrideToken, ["override"]);
// 将 override 与 derivativeToken 合并为一个新对象。
// Merge with override
let mergedDerivativeToken = Object.assign(Object.assign({}, derivativeToken), {
override
});
// 生成一个 AliasToken。
// Format if needed
mergedDerivativeToken = formatToken(mergedDerivativeToken);
if (components) { // 如果存在组件 Token
Object.entries(components).forEach(_ref => {
let [key, value] = _ref;
const {
theme: componentTheme // 组件Token 中扩展了 theme
} = value,
componentTokens = __rest(value, ["theme"]);
let mergedComponentToken = componentTokens;
// 如果属性值中函数 theme 属性,进行递归调用。
if (componentTheme) { // 具有单独主题,则递归调用 getComputedToken 进行处理。
mergedComponentToken = getComputedToken(Object.assign(Object.assign({}, mergedDerivativeToken), componentTokens), {
override: componentTokens
}, componentTheme);
}
// 添加到最终 Token 中。
mergedDerivativeToken[key] = mergedComponentToken;
});
}
return mergedDerivativeToken;
};
生成 alias token。
node_modules/antd/es/theme/util/alias.js
/**
* Seed (designer) > Derivative (designer) > Alias (developer).
*
* Merge seed & derivative & override token and generate alias token for developer.
*/
export default function formatToken(derivativeToken) {
const {
override
} = derivativeToken,
restToken = __rest(derivativeToken, ["override"]);
const overrideTokens = Object.assign({}, override);
// overrideTokens 中不能包含 seedToken 中的属性。
Object.keys(seedToken).forEach(token => {
delete overrideTokens[token];
});
// 覆盖派生 token 中的属性。
const mergedToken = Object.assign(Object.assign({}, restToken), overrideTokens);
const screenXS = 480;
const screenSM = 576;
const screenMD = 768;
const screenLG = 992;
const screenXL = 1200;
const screenXXL = 1600;
// Motion
if (mergedToken.motion === false) {
const fastDuration = '0s';
mergedToken.motionDurationFast = fastDuration;
mergedToken.motionDurationMid = fastDuration;
mergedToken.motionDurationSlow = fastDuration;
}
// Generate alias token
const aliasToken = Object.assign(Object.assign(Object.assign({}, mergedToken), {
// ============== Background ============== //
colorFillContent: mergedToken.colorFillSecondary,
colorFillContentHover: mergedToken.colorFill,
colorFillAlter: mergedToken.colorFillQuaternary,
colorBgContainerDisabled: mergedToken.colorFillTertiary,
...
}), overrideTokens);
return aliasToken;