Home > Net >  TypeScript: How to properly narrow a Union of Intersection Types
TypeScript: How to properly narrow a Union of Intersection Types

Time:09-13

I am currently building a component that accepts some routes, and makes a stepper with a nested router view.

I'm having some trouble with satisfying TypeScript.

I am using vue-router's RouteLocationRaw type.

RouteLocationRaw is a union of string and two intersection types defined as

export declare type RouteLocationRaw = string | (RouteQueryAndHash & LocationAsPath & RouteLocationOptions) | (RouteQueryAndHash & LocationAsRelativeRaw & RouteLocationOptions);

export declare interface RouteQueryAndHash {
    query?: LocationQueryRaw;
    hash?: string;
}

export declare interface LocationAsPath {
    path: string;
}

export declare interface RouteLocationOptions {
    replace?: boolean;
    force?: boolean;
    state?: HistoryState;
}

export declare interface LocationAsRelativeRaw {
    name?: RouteRecordName;
    params?: RouteParamsRaw;
}

What I would like to do is compare the name of the current route against those passed into the component like so

const activeRoute = computed(() => props.routes.find((propRoute) => propRoute.name === route.name))

This logic works as I would like, but TypeScript complains. With the approach above I get the following errors.

Property 'name' does not exist on type 'RouteLocationRaw'.
  Property 'name' does not exist on type 'string'.

TypeScript seems to automatically assume that the type is a string which is the first part of the union. Already kind of a strange thing to assume, but narrowing beyond a string isn't helpful either.

If I add a case for handling routes that are of type string TypeScript still doesn't recognize that name could be a property of route.

Property 'name' does not exist on type '(RouteQueryAndHash & LocationAsPath & RouteLocationOptions) | (RouteQueryAndHash & LocationAsRelativeRaw & RouteLocationOptions)'.
  Property 'name' does not exist on type 'RouteQueryAndHash & LocationAsPath & RouteLocationOptions'.

CodePudding user response:

I'm going to simplify your code to this, which has the same error.

declare const propRoute: RouteLocationRaw // the current route, lets say

if (propRoute.name === 'foo') {
  console.log('foo is active')
}

Now to access a property of any value, that type must declare that property. And when you access a property of a union, then all members of that union must declare that. So typescript is not assuming that the value is string, it's telling you that it might be a string and if it is, then this property access doesn't make any sense.

So, to access the name property, you need to narrow the union to types that only support the name property. So you have to filter out string and you have to filter out an object property that lacks that property.

That might look like this:

if (
  typeof propRoute !== 'string' && // filter out string.
  'name' in propRoute && // require the `name` property.
  propRoute.name === 'foo' // check the name proeprty.
) {
  console.log('foo is active')
}

See playground

  • Related