Exploiter les Discriminated Union en Typescript
De quoi parle-t-on ?
En Typescript, il est possible de définir des types de plusieurs manières: interface, classe, enum, mot-clé
type
, as const
, 등 Dans cet 기사, nous allons nous concentrer sur les types construits à partir d'une union disjointe et les avantages d'une telle pratique. L'union en Typescript se fait via le symbole |
(예: type Union = A | B | C
). Le terme disjoint n'est pas anodin car contrairement au polymorphisme, les types que l'on va utiliser peuvent ne rien avoir en commun.미장 상황
Prenons un exemple très simple, la représentation des utilisateurs dans une application. Ces utilisateurs peuvent être des invités(손님), des 클라이언트(고객) ou des 관리자(관리자). Les utilisateurs connectés ont un identifiant et les administrators ont des permission spécifiques 친척 à leur domaine d'administration. Contruisons donc une 인터페이스
User
실용주의자를 따르십시오.interface User {
userType: "Guest" | "Customer" | "Admin";
login?: string;
accessRights?: string[]; // could be more specific
}
특정 소유주는 옵션이 없습니다. car elles n'existent pas pour l'utilisateur invité(et qu'on use le mode
strict
du compilateur Typescript).Cette 인터페이스 nous permet de créer des utilisateurs valides:
const johnDoe: User = {
userType: "Admin",
login: "JohnDoe",
accessRights: ["database", "monitoring"],
};
const guest: User = {
userType: "Guest",
};
Mais nous pouvons également créer des utilisateurs qui 특파원 à des cas non souhaités :
const customerWithoutLogin: User = {
userType: "Customer",
};
const guestWithAccessRights: User = {
userType: "Guest",
accessRights: ["fs"],
};
Et on ne peut pas garantir si par exemple l'identifiant existe ou pas:
johnDoe.login.toUpperCase(); // Object is possibly 'undefined'.
guest.login.toUpperCase(); // Object is possibly 'undefined'.
customerWithoutLogin.login.toUpperCase(); // Object is possibly 'undefined'.
guestWithAccessRights.login.toUpperCase(); // Object is possibly 'undefined'.
En général on finit par utiliser une condition ou du chainage optionnel, ce qui renforce l'incertitude sur le fonctionnement au runtime :
// Which one is really executed ?
johnDoe.login?.toUpperCase();
guest.login?.toUpperCase();
customerWithoutLogin.login?.toUpperCase();
guestWithAccessRights.login?.toUpperCase();
Pour réduire cette incertitude, il ne nous reste plus qu'à écrire des tests unitaires, faire du monitoring, du debug et lever des exceptions. Heureusement, nous pouvons éviter tout ca avec un meilleur type.
차별된 조합
Explicit is better than implicit
Nous avons trois 유형 d'utilisateurs distinct et les regrouper dans une même interface/classe est une erreur commune. Et c'est normal, on nous répète souvent DRY(Do n't Repeat Yourself) et on a envie de factoriser les utilisateurs dans une même classe ou dans une même interface pour y appliquer des méthodes communes.
Et si on faisait le contraire ? Trois 유형 d'utilisateurs, donc trois 인터페이스.
interface GuestUser {
userType: "Guest";
}
interface CustomerUser {
userType: "Customer";
login: string;
}
interface AdminUser {
userType: "Admin";
login: string;
accessRights: string[];
}
Ensuite, il nous suffit de définir un type qui 해당 à l'union des trois 인터페이스 구별:
type User = GuestUser | CustomerUser | AdminUser;
Cette fois, la syntaxe nous permet toujours de créer des utilisateurs valides:
const johnDoe: User = {
userType: "Admin",
login: "JohnDoe",
accessRights: ["database", "monitoring"],
};
const customer: User = {
userType: "Customer",
login: "JaneDoe",
};
const guest: User = {
userType: "Guest",
};
Mais interdit la création d'utilisateurs qui n'ont pas de sens :
/**
* Type '{ userType: "Customer"; }' is not assignable to type 'User'.
* Property 'login' is missing in type '{ userType: "Customer"; }'
* but required in type 'CustomerUser'.
*/
const customerWithoutLogin: User = {
userType: "Customer",
};
/**
* Type '{ userType: "Guest"; accessRights: string[]; }'is not assignable to type 'User'.
* Object literal may only specify known properties,
* and 'accessRights' does not exist in type 'GuestUser'.
*/
const guestWithAccessRights: User = {
userType: "Guest",
accessRights: ["fs"],
};
L'accès aux propriétés est également bien plus predictible :
johnDoe.login.toUpperCase(); // OK
customer.login.toUpperCase(); // OK
guest.login.toUpperCase(); // Property 'login' does not exist on type 'GuestUser'.
L'inférence de fait également des merveilles 유형:
// login is defined because GuestUser is excluded (Type guard)
const displayLogin = (user: User) =>
user.userType === "Guest" ? "Guest" : user.login;
ProTip: Si l'inférence ne fonctionne pas, pensez à définir un champ qui va aider Typescript à determiner le bon type(
userType
dans notre exemple). Vous pouvez aussi 식별자 le type manuellement avec le mot-clé is
.const isAdmin = (user: User): user is AdminUser =>
(user as AdminUser).accessRights !== undefined;
const users: User[] = [johnDoe, customer, guest];
users.filter(isAdmin).forEach((admin) => console.log(admin.accessRights));
결론
Vous pouvez maintenant être plus précis sur le typage des données. Rien de révolutionnaire ici mais rappelez-vous que le typage est un bon moyen d'augmenter la prédictibilité de votre code.
알러 플러스 로스
Je vous Invitation à aller voir mon article sur le pattern matching en JS (qui arrive bientôt) qui complète assez bien les unions disjointes que l'on vient de voir. En combinant les deux, vous pouvez notamment faire une sorte de polymorphisme sans héritage et sans classe.
const redirectToHomePage = () => (location.href = "/");
const redirectToAccountPage = () => (location.href = "/account");
const redirectToAdminDashboardPage = () => (location.href = "/admin/dashboard");
export const redirectToUserPage = (user: User) =>
({
Guest: () => redirectToHomePage(),
Customer: () => redirectToAccountPage(),
Admin: () => redirectToAdminDashboardPage(),
}[user.userType]());
Un article également disponible sur :
Reference
이 문제에 관하여(Exploiter les Discriminated Union en Typescript), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/younup/exploiter-les-discriminated-union-en-typescript-8f0텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)