你知道怎么校验 flex 属性的值吗?

240 阅读4分钟

前言

今天我和平常一样在更新个人的组件时,封装了 Flex 组件。

在封装的过程中,涉及到了 flex 值的问题:

export default () => {
  const flexStyle = useMemo(() => {
     return {
       flex: value
     }
  }, [flex])
  
  return (
     <div style={{...flexStyle}}></div>
  )
}

然后这一过程里面发现自己对 flex 布局也就只会 justify-content、align-items,用上面那种 flex: 值 的形式都少之又少。

封装完之后跟着MDN文档过了一遍校验后,对 flex 的认识也更深了。所以记录下来

style.flex 值的组成

根据MDN文档中我们知道,flex 由三个属性组成

然后对于每一个属性,我们先简单走一遍:

flex-grow

它的值 只能是 number,取值范围是: <number [0,∞]>

flex-shrink

它和 flex-grow 一样,只能是 number, 取值范围是 <number [0,∞]>

flex-basis

它的取值可以和 style.width 一样,即:2px、3em,也可以是尺寸关键字,比如:fill、max-content、content等,具体可以看flex-basis

简单走了一遍 属性之后,我们来看 flex 使用的方法

style.flex 使用的三种方法

style.flex 使用的时候,可以以一个值两个值三个值的方式来指定 style.flex 属性

  • 单值语法:值必须是以下之一:

    • 一个 <flex-grow> 的有效值:此时简写会扩展为 flex: <flex-grow> 1 0
    • 一个 <flex-basis> 的有效值:此时简写会扩展为 flex: 1 1 <flex-basis>
    • 关键字 none 或者全局关键字之一。
  • 双值语法

    • 第一个值必须是一个 flex-grow 的有效值。

    • 第二个值必须是以下之一:

      • 一个 flex-shrink 的有效值:此时简写会扩展为 flex: <flex-grow> <flex-shrink> 0
      • 一个 flex-basis 的有效值:此时简写会扩展为 flex: <flex-grow> 1 <flex-basis>
  • 三值语法:值必须按照以下顺序指定:

    1. 一个 flex-grow 的有效值。
    2. 一个 flex-shrink 的有效值。
    3. 一个 flex-basis 的有效值。

好了,清楚了上面的语法之后,我们来写一下他们的判断函数

flex-grow、flex-shrink 的判断

前面说了,flex-grow 和 flex-shrink 的类型只能是 number,且范围是 [0,∞]

/**
 * 校验是否是 flex-grow 的有效值
 * @param str
 * @returns
 */
const isFlexGrow = (str: string): boolean => {
  return /^([1-9]+(\.\d+)?)|([0](\.\d+)?)$/.test(str);
};

/**
 * 校验是否是 flex-shrink 的有效值
 * @param str
 * @returns
 */
const isFlexShrink = (str: string): boolean => {
  return /^([1-9]+(\.\d+)?)|([0](\.\d+)?)$/.test(str);
};

flex-basis 的判断

flex-basis 的判断稍微复杂点。因为它可以是 style.width 的取值,也可以是 尺寸关键字

在这里,因为我封装 Flex 组件的时候,flex 是以 字符串 的形式传进来的

/**
 * 校验是否是 flex-basis 的有效值
 * @param str
 * @returns
 */
const isFlexBasis = (str: string): boolean => {
  // 如果传入的 flex-basis 是 尺寸关键词
  if (flexBasisKeyWords.includes(str)) {
    return true;
  }
  // 如果传入的是 20px、12em 这样的带单位的值
  const regs = [
    'px',
    'in',
    'cm',
    'mm',
    'pt',
    'pc',
    'em',
    'rem',
    'ex',
    'vh',
    'vw',
    'vmin',
    'vmax',
    '%',
    'fr',
  ];
  const splitArr = str.split(/[0-9]/);
  const currentReg = splitArr[splitArr.length - 1];
  if (currentReg) {
    return regs.includes(currentReg);
  } else {
    return false;
  }
};

flex 值的合法校验(重点)

接下来,就是最重要的 flex单值双值三值 的情况下使用时的检验方法

单值检验

前面说了,单值语法传入的值必须是 flex-growflex-basis 其中的一个有效值。

  • 如果传入的是 flex-grow 的有效值,则 flex 的样式规则为: flex: <flex-grow> 1 0
  • 如果传入的是 flex-basis 的有效值,则 flex 的样式规则为: flex: 1 1 <flex-basis>

