创建主题(createTheme)
1、缓存主题
创建缓存空间(Map) 缓存主题配置
const cacheThemes = new ThemeCache();
缓存主题
cacheThemes.set(derivativeArr, new Theme(derivativeArr));
new Theme(derivativeArr)
实例主题,将主题方法设值到示例成员属性,并提供获取主题值get方法(getDerivativeToken)
2、使用缓存主题token
// 获取主题实例化 token 值, 获取hash key
const [token, hashId] = useCacheToken<DerivativeToken, DesignToken>(
theme,
[defaultDesignToken, rootDesignToken],
{
salt: typeof hashed === 'string' ? hashed : '',
},
);
3、缓存优化渲染
// 全局注册,内部会做缓存优化
const wrapSSR = useStyleRegister(
{ theme, token, hashId, path: [prefixCls] },
() => [
genDefaultButtonStyle(defaultCls, token),
genPrimaryButtonStyle(primaryCls, token),
genGhostButtonStyle(ghostCls, token),
],
);
........
// useStyleRegister 最后返回一个函数组件,包含单独的样式、被包裹的组件节点
return (node: React.ReactElement) => {
let styleNode: React.ReactElement;
if (!ssrInline || isMergedClientSide || !defaultCache) {
styleNode = <Empty />;
} else {
styleNode = (
<style
{...{
[ATTR_TOKEN]: cachedTokenKey,
[ATTR_MARK]: cachedStyleId,
}}
dangerouslySetInnerHTML={{ __html: cachedStyleStr }}
/>
);
}
return (
<>
{styleNode}
{node}
</>
);
};
从DOM中获取样式
if (existPath(cachePath)) {
//inlineCacheStyleStr 样式字符串
const [inlineCacheStyleStr, styleHash] = getStyleAndHash(cachePath);
if (inlineCacheStyleStr) {
return [
inlineCacheStyleStr,
tokenKey,
styleHash,
{},
clientOnly,
order,
];
}
}
生成样式
// Generate style
const styleObj = styleFn();
const [parsedStyle, effectStyle] = parseStyle(styleObj, {
hashId,
hashPriority,
layer,
path: path.join('-'),
transformers,
linters,
});
const styleStr = normalizeStyle(parsedStyle);
回调调用获取样式(注意:此代码为调用cssinjs的api方法代码,非@ant-design/cssinjs源码)
// 全局注册,内部会做缓存优化
const wrapSSR = useStyleRegister(
{ theme, token, hashId, path: [prefixCls] },
// 调用该方法获取样式
() => [
genDefaultButtonStyle(defaultCls, token),
genPrimaryButtonStyle(primaryCls, token),
genGhostButtonStyle(ghostCls, token),
],
);
export function getStyleAndHash(
path: string,
): [style: string | null, hash: string] {
const hash = cachePathMap[path];
let styleStr: string | null = null;
if (hash && canUseDom()) {
if (fromCSSFile) {
styleStr = CSS_FILE_STYLE;
} else {
const style = document.querySelector(
`style[${ATTR_MARK}="${cachePathMap[path]}"]`,
);
if (style) {
styleStr = style.innerHTML;
} else {
// Clean up since not exist anymore
delete cachePathMap[path];
}
}
}
return [styleStr, hash];
}
//提供缓存上下文
const StyleContext = React.createContext<StyleContextProps>({
hashPriority: 'low',
cache: createCache(),
defaultCache: true,
});
//处理document.body里面的style标签,将style 放到head标签中,并删除body重复的style标签
//返回缓存值实体
export function createCache() {
const cssinjsInstanceId = Math.random().toString(12).slice(2);
// Tricky SSR: Move all inline style to the head.
// PS: We do not recommend tricky mode.
if (typeof document !== 'undefined' && document.head && document.body) {
const styles = document.body.querySelectorAll(`style[${ATTR_MARK}]`) || [];
const { firstChild } = document.head;
Array.from(styles).forEach((style) => {
(style as any)[CSS_IN_JS_INSTANCE] =
(style as any)[CSS_IN_JS_INSTANCE] || cssinjsInstanceId;
// Not force move if no head
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
document.head.insertBefore(style, firstChild);
}
});
// Deduplicate of moved styles
const styleHash: Record<string, boolean> = {};
Array.from(document.querySelectorAll(`style[${ATTR_MARK}]`)).forEach(
(style) => {
const hash = style.getAttribute(ATTR_MARK)!;
if (styleHash[hash]) {
if ((style as any)[CSS_IN_JS_INSTANCE] === cssinjsInstanceId) {
style.parentNode?.removeChild(style);
}
} else {
styleHash[hash] = true;
}
},
);
}
return new CacheEntity(cssinjsInstanceId);
}
缓存核心
// cachedStyleStr css 样式字符串
const [cachedStyleStr, cachedTokenKey, cachedStyleId] = useGlobalCache<CacheType>(
prefix: string,
keyPath: KeyType[],
cacheFn: () => CacheType,
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
// Add additional effect trigger by `useInsertionEffect`
onCacheEffect?: (cachedValue: CacheType) => void,
)
export default function useGlobalCache<CacheType>(
prefix: string,
keyPath: KeyType[],
cacheFn: () => CacheType,
onCacheRemove?: (cache: CacheType, fromHMR: boolean) => void,
// Add additional effect trigger by `useInsertionEffect`
onCacheEffect?: (cachedValue: CacheType) => void,
): CacheType {
const { cache: globalCache } = React.useContext(StyleContext);
const fullPath = [prefix, ...keyPath];
const deps = fullPath.join('_');
const register = useEffectCleanupRegister([deps]);
const HMRUpdate = useHMR();
type UpdaterArgs = [times: number, cache: CacheType];
const buildCache = (updater?: (data: UpdaterArgs) => UpdaterArgs) => {
globalCache.update(fullPath, (prevCache) => {
const [times = 0, cache] = prevCache || [];
// HMR should always ignore cache since developer may change it
let tmpCache = cache;
if (process.env.NODE_ENV !== 'production' && cache && HMRUpdate) {
onCacheRemove?.(tmpCache, HMRUpdate);
tmpCache = null;
}
const mergedCache = tmpCache || cacheFn();
const data: UpdaterArgs = [times, mergedCache];
// Call updater if need additional logic
return updater ? updater(data) : data;
});
};
// Create cache
React.useMemo(
() => {
buildCache();
},
/* eslint-disable react-hooks/exhaustive-deps */
[deps],
/* eslint-enable */
);
let 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);
}
const cacheContent = cacheEntity![1];
// Remove if no need anymore
useCompatibleInsertionEffect(
() => {
onCacheEffect?.(cacheContent);
},
(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(([times, cache]) => {
if (polyfill && times === 0) {
onCacheEffect?.(cacheContent);
}
return [times + 1, cache];
});
return () => {
globalCache.update(fullPath, (prevCache) => {
const [times = 0, cache] = prevCache || [];
const nextCount = times - 1;
if (nextCount === 0) {
// Always remove styles in useEffect callback
register(() => onCacheRemove?.(cache, false));
return null;
}
return [times - 1, cache];
});
};
},
[deps],
);
return cacheContent;
}
如何打包出 css in js 单独样式?
1、动态生成 style 标签
2、与包裹组件一起渲染
3、操作DOM 把body中的style 标签插入到head中
如何生成样式字符串?
1、在一次的组件缓存中调用了两次 useGlobalCache,第一次缓存当前主题token,第二次缓存组件样式
2、首次生成样式 调用 cacheFn 回调方法,获取样式字符串
3、cacheFn 调用, 命中缓存 调用getStyleAndHash获取DOM中标签的样式,否则调用styleFn获取样式对象,通过parseStyle、normalizeStyle 解析生成样式字符串
何时注入主题样式?
在调用 styleFn 回调函数时,会把主题token传递到样式函数,在业务侧实现主题样式修改。
const wrapSSR = useStyleRegister(
{ theme, token, hashId, path: [prefixCls] },
() => [
genDefaultButtonStyle(defaultCls, token),
genPrimaryButtonStyle(primaryCls, token),
genGhostButtonStyle(ghostCls, token),
],
);
const genDefaultButtonStyle = (
prefixCls: string,
token: DerivativeToken,
): CSSInterpolation =>
genSolidButtonStyle(prefixCls, token, () => ({
backgroundColor: token.componentBackgroundColor,
color: token.textColor,
'&:hover': {
borderColor: token.primaryColor,
color: token.primaryColor,
},
}));
总结脑图