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;
};
相关推荐
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
小牛itbull3 小时前
ReactPress:构建高效、灵活、可扩展的开源发布平台
react.js·开源·reactpress
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
小牛itbull8 小时前
ReactPress – An Open-Source Publishing Platform Built with React
前端·react.js·前端框架
贵州晓智信息科技11 小时前
深入理解 React 架构从概览到核心机制
前端·react.js·架构
September_ning13 小时前
JavaScript的展开运算符在React中的应用
javascript·react.js
前端小王hs13 小时前
react-markdown标题样式不生效问题
前端·javascript·react.js·前端框架·前端小王hs
键盘上的蚂蚁-13 小时前
duxapp放弃了redux,在duxapp状态实现方案
前端·javascript·react.js
前端小王hs13 小时前
react-markdown内容宽度溢出和换行不生效问题
前端·javascript·react.js·前端框架·前端小王hs