Search code examples
typescripttype-declaration

Typescript class states


Consider User class:

class User {
  isAuthenticated: boolean
  friends: User[] | undefined,
}

User has friends only if he is authenticated. I want to do this:

declare const user: User

if (user.isAuthenticated) {
  // user.friends is an array of Users
} else {
  // user.friends is undefined
}

Splitting the class into two classes User and AnonymousUser is not a solution.

UPDATE: Maybe my question wasn't clear enough. I guarantee that if user isAuthenticated, his friends field will be an array, otherwise undefined. I want to tell typescript about that. Something like this:

class User {
  isAuthenticated: boolean
  // Here I don't know what to do.
  friends: this.isAuthenticated extends true ? User[] : undefined
}

Solution

  • You can almost do this using a feature called user-defined type guards:

    class User {
        private isAuthenticated_: boolean;
    
        public isAuthenticated(): this is User & HasFriends {
            return this.isAuthenticated_;
            // At the call sites, you will need to "guard" some code with a condition
            // involving this function. `this` will get a static type "upgrade" in the
            // `true` branch only.
        }
    }
    
    interface HasFriends {
        friends: User[];
    }
    

    The difference to what you want is that you cannot make the original property User.isAuthenticated itself serve double duty as a type guard. Getters cannot be type guards either, thus the above solution involving a function.

    Now you can do this:

    if (someUser.isAuthenticated()) {
        // Here, TypeScript will see `someUser` as being typed `User & HasFriends`
        // and allow you to access its `friends` property (regardless of whether
        // it is actually defined on the object or not).
        celebrateWith(someUser.friends);
    }
    else {
        // Here, `someUser`'s static type remains unchanged, so friends isn't
        // visible to TypeScript (again, regardless of actual existence at runtime).
    }