26
loading...
This website collects cookies to deliver better user experience
function fn(x: unknown) {
const isString = typeof x === 'string'
if (isString) {
x.length // Ok
}
}
x.length
is a type error...isString
is true
, x
must be a string
, the type checker doesn't know that!isString
is just a stupid old boolean - it doesn't know or care why it happens to be true or false.typeof x === 'string'
has to be inlined inside the if statement (or ternary if you're that way inclined).function fn(x: unknown) {
if (typeof x === 'string') {
x.length // Ok
}
}
function brokenIsStr(x: unknown): x is string {
return typeof x !== 'string'
}
isString
is now imbued with the implication our brains associate with it - TS understands that iff isString
is true
, x
must be a string
.foo
's type can only imply something about bar
when foo
is const
and either:bar
in the current scope (i.e. foo
is a boolean)bar
(i.e. bar
is a discriminated union)function fn(x: unknown) {
const isString = typeof x === 'string'
const twoLevelsDeep = isString || isString
const threeLevelsDeep = twoLevelsDeep || isString
const fourLevelsDeep = threeLevelsDeep || isString
const fiveLevelsDeep = fourLevelsDeep || isString
const sixLevelsDeep = fiveLevelsDeep || isString
const justOneLevelDeep = isString || isString || isString || isString || isString || isString
if(fiveLevelsDeep) {
x // string
}
if(sixLevelsDeep) {
x // unknown
}
if(justOneLevelDeep) {
x // string
}
}
foo
cannot influence the inferred type of bar
here:function fn({ foo, bar }: Baz) {
...
baz
:function fn(baz: Baz) {
const { foo, bar } = baz
...
type ShoppingStep = {
step: "shopping"
discountCode?: string
loggedIn: boolean
}
type SelectShippingStep = Omit<ShoppingStep, "step"> & {
step: "select-shipping"
items: Array<Item>
}
type ConfirmOrderStep = Omit<SelectShippingStep, "step"> & {
step: "confirm-order"
shippingAddress: Address
}
export function OnlineShop(): JSX.Element {
const [state, setState] = useState<
ShoppingStep | SelectShippingStep | ConfirmOrderStep
>({
step: "shopping",
loggedIn: false,
})
...
}
function Catalogue(props: ShoppingStep): JSX.Element
function ShippingSelect(props: SelectShippingStep): JSX.Element
function ConfirmOrder(
props: ConfirmOrderStep & {
freeShipping: boolean;
children?: ReactNode
},
): JSX.Element
const shippingMessage =
"shippingAddress" in state &&
checkFreeShippingEligibility(
state.items,
state.shippingAddress
)
? `Congrats! Free shipping on ${state.items.length} items!`
: undefined
switch (state.step) {
case "shopping":
return <Catalogue {...state} />
case "select-shipping":
return <ShippingSelect {...state} />
case "confirm-order":
return (
<ConfirmOrder
{...state}
freeShipping={
"shippingAddress" in state &&
checkFreeShippingEligibility(
state.items,
state.shippingAddress
)
}
>
{shippingMessage ?? "Now pay up!"}
</ConfirmOrder>
)
}
const freeShipping =
"shippingAddress" in state &&
checkFreeShippingEligibility(
state.items,
state.shippingAddress
)
const shippingMessage =
freeShipping
? `Congrats! Free shipping on ${state.items.length} items!`
: undefined
...
case "confirm-order":
return (
<ConfirmOrder {...state} freeShipping={freeShipping}>
{shippingMessage ?? "Now pay up!"}
</ConfirmOrder>
)
? `Congrats! Free shipping on ${state.items.length} items!`
state.items
not necessarily being present: here's proof.const shippingMessage =
"shippingAddress" in state && freeShipping
? `Congrats! Free shipping on ${state.items.length} items!`
: undefined
const hasShippingAddress = "shippingAddress" in state
// `hasShippingAddress` conditional alias
// allows state to be narrowed to ConfirmOrderStep
// so `items` and `shippingAddress` are known to be present
const freeShipping =
hasShippingAddress &&
checkFreeShippingEligibility(
state.items,
state.shippingAddress
)
// state is again narrowed to ConfirmOrderStep because
// `freeShipping` is an aliased conditional twice removed!
const shippingMessage = freeShipping
? `Congrats! Free shipping on ${state.items.length} items!`
: undefined
const {step} = state
// switching on an (aliased) destructured discriminant property
switch (step) {
...
case "confirm-order":
return (
<ConfirmOrder {...state} freeShipping={freeShipping}>
{shippingMessage ?? "Now pay up!"}
</ConfirmOrder>
)
}