TypeScript - возможно ли проверять типы строк на основе сопоставления с образцом или даже длины?

Рассмотрим следующий компонент, который использует библиотеку с именем 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.

github.com/Microsoft/TypeScript/issues/…, и с этого момента больше разговоров о регулярном выражении как о типе
Amir-Mousavi 13.02.2019 17:16

Вот реализация для одного: stackoverflow.com/questions/68064202/how-to-type-a-color-pro‌​p/…

gaitat 06.08.2021 19:33
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
5
2
3 793
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

13.06.2021: Обновлено для TS4.1+


Не совсем во время компиляции, нет. Существует предложение (теперь в microsoft/TypeScript#41160), разрешающий типы строк, проверенные регулярным выражением, но неясно, будет ли он когда-либо реализован. Если вы хотите перейти к этому предложению, поставить ему ? и описать убедительный вариант использования, которого еще нет в списке, это не повредит (но, вероятно, и не поможет).

Вы можете пытаться использовать литеральные типы шаблонов для этого, чтобы программно сгенерировать большой союз, который соответствует каждому допустимому строковому литералу. Это даже работает, если вам нужны только три цифры:

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

Поэтому вам придется использовать обходной путь.


Одним из обходных путей является использование литеральных типов шаблонов как общийограничение вместо создания конкретного типа ValidColorString. Вместо этого используйте тип AsValidColorString<T>, который принимает строковый тип 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 и создать номинальный подтип string с защита пользовательского типа, чтобы сузить значения 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");
    }

Последнее не идеально, но, по крайней мере, немного приближает вас к реализации таких ограничений.

Надеюсь, что это дает вам некоторые идеи; удачи!

Ссылка на код для игровой площадки

хм, это позор, но я думаю, это лучше, чем ничего. В конечном счете, я думаю, что это не подходит для моего варианта использования, но, тем не менее, я ценю информацию и примеры.

Robbie Milejczak 13.02.2019 20:33

Другие вопросы по теме