【问题标题】:TypeScript - is it possible validate string types based on pattern matching or even length?TypeScript - 是否可以根据模式匹配甚至长度验证字符串类型?
【发布时间】:2019-07-07 13:05:45
【问题描述】:

考虑以下组件,它使用一个名为 styled-components 的库来创建一个预先设置样式的 Text 组件:

const StyledText = styled(Text)`
  font-family: Roboto;
  color: ${(props: ITextProps) => props.color || '#000' }; 
`

ITextProps 在哪里:

interface ITextProps {
  color: string;
}

有没有办法强制只将有效的十六进制字符串传递给我的组件?

理想情况下,颜色属性应始终与模式/#\d{3}(\d{3})?/g 匹配,# 后跟至少 3 位数字,也可以选择多 3 位数字。如果这不可能,那么有没有办法至少强制字符串长度为 4 或 7 个字符?

我的研究让我陷入了死胡同,所以我想知道 SO 上是否有人知道我如何在 TypeScript 中实现这种行为。

【问题讨论】:

标签: reactjs typescript react-native


【解决方案1】:

2021-06-13:针对 TS4.1+ 更新


不是真的在编译时,不。有一个suggestion (now at microsoft/TypeScript#41160) 允许正则表达式验证的字符串类型,但尚不清楚它是否会被实现。如果您想采纳该建议并给它一个 ? 并描述一个尚未列出的引人注目的用例,它不会受到伤害(但它可能也不会真正有帮助)。

您可以尝试为此使用template literal types 以编程方式生成一个大的union,它匹配每个可接受的字符串文字。如果您只需要三位数,这甚至可以工作:

type UCaseHexDigit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | 
  '8' | '9' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
type HexDigit = UCaseHexDigit | Lowercase<UCaseHexDigit>
type ValidThreeDigitColorString = `#${HexDigit}${HexDigit}${HexDigit}`;
// type ValidThreeDigitColorString = "#000" | "#001" | "#002" | "#003" | "#004" | "#005" 
// | "#006" |  "#007" | "#008" | "#009" | "#00A" | "#00B" | "#00C" | "#00D" | "#00E" 
// | "#00F" | "#00a" | "#00b" | "#00c" | "#00d" |  // "#00e" 
// | ... 10626 more ... | "#fff"

但由于此类模板文字类型只能处理数万成员的联合,因此如果您尝试使用六位数执行此操作,则会中断:

type ValidSixDigitColorString =
    `#${HexDigit}${HexDigit}${HexDigit}${HexDigit}${HexDigit}${HexDigit}`; // error!
//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Expression produces a union type that is too complex to represent

所以你必须使用一种解决方法。


一种解决方法是使用模板文字类型作为generic 约束,而不是使ValidColorString 成为具体类型。相反,有一个像AsValidColorString&lt;T&gt; 这样的类型,它接受一个字符串类型T检查它以查看它是否有效。如果是,它就会被单独留下。如果不是,则返回一个与错误颜色“接近”的有效颜色字符串。例如:

type ToHexDigit<T extends string> = T extends HexDigit ? T : 0;
type AsValidColorString<T extends string> =
    T extends `#${infer D1}${infer D2}${infer D3}${infer D4}${infer D5}${infer D6}` ?
    `#${ToHexDigit<D1>}${ToHexDigit<D2>}${ToHexDigit<D3>}${ToHexDigit<D4>}${ToHexDigit<D5>}${ToHexDigit<D6>}` :
    T extends `#${infer D1}${infer D2}${infer D3}` ?
    `#${ToHexDigit<D1>}${ToHexDigit<D2>}${ToHexDigit<D3>}` :
    '#000'

const asTextProps = <T extends string>(
   textProps: { color: T extends AsValidColorString<T> ? T : AsValidColorString<T> }
) => textProps;

这很复杂;大多数情况下,它会拆分字符串T 并检查每个字符,将坏字符转换为0。然后,不要将某些内容注释为 TextProps,而是调用 asTextProps 进行验证:

const textProps = asTextProps({
    color: "#abc" // okay
})

const badTextProps = asTextProps({
    color: "#00PS1E" // error
//  ~~~~~
//  Type '"#00PS1E"' is not assignable to type '"#00001E"'.(2322)
})

这在编译时有效,但可能比它的价值更麻烦。


最后,您可以退回到 TS4.1 之前的解决方案,并使用 user-defined type guardstring 的值缩小到它的范围内,并创建一个名义上的 string 子类型...然后跳过各种使用它的箍:

    type ValidColorString = string & { __validColorString: true };

    function isValidColorString(x: string): x is ValidColorString {
      const re = /#[0-9a-fA-F]{3}([0-9a-fA-F]{3})?/g; // you want hex, right?
      return re.test(x);
    }

用法:

    const textProps: ITextProps = {
      color: "#abc"
    }; // error, compiler doesn't know that "#abc" is a ValidColorString
    
    const color = "#abc";
    if (isValidColorString(color)) {
      const textProps2: ITextProps = {
        color: color
      }; // okay now
    } else {
      throw new Error("The world has ended");
    }

后者并不完美,但至少可以让您更接近强制执行此类约束。

希望能给你一些想法;祝你好运!

Playground link to code

【讨论】:

  • 嗯,这很可惜,但我想总比没有好。最终我认为这不适合我的用例,但我仍然感谢这些信息和示例。
猜你喜欢
  • 1970-01-01
  • 2018-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-16
  • 1970-01-01
相关资源
最近更新 更多