Antd Button 源码分析(二)

Button 组件样式的生成

本文主要分析 Button 组件样式的生成过程。

接下来就以 useStyle 函数作为入口点函数进行分析。

样式生成过程:

  1. 调用 useToken 函数获取 Token(包含主题和组件)。useToken 会在下一篇博客中进行分析,它用来生成 theme 和 token。
  2. 生成组件 Token。
  3. 根据 Token 生成组件的样式对象。
  4. 解析样式对象,缓存并插入到 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 中。 三种样式:

  1. 为 a 标签生成样式。
  2. 为 icon 生成样式。
  3. 为组件生成样式。这里如果 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 一起返回。

  1. 参数不是 css 对象,并且当前解析的是最外层(root 为 true),直接将内容追加的结果集中。
  2. 解析 keyframe(有"_keyframe" 属性),保存在 effectStyle 对象中,effectStyle 会作为结果返回。
  3. 解析 css 对象
    例:cssObj = {'&': {a: {backGroundColor: '#fff', &:hover{color:'#51ccee'}}}} 最终解析为 ':where(.hashId) &{a{background-color: #61dafb; &:hover{color: #51ccee}}}'
    1. 解析选择器。通过设置 root 为 true,表示当前解析的是选择器名称。
      • 如果使用 hashId,则为选择器名添加 hashId 作为前缀,并根据 hashPriority 调整 css 优先级。 当 hashPriority === "low" 时,添加伪类函数 ":where(.hashId)" 来降低类选择器的优先级。
      • 如果类选择器名称是 '&' 或 '' 不符合 stylis 语法,所以直接解析内层。
    2. 解析内容,如上例中 a 的 value 部分。这一部分也是调用 parseStyle 解析但是会将 root 置为 false
      • 处理样式名:会将驼峰形式的属性名会先换行为 css 标准属性。
      • 处理样式值:会判断是否需要添加单位,如果值有单位并且是 number 类型会添加"px"。
  4. 如果解析的是内容,为它添加"{}" 即 background-color: '#fff' => {background-color: '#fff'}
  5. 处理 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;
};
相关推荐
. . . . .34 分钟前
react-navtive实战记录
react.js·android-studio
Aphasia31111 小时前
手写KeepAlive组件
前端·react.js·面试
whatever who cares16 小时前
大型 React 项目的文件结构
前端·react.js·前端框架
假如让我当三天老蒯17 小时前
useCallback 详细解释(从原理到用法)(自学用)
前端·react.js
Vu46117 小时前
nextjs的图片和文字优化
react.js
gogoing20 小时前
React Hooks 完整指南
react.js
假如让我当三天老蒯21 小时前
State和Props区别和左右(自学用)
前端·react.js
夜雪闻竹1 天前
React Query + REST API 最佳实践
前端·react.js·前端框架
戈德斯文1 天前
我做了一面互联网摸鱼墙:从无限 Canvas 到本地生产环境
react.js·canvas·next.js