Основная проблема, с которой вы, похоже, сталкиваетесь, заключается в том, что компилятор использует некоторую эвристику для определения, принимать ли значение, подобное "a"
, и выводить его тип как строковый литерал "a"
, или стоит ли расширять его до string
. Строковые литеральные значения, присвоенные не const
переменным или параметрам функции, имеют тенденцию расширяться до string
по умолчанию.
Как вы заметили, один из способов предотвратить это расширение - использовать const
утверждение . Тип "a" as const
всегда будет "a"
и не будет расширен до string
. Это работает в вашем случае, но накладывает бремя на абонент из Box
, и вы, вероятно, хотели бы, чтобы это произошло, не требуя, чтобы люди, использующие Box
, писали as const
в любом месте.
Другой способ предотвратить расширение состоит в том, чтобы иметь значение в узле вывода общего типа c, которое было ограничено для типа, содержащего string
или строковый литерал. (Вы можете прочитать больше о конкретных правилах в запросе на выборку, реализующем это поведение, microsoft / TypeScript # 10676 .) Так что если у вас есть declare function foo<T extends string>(x: T): void;
и вызов foo("a")
, то T
будет выведено как "a"
. Но , если у вас bar<T extends unknown>(x: T): void;
и звоните bar("a")
, то T
будет выводиться как string
. Будет работать тип объединения, содержащий нечто, присваиваемое string
, поэтому T extends string | number
все равно даст вам "a"
.
Может быть, теперь вы думаете: «Хорошо, keyof JSX.IntrinsicElements | unknown
должен быть объединением, содержащим нечто, присваиваемое string
, поскольку keyof JSX.IntrinsicElements
выглядит как "a" | "abbr" | "address" | "area" | "article" | ...
, и каждый из них является строковым литералом". Ну, как вы также заметили, unknown
действительно все испортило. Видите ли, unknown
является верхним типом TypeScript . тип верха - универсальный супертип ; любой тип X
будет подтипом unknown
. И, следовательно, X | unknown
будет эквивалентно unknown
. Компилятор агрессивно упрощает любое объединение с unknown
до unknown
. И вы теряете "a" | "abbr" | ...
..., который больше не намекает компилятору на предотвращение расширения с "a"
до string
.
Итак, обходной путь здесь, вероятно, состоит в том, чтобы отказаться от явного unknown
и вместо этого использовать что-то, что эквивалентно ему с точки зрения того, какие типы присваиваются, но который компилятор сохраняет как объединение и не агрессивно упрощать. До введения unknown
вам нужно было использовать что-то вроде {} | undefined | null
для обозначения «всех возможных типов», и это все еще работает:
type Unknown = {} | undefined | null;
type AsType =
| keyof JSX.IntrinsicElements
| Unknown
И тогда вы получите желаемое поведение:
<Box as="a" tag="tag" href /> // error!
// ~~~~ <-- true is not a string
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код