typescript - Tvinga korrekt användning av egenskaper
Säg att jag har en typ som representerar en användare med deras inloggning och förnamn:
export interface User {
login: string;
name: string;
}
Nu vill jag fråga ett API med den användarens inloggning:
const formatLogin (login: string) => login.toLowerCase().trim();
const getUser = (login: string) => ajax(`${API}/users/${formatLogin(login)}`);
Detta fungerar. Senare i min kod kanske jag använder:
getUser(this.user.name);
men det är ett fel. Det borde vara this.user.login. De är båda strängar, så TypeScript klagar inte.
Finns det ett sätt att tvinga att en egenskap används med TypeScript?
Jag har försökt använda export type Username=string men det fungerar inte eftersom name fortfarande uppfyller den typen eftersom det bara är en string. Jag har också försökt export interface Username {} vilket inte fungerar med formatLogin som kräver strängmetoder toLowerCase och trim. interface Username extends String {} har samma problem.
Svar #1
Ett tillvägagångssätt är att använda så kallade märkesdesignerade typer, följande exemplet som ges i källorna till TypeScript-kompilatorn själv:
export type LoginString = string & { __loginBrand: any};
export interface User {
login: LoginString;
name: string;
}
const formatLogin = (login: string) => login.toLowerCase().trim();
const getUser = (login: LoginString) => { };
let user: User;
formatLogin(user.login); // ok
getUser(user.name); // error
Du måste manuellt kasta en vanlig sträng till LoginString-typen när du initierar ett User-objekt, du behöver inte ange ett faktiskt värde för __loginBrand - det är endast för typkontroll.
Ett annat tillvägagångssätt, föreslaget också av @estus, är att använda destruerat objekt som en slags namngivna parametrar för API:et. Det är inte typkontrollerat, men alla avvikelser märks tydligt, förutsatt att alla namn används konsekvent överallt:
export interface GetUser {
login: string;
}
const getUser = ({login}: GetUser) => {}
getUser(user); // ok
getUser({login: user.name}); // huh?
Svar #2
Det är omöjligt för typningssystemet att särskilja mellan dessa två egenskaper eftersom de är godtyckliga strängar.
Detta är området där testtäckning bör hjälpa till att eliminera mänskliga fel.
Problemet skulle kunna lösas inte med typer utan med en rimlig metod för namngivning av metoder som inte lämnar plats för fel. Till exempel detta misstag
getUserByLogin(this.user.name);
kommer sannolikt att dra mer uppmärksamhet.
Beroende på fallet kan getUser acceptera inte en sträng utan en referens till hela objektet och hämta önskad egenskap från det:
getUser = ({ login }: User ) => ajax(`${API}/users/${formatLogin(login)}`);
