Типы классов Typescript - PullRequest
       47

Типы классов Typescript

0 голосов
/ 07 марта 2019

Рассмотрим 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
}

Разделение класса на два класса User и AnonymousUser не является решением.

ОБНОВЛЕНИЕ: Может быть, мой вопросне было достаточно ясноЯ гарантирую, что если пользователь isAuthenticated, его поле friends будет массивом, в противном случае undefined.Я хочу рассказать об этом машинописи.Как то так:

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

Ответы [ 2 ]

2 голосов
/ 07 марта 2019

Вы можете почти сделать это, используя функцию, называемую охранники определяемого пользователем типа :

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 само по себе выполнять двойную функцию в качестве охранника типа. Получатели также не могут быть защитниками типов, поэтому приведенное выше решение включает функцию .

Теперь вы можете сделать это:

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).
}
2 голосов
/ 07 марта 2019

Помните, что проверка типов в TypeScript static . Это происходит во время компиляции. Таким образом, вы не можете использовать TypeScript для проверки типов, которая зависит от условий выполнения.

Вы, конечно, можете объявить класс так, как вы это делаете, но для экземпляра будет вполне возможно иметь isAuthenticated быть true при friends быть undefined (или friends быть массив, в то время как isAuthenticated равен false).

Для статической проверки типов вам необходимо использовать решение, исключенное в вопросе: отдельные типы для анонимных и аутентифицированных пользователей. Действие аутентификации 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, но, конечно, вместо нее можно использовать закрытые поля JavaScript , если вы их используете.)


Если вы можете гарантировать, что экземпляры будут соответствовать типам, вы можете использовать для этого типы объединения. Предполагая, --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
};

на детской площадке

Затем примените правильный тип к любому пользователю.

...