听说你很会用 flex?那你知道 flex 属性的校验规则吗?

前言

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

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

jsx 复制代码
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,∞]

tsx 复制代码
/**
 * 校验是否是 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 是以 字符串 的形式传进来的

tsx 复制代码
/**
 * 校验是否是 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 的样式来处理的

tsx 复制代码
  /**
   * 单值语法检验
   * @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
tsx 复制代码
  /**
   * 双值语法检验
   * @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 的顺序依次校验

tsx 复制代码
  /**
   * 三值语法检验
   * @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 属性值

tsx 复制代码
  // 语法校验: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"

flex="auto"

flex="2 2px"

flex="2 max-content"

flex="1 1 2px"

结尾

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

地址是:happy-ui

相关推荐
小林ixn7 小时前
从零到一理解 React 父子组件通信:手写一个 Todo 应用带你彻底搞懂单向数据流
前端·javascript·react.js
qetfw10 小时前
MXU:Tauri 2 + React 的 MaaFramework 跨平台 GUI 源码
前端·python·react.js·前端框架·开源项目·效率工具
鱼毓屿御11 小时前
从「只会聊」到「边想边做」的跃迁
前端·学习·react.js
嘟嘟071712 小时前
搞懂 useState 从原生 DOM 到 React 惰性初始化的完整链路
前端·react.js·编程语言
先吃饱再说15 小时前
React 组件设计:从 Props 传递到组件封装
前端·react.js
小月土星17 小时前
React 状态管理核心原理:从批量 DOM 到派生状态
react.js
思码梁田19 小时前
CSS cursor 属性全解析:从“小手“开始,玩转鼠标光标
前端·css·计算机外设·cursor·pointer
烬羽19 小时前
AI 写代码总翻车?试试"先画图再砌墙"的 Vibe Coding 三步法
react.js·ai编程·vibecoding
光影少年19 小时前
react navite 页面跳转、传参、路由监听、导航栏自定义
前端·react native·react.js
黑土豆20 小时前
CSS 新特性:Container Queries 到底能解决什么痛点?
前端·css