打字稿类状态

时间:2019-03-07 17:46:27

标签: typescript type-declaration

考虑User类:

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

用户只有通过身份验证才能拥有朋友。我要这样做:

declare const user: User

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

将类分为UserAnonymousUser两个类不是解决方案。

更新:也许我的问题还不够清楚。我保证如果用户isAuthenticated,他的friends字段将是一个数组,否则为undefined。我想告诉打字稿。像这样:

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

2 个答案:

答案 0 :(得分:2)

请记住TypeScript的类型检查是静态。它发生在编译时。因此,您不能让TypeScript进行取决于运行时条件的类型检查。

您当然可以按照自己的方式声明该类,但是对于一个实例,将isAuthenticated设为true的同时将friends设为undefined(或让friends为数组,而isAuthenticatedfalse)。

对于此类型的静态类型检查,您需要使用在以下问题中排除的解决方案:匿名和已验证用户的单独类型。验证AnonymousUser的操作将返回等效的(但已验证)User对象。 (撤销身份验证的行为将类似地返回一个等效的[但未经身份验证的] AnonymousUser对象。)

鉴于您不想这样做,我想说这些应该是访问器属性,以便类中的逻辑可以确保它们是一致的。例如:

class User {
    private _friends: User[] | undefined = undefined;
    get isAuthenticated(): boolean {
        return this.friends !== undefined;
    }
    get friends(): User[] | undefined {
        return this._friends;
    }
}

...然后类中的逻辑通过设置this._friends to [];来使用户认证(或通过设置this._friends = undefined;来撤消认证)。这样,实例就不会不一致,因为这两个属性都依赖于相同的基础状态。

(该示例使用TypeScript的private版本,但如果使用的话,当然可以使用JavaScript's private fields。)


如果可以保证实例符合类型,则可以为此使用联合类型。假设--strictNullChecks

declare type UnauthenticatedUser = {
    isAuthenticated: false;
    friends: undefined;
};

declare type AuthenticatedUser = {
    isAuthenticated: true;
    friends: User[];
}

declare type User = UnauthenticatedUser | AuthenticatedUser;

// Works
const u1: User = {
    isAuthenticated: true,
    friends: []
};

// Works
const u2: User = {
    isAuthenticated: false,
    friends: undefined
};

// Errors:
// > Type '{ isAuthenticated: true; friends: undefined; }' is not assignable to type 'User'.
// > Type '{ isAuthenticated: true; friends: undefined; }' is not assignable to type 'AuthenticatedUser'.
// > Types of property 'friends' are incompatible.
// > Type 'undefined' is not assignable to type 'User[]'."
const u3: User = {
    isAuthenticated: true,
    friends: undefined
};

On the playground

然后将正确的类型应用于任何给定的用户。

答案 1 :(得分:2)

您可以使用名为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[];
}

与您想要的不同之处在于,您不能使原始的属性 User.isAuthenticated本身担当类型防护的双重职责。 Getters 也不是类型保护,因此上述解决方案涉及 function

现在您可以执行以下操作:

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).
}