然后,还需要注意一下部分关键字:nonecontentfillauto 时的 flex 值(这部分是我参照 antd 的样式来处理的

  /**
   * 单值语法检验
   * @param single
   * @returns
   */
  const validateSingleValued = (single: string): CSSProperties => {
    // 如果只包含数值,说明此时传入的是 flex-grow 属性的有效值,此时简写会扩展为 flex: <flex-grow> 1 0。
    // 比如: flex: 1
    if (isFlexGrow(single)) {
      return {
        flex: `${single} 1 0%`,
      };
    } else if (isFlexBasis(single)) {
      // 如果传入的是带单位的数字,或者是 flex-basis 的尺寸关键词时,此时是 flex-basis 的有效值,此时拓展为: flex: 1 1 <flex-basis>
      // 比如:flex: 3em 、flex: max-content
      return single !== 'fill' &&
        single !== 'content' &&
        single !== 'none' &&
        single !== 'auto'
        ? {
            flex: `1 1 ${single}`,
          }
        : single === 'none'
        ? {
            flex: '0 0 auto',
          }
        : single === 'auto'
        ? {
            flex: '1 1 auto',
          }
        : {};
    } else if (flexSigleGlobals.includes(single)) {
      // 如果传入的是 单值时的 全局值,如:flex: 'inherit'
      return {
        flex: single !== 'none' ? `${single}` : '0 0 auto',
      };
    } else {
      return {};
    }
  };

双值校验

根据规则:

  • 第一个值必须是 flex-grow 的有效值
  • 第二个值必须是 flex-basis 的有效值或者 flex-shrink 的有效值
    • 如果是 flex-basis 的有效值,则 flex 的样式规则为 flex: <flex-grow> 1 <flex-basis>
    • 如果是 flex-shrink 的有效值,则 flex 的样式规则为 flex: <flex-grow> <flex-shrink> 0
  /**
   * 双值语法检验
   * @param grow 传入的 flex-grow 值
   * @param second 传入的 flex-basis 或者 flex-shrink 的有效值
   * @returns
   */
  const validateDoubleValued = (grow: string, second: string): CSSProperties => {
    // 首先,第一个值必须是 flex-grow 的有效值,此时必须是 number,值是 [0, 正无穷],负值无效,默认为 0
    // https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex-grow
    if (!isFlexGrow(grow)) {
      return {};
    } else if (isFlexBasis(second)) {
      // 第二个值必须是 flex-basis 的有效值 或者是 flex-shrink 的有效值
      // 此时拓展为: flex: <flex-grow> 1 <flex-basis> 或者 flex: <flex-grow> <flex-shrink> 0
      return {
        flex: `${grow} 1 ${second}`,
      };
    } else if (isFlexShrink(second)) {
      return {
        flex: `${grow} ${second} 0%`,
      };
    } else {
      return {};
    }
  };

三值校验

三值校验时,要按照 flex-growflex-shrinkflex-basis 的顺序依次校验

  /**
   * 三值语法检验
   * @param grow 传入的 flex-grow 值
   * @param basis 传入的 flex-basis 值
   * @param shrink 传入的 flex-shrink 值
   * @returns
   */
  const validateTernaryValued = (
    grow: string,
    basis: string,
    shrink: string,
  ): CSSProperties => {
    // 按照 flex-grow flex-shrink flex-basis 的顺序校验是否合法
    return isFlexGrow(grow) && isFlexShrink(shrink) && isFlexBasis(basis)
      ? {
          flex: `${grow} ${shrink} ${basis}`,
        }
      : {};
  };

计算 flex 属性

校验规则搞完后,最后来计算 flex 属性值

  // 语法校验:https://developer.mozilla.org/zh-CN/docs/Web/CSS/flex#%E8%AF%AD%E6%B3%95
  const flexStyle = useMemo(() => {
    // 根据 flexLength,去做单值语法、双值语法、三值语法的校验
    const flexLength = flex && flex !== '' ? flex.split(' ').length : 0;
    // 如果 flex 无效
    if (!flexLength) return {};
    // 校验 flex
    if (flexLength === 1) {
      return validateSingleValued(flex.split(' ')[0]);
    } else if (flexLength === 2) {
      return validateDoubleValued(flex.split(' ')[0], flex.split(' ')[1]);
    } else {
      return validateTernaryValued(
        flex.split(' ')[0],
        flex.split(' ')[2],
        flex.split(' ')[1],
      );
    }
  }, [flex]);

页面效果

flex="1"

image.png

flex="auto"

image.png

flex="2 2px"

image.png

flex="2 max-content"

image.png

flex="1 1 2px"

image.png

结尾

源码的话我现在还没上传到github,但是你可以过几天再来看看,说不定到时候就传上去了

地址是:happy-ui