Рассмотрим следующий компонент, который использует библиотеку с именем 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.
Вот реализация для одного: stackoverflow.com/questions/68064202/how-to-type-a-color-prop/…





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");
}
Последнее не идеально, но, по крайней мере, немного приближает вас к реализации таких ограничений.
Надеюсь, что это дает вам некоторые идеи; удачи!
Ссылка на код для игровой площадки
хм, это позор, но я думаю, это лучше, чем ничего. В конечном счете, я думаю, что это не подходит для моего варианта использования, но, тем не менее, я ценю информацию и примеры.