Button 组件样式的生成
本文主要分析 Button 组件样式的生成过程。
接下来就以 useStyle 函数作为入口点函数进行分析。
样式生成过程:
- 调用 useToken 函数获取 Token(包含主题和组件)。useToken 会在下一篇博客中进行分析,它用来生成 theme 和 token。
- 生成组件 Token。
- 根据 Token 生成组件的样式对象。
- 解析样式对象,缓存并插入到 Head 中。
Button 构造过程中调用 useStyle 生成样式。
js
const InternalButton = (props, ref) => {
...
const {
getPrefixCls,
autoInsertSpaceInButton,
direction,
button
} = useContext(ConfigContext);
const prefixCls = getPrefixCls('btn', customizePrefixCls);
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls);
...
genStyleHooks
这里仅是用来配置 Button 的样式对象。具体执行在 theme 中的 genStyleHooks 函数。
node_modules/antd/es/button/style/index.js
js
export default genStyleHooks('Button', token => {
const buttonToken = prepareToken(token);
return [
// Shared
genSharedButtonStyle(buttonToken),
// Size
genSizeSmallButtonStyle(buttonToken), genSizeBaseButtonStyle(buttonToken), genSizeLargeButtonStyle(buttonToken),
// Block
genBlockButtonStyle(buttonToken),
// Group (type, ghost, danger, loading)
genTypeButtonStyle(buttonToken),
// Button Group
genGroupStyle(buttonToken)];
}, prepareComponentToken, {
unitless: {
fontWeight: true
}
});
例:定义了 Button 的样式对象
js
const genSharedButtonStyle = token => {
const {
componentCls,
iconCls,
fontWeight
} = token;
return {
[componentCls]: {
outline: 'none',
position: 'relative',
display: 'inline-block',
fontWeight,
whiteSpace: 'nowrap',
textAlign: 'center',
backgroundImage: 'none',
background: 'transparent',
border: `${unit(token.lineWidth)} ${token.lineType} transparent`,
cursor: 'pointer',
transition: `all ${token.motionDurationMid} ${token.motionEaseInOut}`,
userSelect: 'none',
touchAction: 'manipulation',
lineHeight: token.lineHeight,
color: token.colorText,
'&:disabled > *': {
pointerEvents: 'none'
},
...
}
};
};
生成组件样式。
- 生成组件的样式对象,解析后,缓存并插入到 Head 中
- useCSSVar 调用后,如果开启 css 变量会将组件 token 转换为 css 变量并插入到 Head 中。 node_modules/antd/es/theme/util/genComponentStyleHook.js
js
export const genStyleHooks = (component, styleFn, getDefaultToken, options) => {
// 生成组件的样式对象,解析后,缓存并插入到 Head 中。
const useStyle = genComponentStyleHook(component, styleFn, getDefaultToken, options);
// useCSSVar 调用后,如果开启 css 变量会将组件 token 转换为 css 变量并插入到 Head 中。
const useCSSVar = genCSSVarRegister(Array.isArray(component) ? component[0] : component, getDefaultToken, options);
return function (prefixCls) { // 如:prefixCls 为 "ant-btn"
let rootCls = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : prefixCls;
const [, hashId] = useStyle(prefixCls);
const [wrapCSSVar, cssVarCls] = useCSSVar(rootCls);
return [wrapCSSVar, hashId, cssVarCls];
};
};
genComponentStyleHook
返回一个函数用来生成 css 样式,缓存并插入到 Head 中。 三种样式:
- 为 a 标签生成样式。
- 为 icon 生成样式。
- 为组件生成样式。这里如果 injectStyle 配置为 false 时,不做处理。
作为结果的 wrapSSR 函数,仅在非浏览器中运行时,调用后才插入样式,否则在组件挂载时就已将样式插入。
注:useToken 函数在下一篇中分析。
node_modules/antd/es/theme/util/genComponentStyleHook.js
js
export default function genComponentStyleHook(componentName, styleFn, getDefaultToken) {
let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
// 例:如果是 Button 组件, cells 内容为 [Button, Button]
const cells = Array.isArray(componentName) ? componentName : [componentName, componentName];
const [component] = cells;
const concatComponent = cells.join('-'); //例: Button-Button
return prefixCls => {
// hashId 为空表示不使用 hashId,默认使用。
// hashId 在开发者模式下为带有"css-dev-only-do-not-override-" 前缀
const [theme, realToken, hashId, token, cssVar] = useToken(); // 获取主题及 token。
const {
getPrefixCls, // 生成组件前缀的函数
iconPrefixCls, // 图标样式的前缀
csp
} = useContext(ConfigContext);
const rootPrefixCls = getPrefixCls(); // 不传参数,返回 ant。
const type = cssVar ? 'css' : 'js'; // 判断是否使用 css 变量
// 根据 type 选择运算辅助类。可以进行加、减、乘、除。
const calc = genCalc(type);
// 选择使用 js 函数或 css 函数。
const {
max,
min
} = genMaxMin(type);
// Shared config
const sharedConfig = {
theme,
token,
hashId,
nonce: () => csp === null || csp === void 0 ? void 0 : csp.nonce,
clientOnly: options.clientOnly, // 是否支持 ssr
// antd is always at top of styles
order: options.order || -999 // 样式插入的优先级
};
// 生成 antd 中 a 标签的通用样式。
// Generate style for all a tags in antd component.
useStyleRegister(Object.assign(Object.assign({}, sharedConfig), {
clientOnly: false,
path: ['Shared', rootPrefixCls]
}), () => [{ // 会将数组中的 css 对象转换为 css 样式
// Link
'&': genLinkStyle(token)
}]);
// Generate style for icons
useResetIconStyle(iconPrefixCls, csp);
const wrapSSR = useStyleRegister(Object.assign(Object.assign({}, sharedConfig), {
// 例:["Button-Button", "ant-btn", "anticon"]
path: [concatComponent, prefixCls, iconPrefixCls]
}), () => {
if (options.injectStyle === false) { //用来控制是否插入样式。默认为 `undefined`
return [];
}
const {
token: proxyToken, // 生成一个 token 的代理对象。 记录每次获取的属性。
flush // 保存组件对应的 Token 和所使用的属性。
} = statisticToken(token); // 用在 dev 环境下。
// 获取组件的默认 Token。 getDefaultToken 可能是 Token 对象也可能是函数。
// 如果 getDefaultToken 是函数
// 则将 realToken[component] 与 realToken 合并为一个新对象,作为getDefaultToken的参数。
// 否则返回 getDefaultToken
// realToken[component] 表示组件 Token。
const defaultComponentToken = getDefaultComponentToken(component, realToken, getDefaultToken);
const componentCls = `.${prefixCls}`; // 例:".ant-btn"
// 生成组件 token
const componentToken = getComponentToken(component, realToken, defaultComponentToken, {
deprecatedTokens: options.deprecatedTokens,
format: options.format
});
if (cssVar) { // 是否使用 css 变量,默认不使用
// 将 defaultComponentToken 中的样式值转换为 css 变量形式
Object.keys(defaultComponentToken).forEach(key => {
defaultComponentToken[key] = `var(${token2CSSVar(key, getCompVarPrefix(component, cssVar.prefix))})`;
});
}
// 将参数对象进行合并。
const mergedToken = mergeToken(proxyToken, {
componentCls, // 例:".ant-btn"
prefixCls, // 例:"ant-btn"
iconCls: `.${iconPrefixCls}`, // 例:".anticon"
antCls: `.${rootPrefixCls}`, // 例:".ant"
calc,
max,
min
}, cssVar ? defaultComponentToken : componentToken);
// 根据 token 生成 css 对象。
const styleInterpolation = styleFn(mergedToken, {
hashId,
prefixCls,
rootPrefixCls,
iconPrefixCls
});
// component = "Button"
// 记录 Button 组件对应的 Token 和所有的属性。
flush(component, componentToken);
// 生成一个组件的通用样式的 css 对象。如组件的字号,边框等。
// 例:{"[class^="ant-btn"], [class*="ant-btn"]": {...}}
return [options.resetStyle === false ? null : genCommonStyle(mergedToken, prefixCls), styleInterpolation];
});
return [wrapSSR, hashId];
};
}
useStyleRegister
解析 css 对象,将解析结果缓存,如果在浏览器中运行时,在组件创建或卸载时插入或删除 css 样式,否则在作为返回结果的函数中插入 css 样式。
样式的处理:
- 浏览器渲染或设置 mock 为 'client' 的情况下,会在组件挂载时(调用 useStyleRegister 的组件),插入 css 样式。
例:
js
<style data-rc-order="prependQueue" data-rc-priority="-999" data-css-hash="1s7a99k" data-token-hash="pi6o7t" data-cache-path="pi6o7t|Shared|ant">:where(.css-dev-only-do-not-override-gzal6t) a{color:#1677ff;text-decoration:none;background-color:transparent;outline:none;cursor:pointer;transition:color 0.3s;-webkit-text-decoration-skip:objects;}...</style>
- 如果开启 ssrInline & 非浏览器渲染 & StyleContext 为默认初始化时,会在方法返回的函数被调用时插入样式。
- 另外解析后的样式会经过 stylis 进一步处理。
node_modules/@ant-design/cssinjs/es/hooks/useStyleRegister.js
js
/**
* Register a style to the global style sheet.
*/
export default function useStyleRegister(info, styleFn) {
var token = info.token,
path = info.path,
hashId = info.hashId,
layer = info.layer,
nonce = info.nonce,
clientOnly = info.clientOnly,
_info$order = info.order,
order = _info$order === void 0 ? 0 : _info$order; // 优先级
var _React$useContext = React.useContext(StyleContext),
autoClear = _React$useContext.autoClear,
mock = _React$useContext.mock,
defaultCache = _React$useContext.defaultCache,
hashPriority = _React$useContext.hashPriority,
container = _React$useContext.container,
ssrInline = _React$useContext.ssrInline,
transformers = _React$useContext.transformers,
linters = _React$useContext.linters,
cache = _React$useContext.cache;
var tokenKey = token._tokenKey;
var fullPath = [tokenKey].concat(_toConsumableArray(path));
// 判断是否运行在浏览器端
// Check if need insert style
var isMergedClientSide = isClientSide;
if (process.env.NODE_ENV !== 'production' && mock !== undefined) {
isMergedClientSide = mock === 'client';
}
// STYLE_PREFIX = 'style'
// 缓存和更新
var _useGlobalCache = useGlobalCache(STYLE_PREFIX, fullPath,
// Create cache if needed
function () {
var cachePath = fullPath.join('|');
// Get style from SSR inline style directly
if (existPath(cachePath)) {
// 查找 属性 'data-css-hash' 是 cachePath 的 style 元素
// 返回 style 元素中的内容和 hash
var _getStyleAndHash = getStyleAndHash(cachePath),
_getStyleAndHash2 = _slicedToArray(_getStyleAndHash, 2),
inlineCacheStyleStr = _getStyleAndHash2[0],
styleHash = _getStyleAndHash2[1];
if (inlineCacheStyleStr) {
return [inlineCacheStyleStr, tokenKey, styleHash, {}, clientOnly, order];
}
}
// Generate style
var styleObj = styleFn();
// 将 css 对象转换为 css 样式。
var _parseStyle5 = parseStyle(styleObj, {
hashId: hashId,
hashPriority: hashPriority,
layer: layer,
path: path.join('-'),
transformers: transformers,
linters: linters
}),
_parseStyle6 = _slicedToArray(_parseStyle5, 2),
parsedStyle = _parseStyle6[0], // css 样式
effectStyle = _parseStyle6[1]; // css 中的 @keyframe 部分
// 使用 stylis 编译 css 样式,之后匹配 "{%%%:%;}" 并将它替换为 ";"
// 例:@layer a,b,c{%%%:%;} @layer c{.container{color:#fff;}}
// 转换为 @layer a,b,c; @layer c{.container{color:#fff;}}
var styleStr = normalizeStyle(parsedStyle);
var styleId = uniqueHash(fullPath, styleStr);
// 返回缓存内容
return [styleStr, tokenKey, styleId, effectStyle, clientOnly, order];
},
// Remove cache if no need
function (_ref2, fromHMR) { // 组件卸载时触发
var _ref3 = _slicedToArray(_ref2, 3),
styleId = _ref3[2];
if ((fromHMR || autoClear) && isClientSide) {
//删除 'data-css-hash' 值为 styleId 的元素
removeCSS(styleId, {
mark: ATTR_MARK
});
}
},
// Effect: Inject style here
function (_ref4) { // 组件挂载时触发
var _ref5 = _slicedToArray(_ref4, 4),
styleStr = _ref5[0],
_ = _ref5[1],
styleId = _ref5[2],
effectStyle = _ref5[3];
// 如果在浏览器,插入 css 样式
// CSS_FILE_STYLE = '_FILE_STYLE__'
if (isMergedClientSide && styleStr !== CSS_FILE_STYLE) {
var mergedCSSConfig = {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: container,
priority: order
};
var nonceStr = typeof nonce === 'function' ? nonce() : nonce;
if (nonceStr) {
mergedCSSConfig.csp = {
nonce: nonceStr
};
}
// 插入或更新 css 样式
var style = updateCSS(styleStr, styleId, mergedCSSConfig);
// CSS_IN_JS_INSTANCE = '__cssinjs_instance__'
style[CSS_IN_JS_INSTANCE] = cache.instanceId;
// tokenKey = 'data-token-hash'
// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, tokenKey);
// Debug usage. Dev only
if (process.env.NODE_ENV !== 'production') {
style.setAttribute(ATTR_CACHE_PATH, fullPath.join('|'));
}
// 插入 keyframe 样式
// Inject client side effect style
Object.keys(effectStyle).forEach(function (effectKey) {
// normalizeStyle => (0, _stylis.serialize)((0, _stylis.compile)(styleStr), _stylis.stringify);
updateCSS(normalizeStyle(effectStyle[effectKey]), "_effect-".concat(effectKey), mergedCSSConfig);
});
}
}),
_useGlobalCache2 = _slicedToArray(_useGlobalCache, 3),
cachedStyleStr = _useGlobalCache2[0],
cachedTokenKey = _useGlobalCache2[1],
cachedStyleId = _useGlobalCache2[2];
return function (node) {
var styleNode;
// ssrInline 为 true 会内联到 style 中作为兜底
if (!ssrInline || isMergedClientSide || !defaultCache) {
styleNode = /*#__PURE__*/React.createElement(Empty, null);
} else {
var _ref6;
styleNode = /*#__PURE__*/React.createElement("style", _extends({}, (_ref6 = {}, _defineProperty(_ref6, ATTR_TOKEN, cachedTokenKey), _defineProperty(_ref6, ATTR_MARK, cachedStyleId), _ref6), {
dangerouslySetInnerHTML: {
__html: cachedStyleStr
}
}));
}
return /*#__PURE__*/React.createElement(React.Fragment, null, styleNode, node);
};
}
useGlobalCache
使用 StyleContext 中的 Cache 进行缓存。并通过副作用,来管理缓存。
cacheFn 生成数据。
onCacheRemove 缓存失效时调用。
onCacheEffect 副作用执行时调用。
node_modules/@ant-design/cssinjs/es/hooks/useGlobalCache.js
js
export default function useGlobalCache(prefix, keyPath, cacheFn, onCacheRemove,
// Add additional effect trigger by `useInsertionEffect`
onCacheEffect) {
var _React$useContext = React.useContext(StyleContext),
globalCache = _React$useContext.cache; // 一个 Map
// fullPath 转换为以 _ 链接的 string,作为副作用或 useMemo 的依赖。
var fullPath = [prefix].concat(_toConsumableArray(keyPath));
var deps = fullPath.join('_');
// 返回一个函数,组件未卸载时,可以将参数函数添加到队列中,
// 当组件卸载时,会遍历队列并调用里面的函数。
// 这里的参数 [deps] 作为副作用的依赖。
var register = useEffectCleanupRegister([deps]);
// 在生产模式,返回 false
// 开发模式下,并且是非 ssr,热更新时返回 true,更新之后返回 false。
var HMRUpdate = useHMR();
// 创建或更新缓存。
var buildCache = function buildCache(updater) {
// 调用 update 更新缓存。
// 新值通过回调第二个参数生成。
globalCache.update(fullPath, function (prevCache) {
var _ref = prevCache || [undefined, undefined],
_ref2 = _slicedToArray(_ref, 2),
_ref2$ = _ref2[0],
times = _ref2$ === void 0 ? 0 : _ref2$,
cache = _ref2[1];
// HMR should always ignore cache since developer may change it
var tmpCache = cache;
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
onCacheRemove === null || onCacheRemove === void 0 || onCacheRemove(tmpCache, HMRUpdate);
tmpCache = null;
}
// 使用缓存或者调用 cacheFn 生成缓存
var mergedCache = tmpCache || cacheFn();
var data = [times, mergedCache];
// Call updater if need additional logic
return updater ? updater(data) : data;
});
};
// Create cache
React.useMemo(function () {
buildCache();
}, /* eslint-disable react-hooks/exhaustive-deps */
[deps]
/* eslint-enable */);
var cacheEntity = globalCache.get(fullPath);// 获取缓存
// HMR clean the cache but not trigger `useMemo` again
// Let's fallback of this
// ref https://github.com/ant-design/cssinjs/issues/127
if (process.env.NODE_ENV !== 'production' && !cacheEntity) {
buildCache(); // 非生产环境下并且没有缓存 重新获取。
cacheEntity = globalCache.get(fullPath);
}
var cacheContent = cacheEntity[1];
// React >= 18 使用 useInsertionEffect (执行的时候 DOM 还没有更新)
// 否则使用 useLayoutEffect (执行时 DOM 已经更新)并且 polyfill 为 true。
// fullPath 变化时重新触发
// Remove if no need anymore
useCompatibleInsertionEffect(function () {
onCacheEffect === null || onCacheEffect === void 0 || onCacheEffect(cacheContent);
}, function (polyfill) { // 这个函数用来返回副作用回收时调用的函数。
// 当 React version < 18 时参数 polyfill 表示是否兜底
// It's bad to call build again in effect.
// But we have to do this since StrictMode will call effect twice
// which will clear cache on the first time.
buildCache(function (_ref3) {// 对应 useInsertionEffect 或 useLayoutEffect 销毁时调用。
var _ref4 = _slicedToArray(_ref3, 2),
times = _ref4[0],
cache = _ref4[1];
if (polyfill && times === 0) {
onCacheEffect === null || onCacheEffect === void 0 || onCacheEffect(cacheContent);
}
return [times + 1, cache];
});
return function () {
globalCache.update(fullPath, function (prevCache) {
var _ref5 = prevCache || [],
_ref6 = _slicedToArray(_ref5, 2),
_ref6$ = _ref6[0],
times = _ref6$ === void 0 ? 0 : _ref6$,
cache = _ref6[1];
var nextCount = times - 1;
if (nextCount === 0) { // 缓存计数为 0 时,注册销毁函数。
// 内部会将注册的函数添加到一个队列中。
// 并使用 React.useEffect 函数,在销毁时调用队列中的函数。
// Always remove styles in useEffect callback
register(function () {
// With polyfill, registered callback will always be called synchronously
// But without polyfill, it will be called in effect clean up,
// And by that time this cache is cleaned up.
if (polyfill || !globalCache.get(fullPath)) {
onCacheRemove === null || onCacheRemove === void 0 || onCacheRemove(cache, false);
}
});
return null;
}
return [times - 1, cache];
});
};
}, [deps]);
return cacheContent;
}
解析样式对象
解析 css 对象,转换为 css 样式。其中 keyframe 的解析会保存在单独的对象中与转换后的 css 一起返回。
- 参数不是 css 对象,并且当前解析的是最外层(root 为 true),直接将内容追加的结果集中。
- 解析 keyframe(有"_keyframe" 属性),保存在 effectStyle 对象中,effectStyle 会作为结果返回。
- 解析 css 对象
例:cssObj = {'&': {a: {backGroundColor: '#fff', &:hover{color:'#51ccee'}}}}
最终解析为':where(.hashId) &{a{background-color: #61dafb; &:hover{color: #51ccee}}}'
- 解析选择器。通过设置 root 为 true,表示当前解析的是选择器名称。
- 如果使用 hashId,则为选择器名添加 hashId 作为前缀,并根据 hashPriority 调整 css 优先级。 当 hashPriority === "low" 时,添加伪类函数 ":where(.hashId)" 来降低类选择器的优先级。
- 如果类选择器名称是 '&' 或 '' 不符合 stylis 语法,所以直接解析内层。
- 解析内容,如上例中 a 的 value 部分。这一部分也是调用 parseStyle 解析但是会将 root 置为 false
- 处理样式名:会将驼峰形式的属性名会先换行为 css 标准属性。
- 处理样式值:会判断是否需要添加单位,如果值有单位并且是 number 类型会添加"px"。
- 解析选择器。通过设置 root 为 true,表示当前解析的是选择器名称。
- 如果解析的是内容,为它添加"{}" 即
background-color: '#fff' => {background-color: '#fff'}
- 处理 layer。
- 如果有 layer 为解析后的 css 添加前缀 "@layer layerName "
- 如果有声明 layer 的优先级,由于后续会用 stylis 编译 css,而 stylis 不支持这种语法,因此手动为它添加一个"{%%%:%}"(占位,用来通过 stylis 编译),等后续用 stylis 等编译后再去掉 "{%%%:%;}"。
例:cssStr = "@layer a b{%%%:%} b{}"
stylis 编译 -> "@layer a b{%%%:%;}; b{a{color: '#fff';}}" 替换之后 -> "@layer a b; b{a{color: '#fff';}}"
注:样式中的 '&' 会在 stylis 编译过程中进行处理。 node_modules/@ant-design/cssinjs/es/hooks/useStyleRegister.js
js
export var parseStyle = function parseStyle(interpolation) {
var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {
root: true, // 如果为 false,表示当前解析的是选择器中的内容。
parentSelectors: []
},
root = _ref.root,
injectHash = _ref.injectHash,
parentSelectors = _ref.parentSelectors;
var hashId = config.hashId,
layer = config.layer,
path = config.path,
hashPriority = config.hashPriority,
_config$transformers = config.transformers, // 可以用来处理样式
transformers = _config$transformers === void 0 ? [] : _config$transformers,
_config$linters = config.linters,
linters = _config$linters === void 0 ? [] : _config$linters;
var styleStr = '';
var effectStyle = {};
// 解析动画
function parseKeyframes(keyframes) {
var animationName = keyframes.getName(hashId);
if (!effectStyle[animationName]) { // 如果没有处理过
var _parseStyle = parseStyle(keyframes.style, config, {
root: false,
parentSelectors: parentSelectors
}),
_parseStyle2 = _slicedToArray(_parseStyle, 1),
_parsedStr = _parseStyle2[0];
// @keyframes name {.....}
effectStyle[animationName] = "@keyframes ".concat(keyframes.getName(hashId)).concat(_parsedStr);
}
}
// 将嵌套的数组转换为非嵌套。
function flattenList(list) {
var fullList = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
list.forEach(function (item) {
if (Array.isArray(item)) {
flattenList(item, fullList);
} else if (item) {
fullList.push(item);
}
});
return fullList;
}
// 将非数组转换为数组
var flattenStyleList = flattenList(Array.isArray(interpolation) ? interpolation : [interpolation]);
flattenStyleList.forEach(function (originStyle) {
// Only root level can use raw string
var style = typeof originStyle === 'string' && !root ? {} : originStyle;
if (typeof style === 'string') { // 如果是 root 并且是 string,直接追加到结果中。
styleStr += "".concat(style, "\n");
} else if (style._keyframe) { // 解析动画
// 解析 KeyFrame 并保存到 effectStyle 中。
// Keyframe
parseKeyframes(style);
} else {
// 转换样式。
var mergedStyle = transformers.reduce(function (prev, trans) {
var _trans$visit;
return (trans === null || trans === void 0 || (_trans$visit = trans.visit) === null || _trans$visit === void 0 ? void 0 : _trans$visit.call(trans, prev)) || prev;
}, style);
// Normal CSSObject
Object.keys(mergedStyle).forEach(function (key) {
var value = mergedStyle[key];
if (_typeof(value) === 'object' && value && (key !== 'animationName' || !value._keyframe) && !isCompoundCSSProperty(value)) {
// isCompoundCSSProperty 是否包含 _skip_check_ 或 _multi_value_ 属性
var subInjectHash = false;
// 当成嵌套对象来处理
var mergedKey = key.trim();
// Whether treat child as root. In most case it is false.
var nextRoot = false;
// 拆分多个选择器
if ((root || injectHash) && hashId) {
if (mergedKey.startsWith('@')) {
// 略过媒体查询,交给子节点继续插入 hashId
subInjectHash = true;
} else {
// 注入 hashId
mergedKey = injectSelectorHash(key, hashId, hashPriority);
}
} else if (root && !hashId && (mergedKey === '&' || mergedKey === '')) {
// In case of `{ '&': { a: { color: 'red' } } }` or `{ '': { a: { color: 'red' } } }` without hashId,
// we will get `&{a:{color:red;}}` or `{a:{color:red;}}` string for stylis to compile.
// But it does not conform to stylis syntax,
// and finally we will get `{color:red;}` as css, which is wrong.
// So we need to remove key in root, and treat child `{ a: { color: 'red' } }` as root.
mergedKey = '';
nextRoot = true;
}
// 解析内容,或内嵌选择器
var _parseStyle3 = parseStyle(value, config, {
root: nextRoot,
injectHash: subInjectHash,
parentSelectors: [].concat(_toConsumableArray(parentSelectors), [mergedKey])
}),
_parseStyle4 = _slicedToArray(_parseStyle3, 2),
_parsedStr2 = _parseStyle4[0],
childEffectStyle = _parseStyle4[1];
effectStyle = _objectSpread(_objectSpread({}, effectStyle), childEffectStyle);
styleStr += "".concat(mergedKey).concat(_parsedStr2);
} else { // value 部分是 string,或动画相关或包含 _skip_check_ 或 _multi_value_ 属性
var _value;
function appendStyle(cssKey, cssValue) {
// 是否是 object 并且包含 _skip_check_ 或 _multi_value_ 属性
if (process.env.NODE_ENV !== 'production' && (_typeof(value) !== 'object' || !(value !== null && value !== void 0 && value[SKIP_CHECK]))) {
[contentQuotesLinter, hashedAnimationLinter].concat(_toConsumableArray(linters)).forEach(function (linter) {
return linter(cssKey, cssValue, {
path: path,
hashId: hashId,
parentSelectors: parentSelectors
});
});
}
// 转换为标准 css 属性名。 如:animationName => animation-name
var styleName = cssKey.replace(/[A-Z]/g, function (match) {
return "-".concat(match.toLowerCase());
});
// 利用 emotion 的 unitlessKeys 函数,判断是否属性是否有单位
// Auto suffix with px
var formatValue = cssValue;
if (!unitless[cssKey] && typeof formatValue === 'number' && formatValue !== 0) {
formatValue = "".concat(formatValue, "px");
}
// handle animationName & Keyframe value
if (cssKey === 'animationName' && cssValue !== null && cssValue !== void 0 && cssValue._keyframe) {
parseKeyframes(cssValue);
formatValue = cssValue.getName(hashId);
}
// 设置动画。如:animation-name: keyframe 的名字;
styleStr += "".concat(styleName, ":").concat(formatValue, ";");
}
// MULTI_VALUE 为 _multi_value_
// 如果具有 _multi_value_ 属性,表示对应多个属性值。
var actualValue = (_value = value === null || value === void 0 ? void 0 : value.value) !== null && _value !== void 0 ? _value : value;
if (_typeof(value) === 'object' && value !== null && value !== void 0 && value[MULTI_VALUE] && Array.isArray(actualValue)) {
actualValue.forEach(function (item) {
appendStyle(key, item);
});
} else {
appendStyle(key, actualValue);
}
}
});
}
});
if (!root) { // 为选择器的内容添加"{}",如 a:{color:red;}
styleStr = "{".concat(styleStr, "}");
} else if (layer && supportLayer()) { // 判断是否支持 layer 属性
var layerCells = layer.split(',');
var layerName = layerCells[layerCells.length - 1].trim();
styleStr = "@layer ".concat(layerName, " {").concat(styleStr, "}");
// Order of layer if needed
if (layerCells.length > 1) { // stylis 不支持 定义 layer 的优先级,所以先手动
// 添加 "{%%%:%}" 之后在去掉。
// zombieJ: stylis do not support layer order, so we need to handle it manually.
styleStr = "@layer ".concat(layer, "{%%%:%}").concat(styleStr);
}
}
return [styleStr, effectStyle];
};
处理 icon 样式
插入 icon 样式。不使用 hashId
例:
html
<style data-rc-order="prependQueue" data-css-hash="1ji9rq3" data-token-hash="pi6o7t" data-cache-path="pi6o7t|ant-design-icons|anticon">.anticon{display:inline-flex;align-items:center;color:inherit;font-style:normal;line-height:0;text-align:center;text-transform:none;vertical-align:-0.125em;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;}.anticon >*{line-height:1;}.anticon svg{display:inline-block;}.anticon .anticon .anticon-icon{display:block;}</style>
node_modules/antd/es/theme/util/useResetIconStyle.js
js
const useResetIconStyle = (iconPrefixCls, csp) => {
const [theme, token] = useToken();
// Generate style for icons
return useStyleRegister({
theme,
token,
hashId: '',
path: ['ant-design-icons', iconPrefixCls], // iconPrefixCls = "anticon"
nonce: () => csp === null || csp === void 0 ? void 0 : csp.nonce
}, () => [{ // resetIcon 返回 icon 样式的 css 对象。
[`.${iconPrefixCls}`]: Object.assign(Object.assign({}, resetIcon()), {
[`.${iconPrefixCls} .${iconPrefixCls}-icon`]: {
display: 'block'
}
})
}]);
};
处理组件 Token
- 从 token 中获取对应的组件 token。
- 如果仅使用废弃属,则将其赋到新属性上,否则什么也不做。
- 将默认组件 token 与组件 token 合并为一个新 token。
- 如果有配置 format 函数,则进一步处理合并的 token。
- 删除合并后 token 中与 token 中相同的 key。
node_modules/antd/es/theme/util/genComponentStyleHook.js
js
const getComponentToken = (component, token, defaultToken, options) => {
// 获取组件 Token
const customToken = Object.assign({}, token[component]);
// 处理废弃的属性。
// 如果仅使用废弃属,则将其赋到新属性上,否则什么也不做。
if (options === null || options === void 0 ? void 0 : options.deprecatedTokens) {
const {
deprecatedTokens
} = options;
deprecatedTokens.forEach(_ref => {
let [oldTokenKey, newTokenKey] = _ref;
var _a;
if (process.env.NODE_ENV !== 'production') {
process.env.NODE_ENV !== "production" ? warning(!(customToken === null || customToken === void 0 ? void 0 : customToken[oldTokenKey]), `The token '${String(oldTokenKey)}' of ${component} had deprecated, use '${String(newTokenKey)}' instead.`) : void 0;
}
// Should wrap with `if` clause, or there will be `undefined` in object.
if ((customToken === null || customToken === void 0 ? void 0 : customToken[oldTokenKey]) || (customToken === null || customToken === void 0 ? void 0 : customToken[newTokenKey])) {
(_a = customToken[newTokenKey]) !== null && _a !== void 0 ? _a : customToken[newTokenKey] = customToken === null || customToken === void 0 ? void 0 : customToken[oldTokenKey];
}
});
}
// 用组件 Token 覆盖 defaultToken 中的样式
let mergedToken = Object.assign(Object.assign({}, defaultToken), customToken);
if (options === null || options === void 0 ? void 0 : options.format) {
mergedToken = options.format(mergedToken);
}
// Remove same value as global token to minimize size
Object.keys(mergedToken).forEach(key => {
if (mergedToken[key] === token[key]) {
delete mergedToken[key];
}
});
return mergedToken;
};
genCSSVarRegister
主要是返回一个函数,用来创建组件时,如果开启 css 变量则将组件 token 转换为 css 变量并插入样式,否则什么也不做。
node_modules/antd/es/theme/util/genComponentStyleHook.js
js
const genCSSVarRegister = (component, getDefaultToken, options) => {
// 为属性添加前缀
// 例:zIndexPopup -> "ButtonZIndexPopup"
function prefixToken(key) {
return `${component}${key.slice(0, 1).toUpperCase()}${key.slice(1)}`;
}
const {
unitless: originUnitless = {},
injectStyle = true
} = options !== null && options !== void 0 ? options : {};
const compUnitless = { // 配置无单位属性
[prefixToken('zIndexPopup')]: true
};
// 将 originUnitless 中的名称进行转换并添加到 compUnitless。
Object.keys(originUnitless).forEach(key => {
compUnitless[prefixToken(key)] = originUnitless[key];
});
const CSSVarRegister = _ref3 => {
let {
rootCls,
cssVar
} = _ref3;
const [, realToken] = useToken();
useCSSVarRegister({
path: [component], // [Button]
prefix: cssVar.prefix,
key: cssVar === null || cssVar === void 0 ? void 0 : cssVar.key,
unitless: Object.assign(Object.assign({}, unitless), compUnitless),
ignore,
token: realToken,
scope: rootCls
}, () => {
// 生成组件默认 token
const defaultToken = getDefaultComponentToken(component, realToken, getDefaultToken);
// 生成最终的组件 token
// 使用 realToken[component] 覆盖 defaultToken
const componentToken = getComponentToken(component, realToken, defaultToken, {
format: options === null || options === void 0 ? void 0 : options.format, // 格式化 token
deprecatedTokens: options === null || options === void 0 ? void 0 : options.deprecatedTokens // 废弃的 token key
});
// 将组件 token 中的属性名进行转换,并将原始属性删掉。
Object.keys(defaultToken).forEach(key => {
componentToken[prefixToken(key)] = componentToken[key];
delete componentToken[key];
});
return componentToken;
});
return null;
};
const useCSSVar = rootCls => {
const [,,,, cssVar] = useToken();
return [node => injectStyle && cssVar ? ( /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(CSSVarRegister, {
rootCls: rootCls,
cssVar: cssVar,
component: component
}), node)) : node, cssVar === null || cssVar === void 0 ? void 0 : cssVar.key];
};
return useCSSVar;
};
useCSSVarRegister
调用 fn 获取 Token,将 Token 中的样式值转换为 css 变量形式,转换后的 Token 和包含变量声明的样式。组件挂载时插入样式,卸载时删除样式。 node_modules/@ant-design/cssinjs/es/hooks/useCSSVarRegister.js
js
var useCSSVarRegister = function useCSSVarRegister(config, fn) {
var key = config.key,
prefix = config.prefix,
unitless = config.unitless, // 例 unitless: {fontWeight: true}
ignore = config.ignore,
token = config.token,
_config$scope = config.scope, //
scope = _config$scope === void 0 ? '' : _config$scope;
var _useContext = useContext(StyleContext),
instanceId = _useContext.cache.instanceId,
container = _useContext.container;
var tokenKey = token._tokenKey;
var stylePath = [].concat(_toConsumableArray(config.path), [key, scope, tokenKey]);
// CSS_VAR_PREFIX = 'cssVar'
var cache = useGlobalCache(CSS_VAR_PREFIX, stylePath, function () {
var originToken = fn(); // 获取 token
// 将 token 中的样式值转换为 css 变量
var _transformToken = transformToken(originToken, key, {
prefix: prefix, // 变量名前缀
unitless: unitless, // 不需要单位
ignore: ignore, // 忽略这个样式
scope: scope // 作为声明变量的类选择器名的一部分。如 .key.scope{--ant-btn-padding: 20px}
}),
_transformToken2 = _slicedToArray(_transformToken, 2),
mergedToken = _transformToken2[0],
cssVarsStr = _transformToken2[1]; // mergedToken 中包含变量声明的类选择器
var styleId = uniqueHash(stylePath, cssVarsStr);
return [mergedToken, cssVarsStr, styleId, key]; // 缓存内容
}, function (_ref) { // 组件卸载时
var _ref2 = _slicedToArray(_ref, 3),
styleId = _ref2[2];
if (isClientSide) { // 如果是在浏览器端渲染,删除指定 styleId 的样式
removeCSS(styleId, {
mark: ATTR_MARK // 'data-css-hash'
});
}
}, function (_ref3) { // 组件挂载时
var _ref4 = _slicedToArray(_ref3, 3),
cssVarsStr = _ref4[1],
styleId = _ref4[2];
if (!cssVarsStr) { // 如果没有
return;
}
// 在 style['data-css-hash'] = styleId 中插入或更新样式
var style = updateCSS(cssVarsStr, styleId, {
mark: ATTR_MARK,
prepend: 'queue',
attachTo: container,
priority: -999
});
style[CSS_IN_JS_INSTANCE] = instanceId;
// Used for `useCacheToken` to remove on batch when token removed
style.setAttribute(ATTR_TOKEN, key);
});
return cache;
};