I’ve poured over the TS docs, but can’t seem to find an answer as to why the following is illegal.
I’m able to define a union type with strings, yet if I create the same union type with references to object properties, which ultimately references the same string values, the compiler isn’t happy.
Demo via typescript playground
// Success export type SortOrderWithStrings = 'asc' | 'desc'; export const SortOrderStatic = { ASC: 'asc', DESC: 'desc', } // Fail export type SortOrderWithObject = SortOrderStatic.ASC | SortOrderStatic.DESC;
The errors are as follows. Why is this a namespace issue?
Cannot find namespace 'SortOrderStatic' Exported type alias 'SortOrderReference' has or is using private name 'SortOrderType'
Ultimately, it’d be nice to have a Column
interface the defines the supported sort order types, while then instancing a Column
with SortOrderStatic
to avoid magic strings, but per the above, this doesn’t seem possible.
interface Column { label: string; defaultSortOrder: SortOrderWithObject; } const MyColumn: Column = { label: 'My Column', defaultSortOrder: SortOrderStatic.ASC }
If I define the interface as follows (with SortOrderWithStrings
), and then attempt to use SortOrderStatic
, the compiler once again isn’t happy.
interface Column { label: string; defaultSortOrder: SortOrderWithStrings; } const MyColumn: Column = { label: 'My Column', defaultSortOrder: SortOrderStatic.ASC }
Fails with:
Type 'string' is not assignable to type 'SortOrderWithStrings'.
It’s clear I’m missing something fundamental here, and I appreciate any insight.
Advertisement
Answer
It’s clear I’m missing something fundamental here
Yes, you are confusing compile-time with runtime: unless the compile-time type is a literal string, the compiler cannot guarantee that the runtime access of an object property is the string you think it is. Your object property is type string
, which means at runtime it could be any string.
The string literal is immutable (and so statically verifiable), the object property access not so much.
In your particular case I would just use an enum:
enum SortOrder { ASC = 'ASC', DESC = 'DESC', } export { SortOrder } import { SortOrder } from 'something'; SortOrder.ASC; // ASC as a string
This will maintain type-safety, you can’t pass any ol’ string to something that expects a SortOrder
: (Playground)