[{"data":1,"prerenderedAt":2140},["ShallowReactive",2],{"post-fr-typescript-interfaces":3},{"id":4,"title":5,"body":6,"date":2125,"description":2126,"excerpt":2127,"extension":2128,"meta":2129,"navigation":359,"path":2130,"readTime":116,"seo":2131,"slug":2132,"stem":2133,"tags":2134,"__hash__":2139},"fr_blog/fr/blog/typescript-interfaces.md","TypeScript : interfaces hiérarchiques sur une vraie API",{"type":7,"value":8,"toc":2115},"minimark",[9,14,23,28,31,335,339,342,522,537,541,822,826,829,1063,1067,1070,1529,1532,1626,1641,1645,1648,1809,1823,1827,1830,2072,2076,2079,2108,2111],[10,11,13],"h1",{"id":12},"typescript-strict-en-pratique-interfaces-hiérarchiques-sur-une-vraie-api","TypeScript strict en pratique : interfaces hiérarchiques sur une vraie API",[15,16,17,18,22],"p",{},"La plupart des tutoriels TypeScript montrent des exemples triviaux : ",[19,20,21],"code",{},"interface User { name: string; age: number }",". En production, la réalité est plus complexe. Les APIs externes renvoient des structures imbriquées, des champs optionnels selon le contexte, des unions de types selon l'état. Voici comment structurer tout ça proprement, à partir d'un cas réel.",[24,25,27],"h2",{"id":26},"le-point-de-départ-une-api-avec-une-structure-hiérarchique","Le point de départ : une API avec une structure hiérarchique",[15,29,30],{},"Prenons une API de gestion de certificats qui renvoie des structures de ce type :",[32,33,38],"pre",{"className":34,"code":35,"language":36,"meta":37,"style":37},"language-json shiki shiki-themes github-dark github-light","{\n  \"account\": {\n    \"id\": \"ACC-001\",\n    \"name\": \"EDF Production\",\n    \"type\": \"PRODUCER\"\n  },\n  \"certificates\": [\n    {\n      \"id\": \"GO-2024-001\",\n      \"volume\": 1500.5,\n      \"unit\": \"MWh\",\n      \"period\": { \"from\": \"2024-01-01\", \"to\": \"2024-01-31\" },\n      \"status\": \"ACTIVE\" | \"CANCELLED\" | \"TRANSFERRED\",\n      \"metadata\": {\n        \"technology\": \"WIND\",\n        \"country\": \"FR\",\n        \"installation_id\": \"INS-042\"\n      }\n    }\n  ],\n  \"pagination\": {\n    \"page\": 1,\n    \"per_page\": 50,\n    \"total\": 312\n  }\n}\n","json","",[19,39,40,49,59,75,88,99,105,114,120,133,146,159,190,215,223,236,249,260,266,272,278,286,299,312,323,329],{"__ignoreMap":37},[41,42,45],"span",{"class":43,"line":44},"line",1,[41,46,48],{"class":47},"sQ3_J","{\n",[41,50,52,56],{"class":43,"line":51},2,[41,53,55],{"class":54},"s0DvM","  \"account\"",[41,57,58],{"class":47},": {\n",[41,60,62,65,68,72],{"class":43,"line":61},3,[41,63,64],{"class":54},"    \"id\"",[41,66,67],{"class":47},": ",[41,69,71],{"class":70},"sg6BJ","\"ACC-001\"",[41,73,74],{"class":47},",\n",[41,76,78,81,83,86],{"class":43,"line":77},4,[41,79,80],{"class":54},"    \"name\"",[41,82,67],{"class":47},[41,84,85],{"class":70},"\"EDF Production\"",[41,87,74],{"class":47},[41,89,91,94,96],{"class":43,"line":90},5,[41,92,93],{"class":54},"    \"type\"",[41,95,67],{"class":47},[41,97,98],{"class":70},"\"PRODUCER\"\n",[41,100,102],{"class":43,"line":101},6,[41,103,104],{"class":47},"  },\n",[41,106,108,111],{"class":43,"line":107},7,[41,109,110],{"class":54},"  \"certificates\"",[41,112,113],{"class":47},": [\n",[41,115,117],{"class":43,"line":116},8,[41,118,119],{"class":47},"    {\n",[41,121,123,126,128,131],{"class":43,"line":122},9,[41,124,125],{"class":54},"      \"id\"",[41,127,67],{"class":47},[41,129,130],{"class":70},"\"GO-2024-001\"",[41,132,74],{"class":47},[41,134,136,139,141,144],{"class":43,"line":135},10,[41,137,138],{"class":54},"      \"volume\"",[41,140,67],{"class":47},[41,142,143],{"class":54},"1500.5",[41,145,74],{"class":47},[41,147,149,152,154,157],{"class":43,"line":148},11,[41,150,151],{"class":54},"      \"unit\"",[41,153,67],{"class":47},[41,155,156],{"class":70},"\"MWh\"",[41,158,74],{"class":47},[41,160,162,165,168,171,173,176,179,182,184,187],{"class":43,"line":161},12,[41,163,164],{"class":54},"      \"period\"",[41,166,167],{"class":47},": { ",[41,169,170],{"class":54},"\"from\"",[41,172,67],{"class":47},[41,174,175],{"class":70},"\"2024-01-01\"",[41,177,178],{"class":47},", ",[41,180,181],{"class":54},"\"to\"",[41,183,67],{"class":47},[41,185,186],{"class":70},"\"2024-01-31\"",[41,188,189],{"class":47}," },\n",[41,191,193,196,198,201,205,208,210,213],{"class":43,"line":192},13,[41,194,195],{"class":54},"      \"status\"",[41,197,67],{"class":47},[41,199,200],{"class":70},"\"ACTIVE\"",[41,202,204],{"class":203},"seUEM"," |",[41,206,207],{"class":70}," \"CANCELLED\"",[41,209,204],{"class":203},[41,211,212],{"class":70}," \"TRANSFERRED\"",[41,214,74],{"class":47},[41,216,218,221],{"class":43,"line":217},14,[41,219,220],{"class":54},"      \"metadata\"",[41,222,58],{"class":47},[41,224,226,229,231,234],{"class":43,"line":225},15,[41,227,228],{"class":54},"        \"technology\"",[41,230,67],{"class":47},[41,232,233],{"class":70},"\"WIND\"",[41,235,74],{"class":47},[41,237,239,242,244,247],{"class":43,"line":238},16,[41,240,241],{"class":54},"        \"country\"",[41,243,67],{"class":47},[41,245,246],{"class":70},"\"FR\"",[41,248,74],{"class":47},[41,250,252,255,257],{"class":43,"line":251},17,[41,253,254],{"class":54},"        \"installation_id\"",[41,256,67],{"class":47},[41,258,259],{"class":70},"\"INS-042\"\n",[41,261,263],{"class":43,"line":262},18,[41,264,265],{"class":47},"      }\n",[41,267,269],{"class":43,"line":268},19,[41,270,271],{"class":47},"    }\n",[41,273,275],{"class":43,"line":274},20,[41,276,277],{"class":47},"  ],\n",[41,279,281,284],{"class":43,"line":280},21,[41,282,283],{"class":54},"  \"pagination\"",[41,285,58],{"class":47},[41,287,289,292,294,297],{"class":43,"line":288},22,[41,290,291],{"class":54},"    \"page\"",[41,293,67],{"class":47},[41,295,296],{"class":54},"1",[41,298,74],{"class":47},[41,300,302,305,307,310],{"class":43,"line":301},23,[41,303,304],{"class":54},"    \"per_page\"",[41,306,67],{"class":47},[41,308,309],{"class":54},"50",[41,311,74],{"class":47},[41,313,315,318,320],{"class":43,"line":314},24,[41,316,317],{"class":54},"    \"total\"",[41,319,67],{"class":47},[41,321,322],{"class":54},"312\n",[41,324,326],{"class":43,"line":325},25,[41,327,328],{"class":47},"  }\n",[41,330,332],{"class":43,"line":331},26,[41,333,334],{"class":47},"}\n",[24,336,338],{"id":337},"modéliser-les-types-de-base","Modéliser les types de base",[15,340,341],{},"On commence par les types atomiques — unions littérales et enums :",[32,343,347],{"className":344,"code":345,"language":346,"meta":37,"style":37},"language-typescript shiki shiki-themes github-dark github-light","// types/api.ts\n\nexport type AccountType = \"PRODUCER\" | \"TRADER\" | \"CONSUMER\"\nexport type CertificateStatus = \"ACTIVE\" | \"CANCELLED\" | \"TRANSFERRED\"\nexport type EnergyTechnology = \"WIND\" | \"SOLAR\" | \"HYDRO\" | \"BIOMASS\"\nexport type CountryCode = \"FR\" | \"DE\" | \"ES\" | \"IT\" | \"BE\"\n\nexport interface DateRange {\n  from: string // ISO 8601\n  to: string\n}\n","typescript",[19,348,349,355,361,390,413,442,476,480,493,508,518],{"__ignoreMap":37},[41,350,351],{"class":43,"line":44},[41,352,354],{"class":353},"sryI4","// types/api.ts\n",[41,356,357],{"class":43,"line":51},[41,358,360],{"emptyLinePlaceholder":359},true,"\n",[41,362,363,367,370,374,377,380,382,385,387],{"class":43,"line":61},[41,364,366],{"class":365},"scx8i","export",[41,368,369],{"class":365}," type",[41,371,373],{"class":372},"s-Z4r"," AccountType",[41,375,376],{"class":365}," =",[41,378,379],{"class":70}," \"PRODUCER\"",[41,381,204],{"class":365},[41,383,384],{"class":70}," \"TRADER\"",[41,386,204],{"class":365},[41,388,389],{"class":70}," \"CONSUMER\"\n",[41,391,392,394,396,399,401,404,406,408,410],{"class":43,"line":77},[41,393,366],{"class":365},[41,395,369],{"class":365},[41,397,398],{"class":372}," CertificateStatus",[41,400,376],{"class":365},[41,402,403],{"class":70}," \"ACTIVE\"",[41,405,204],{"class":365},[41,407,207],{"class":70},[41,409,204],{"class":365},[41,411,412],{"class":70}," \"TRANSFERRED\"\n",[41,414,415,417,419,422,424,427,429,432,434,437,439],{"class":43,"line":90},[41,416,366],{"class":365},[41,418,369],{"class":365},[41,420,421],{"class":372}," EnergyTechnology",[41,423,376],{"class":365},[41,425,426],{"class":70}," \"WIND\"",[41,428,204],{"class":365},[41,430,431],{"class":70}," \"SOLAR\"",[41,433,204],{"class":365},[41,435,436],{"class":70}," \"HYDRO\"",[41,438,204],{"class":365},[41,440,441],{"class":70}," \"BIOMASS\"\n",[41,443,444,446,448,451,453,456,458,461,463,466,468,471,473],{"class":43,"line":101},[41,445,366],{"class":365},[41,447,369],{"class":365},[41,449,450],{"class":372}," CountryCode",[41,452,376],{"class":365},[41,454,455],{"class":70}," \"FR\"",[41,457,204],{"class":365},[41,459,460],{"class":70}," \"DE\"",[41,462,204],{"class":365},[41,464,465],{"class":70}," \"ES\"",[41,467,204],{"class":365},[41,469,470],{"class":70}," \"IT\"",[41,472,204],{"class":365},[41,474,475],{"class":70}," \"BE\"\n",[41,477,478],{"class":43,"line":107},[41,479,360],{"emptyLinePlaceholder":359},[41,481,482,484,487,490],{"class":43,"line":116},[41,483,366],{"class":365},[41,485,486],{"class":365}," interface",[41,488,489],{"class":372}," DateRange",[41,491,492],{"class":47}," {\n",[41,494,495,499,502,505],{"class":43,"line":122},[41,496,498],{"class":497},"sFbx2","  from",[41,500,501],{"class":365},":",[41,503,504],{"class":54}," string",[41,506,507],{"class":353}," // ISO 8601\n",[41,509,510,513,515],{"class":43,"line":135},[41,511,512],{"class":497},"  to",[41,514,501],{"class":365},[41,516,517],{"class":54}," string\n",[41,519,520],{"class":43,"line":148},[41,521,334],{"class":47},[15,523,524,525,528,529,532,533,536],{},"Les unions littérales plutôt que des ",[19,526,527],{},"string"," nus : TypeScript t'alertera immédiatement si tu passes ",[19,530,531],{},"'WIND_OFFSHORE'"," là où ",[19,534,535],{},"EnergyTechnology"," est attendu.",[24,538,540],{"id":539},"interfaces-hiérarchiques","Interfaces hiérarchiques",[32,542,544],{"className":344,"code":543,"language":346,"meta":37,"style":37},"export interface Account {\n  id: string\n  name: string\n  type: AccountType\n}\n\nexport interface CertificateMetadata {\n  technology: EnergyTechnology\n  country: CountryCode\n  installation_id: string\n}\n\nexport interface Certificate {\n  id: string\n  volume: number\n  unit: \"MWh\" | \"kWh\"\n  period: DateRange\n  status: CertificateStatus\n  metadata: CertificateMetadata\n}\n\nexport interface Pagination {\n  page: number\n  per_page: number\n  total: number\n}\n\nexport interface CertificatesResponse {\n  account: Account\n  certificates: Certificate[]\n  pagination: Pagination\n}\n",[19,545,546,557,566,575,585,589,593,604,614,624,633,637,641,652,660,670,685,695,705,715,719,723,734,743,752,761,765,770,782,793,806,817],{"__ignoreMap":37},[41,547,548,550,552,555],{"class":43,"line":44},[41,549,366],{"class":365},[41,551,486],{"class":365},[41,553,554],{"class":372}," Account",[41,556,492],{"class":47},[41,558,559,562,564],{"class":43,"line":51},[41,560,561],{"class":497},"  id",[41,563,501],{"class":365},[41,565,517],{"class":54},[41,567,568,571,573],{"class":43,"line":61},[41,569,570],{"class":497},"  name",[41,572,501],{"class":365},[41,574,517],{"class":54},[41,576,577,580,582],{"class":43,"line":77},[41,578,579],{"class":497},"  type",[41,581,501],{"class":365},[41,583,584],{"class":372}," AccountType\n",[41,586,587],{"class":43,"line":90},[41,588,334],{"class":47},[41,590,591],{"class":43,"line":101},[41,592,360],{"emptyLinePlaceholder":359},[41,594,595,597,599,602],{"class":43,"line":107},[41,596,366],{"class":365},[41,598,486],{"class":365},[41,600,601],{"class":372}," CertificateMetadata",[41,603,492],{"class":47},[41,605,606,609,611],{"class":43,"line":116},[41,607,608],{"class":497},"  technology",[41,610,501],{"class":365},[41,612,613],{"class":372}," EnergyTechnology\n",[41,615,616,619,621],{"class":43,"line":122},[41,617,618],{"class":497},"  country",[41,620,501],{"class":365},[41,622,623],{"class":372}," CountryCode\n",[41,625,626,629,631],{"class":43,"line":135},[41,627,628],{"class":497},"  installation_id",[41,630,501],{"class":365},[41,632,517],{"class":54},[41,634,635],{"class":43,"line":148},[41,636,334],{"class":47},[41,638,639],{"class":43,"line":161},[41,640,360],{"emptyLinePlaceholder":359},[41,642,643,645,647,650],{"class":43,"line":192},[41,644,366],{"class":365},[41,646,486],{"class":365},[41,648,649],{"class":372}," Certificate",[41,651,492],{"class":47},[41,653,654,656,658],{"class":43,"line":217},[41,655,561],{"class":497},[41,657,501],{"class":365},[41,659,517],{"class":54},[41,661,662,665,667],{"class":43,"line":225},[41,663,664],{"class":497},"  volume",[41,666,501],{"class":365},[41,668,669],{"class":54}," number\n",[41,671,672,675,677,680,682],{"class":43,"line":238},[41,673,674],{"class":497},"  unit",[41,676,501],{"class":365},[41,678,679],{"class":70}," \"MWh\"",[41,681,204],{"class":365},[41,683,684],{"class":70}," \"kWh\"\n",[41,686,687,690,692],{"class":43,"line":251},[41,688,689],{"class":497},"  period",[41,691,501],{"class":365},[41,693,694],{"class":372}," DateRange\n",[41,696,697,700,702],{"class":43,"line":262},[41,698,699],{"class":497},"  status",[41,701,501],{"class":365},[41,703,704],{"class":372}," CertificateStatus\n",[41,706,707,710,712],{"class":43,"line":268},[41,708,709],{"class":497},"  metadata",[41,711,501],{"class":365},[41,713,714],{"class":372}," CertificateMetadata\n",[41,716,717],{"class":43,"line":274},[41,718,334],{"class":47},[41,720,721],{"class":43,"line":280},[41,722,360],{"emptyLinePlaceholder":359},[41,724,725,727,729,732],{"class":43,"line":288},[41,726,366],{"class":365},[41,728,486],{"class":365},[41,730,731],{"class":372}," Pagination",[41,733,492],{"class":47},[41,735,736,739,741],{"class":43,"line":301},[41,737,738],{"class":497},"  page",[41,740,501],{"class":365},[41,742,669],{"class":54},[41,744,745,748,750],{"class":43,"line":314},[41,746,747],{"class":497},"  per_page",[41,749,501],{"class":365},[41,751,669],{"class":54},[41,753,754,757,759],{"class":43,"line":325},[41,755,756],{"class":497},"  total",[41,758,501],{"class":365},[41,760,669],{"class":54},[41,762,763],{"class":43,"line":331},[41,764,334],{"class":47},[41,766,768],{"class":43,"line":767},27,[41,769,360],{"emptyLinePlaceholder":359},[41,771,773,775,777,780],{"class":43,"line":772},28,[41,774,366],{"class":365},[41,776,486],{"class":365},[41,778,779],{"class":372}," CertificatesResponse",[41,781,492],{"class":47},[41,783,785,788,790],{"class":43,"line":784},29,[41,786,787],{"class":497},"  account",[41,789,501],{"class":365},[41,791,792],{"class":372}," Account\n",[41,794,796,799,801,803],{"class":43,"line":795},30,[41,797,798],{"class":497},"  certificates",[41,800,501],{"class":365},[41,802,649],{"class":372},[41,804,805],{"class":47},"[]\n",[41,807,809,812,814],{"class":43,"line":808},31,[41,810,811],{"class":497},"  pagination",[41,813,501],{"class":365},[41,815,816],{"class":372}," Pagination\n",[41,818,820],{"class":43,"line":819},32,[41,821,334],{"class":47},[24,823,825],{"id":824},"types-utilitaires-pour-les-cas-dusage","Types utilitaires pour les cas d'usage",[15,827,828],{},"L'API renvoie toujours la structure complète, mais l'application n'en a pas toujours besoin en entier. Les types utilitaires TypeScript permettent de dériver des types adaptés à chaque contexte sans dupliquer les interfaces :",[32,830,832],{"className":344,"code":831,"language":346,"meta":37,"style":37},"// Pour l'affichage dans une liste — on n'a pas besoin des métadonnées\nexport type CertificateSummary = Pick\u003C\n  Certificate,\n  \"id\" | \"volume\" | \"unit\" | \"status\" | \"period\"\n>\n\n// Pour la mise à jour — seul le statut est modifiable\nexport type CertificateUpdate = Pick\u003CCertificate, \"id\"> & {\n  status: CertificateStatus\n}\n\n// Pour les filtres de recherche — tous les champs sont optionnels\nexport type CertificateFilters = Partial\u003CPick\u003CCertificate, \"status\">> & {\n  period?: Partial\u003CDateRange>\n  technology?: EnergyTechnology\n  country?: CountryCode\n}\n\n// Pour les formulaires — on exclut les champs générés par l'API\nexport type CertificateForm = Omit\u003CCertificate, \"id\" | \"status\">\n",[19,833,834,839,856,863,888,893,897,902,934,942,946,950,955,990,1006,1014,1022,1026,1030,1035],{"__ignoreMap":37},[41,835,836],{"class":43,"line":44},[41,837,838],{"class":353},"// Pour l'affichage dans une liste — on n'a pas besoin des métadonnées\n",[41,840,841,843,845,848,850,853],{"class":43,"line":51},[41,842,366],{"class":365},[41,844,369],{"class":365},[41,846,847],{"class":372}," CertificateSummary",[41,849,376],{"class":365},[41,851,852],{"class":372}," Pick",[41,854,855],{"class":47},"\u003C\n",[41,857,858,861],{"class":43,"line":61},[41,859,860],{"class":372},"  Certificate",[41,862,74],{"class":47},[41,864,865,868,870,873,875,878,880,883,885],{"class":43,"line":77},[41,866,867],{"class":70},"  \"id\"",[41,869,204],{"class":365},[41,871,872],{"class":70}," \"volume\"",[41,874,204],{"class":365},[41,876,877],{"class":70}," \"unit\"",[41,879,204],{"class":365},[41,881,882],{"class":70}," \"status\"",[41,884,204],{"class":365},[41,886,887],{"class":70}," \"period\"\n",[41,889,890],{"class":43,"line":90},[41,891,892],{"class":47},">\n",[41,894,895],{"class":43,"line":101},[41,896,360],{"emptyLinePlaceholder":359},[41,898,899],{"class":43,"line":107},[41,900,901],{"class":353},"// Pour la mise à jour — seul le statut est modifiable\n",[41,903,904,906,908,911,913,915,918,921,923,926,929,932],{"class":43,"line":116},[41,905,366],{"class":365},[41,907,369],{"class":365},[41,909,910],{"class":372}," CertificateUpdate",[41,912,376],{"class":365},[41,914,852],{"class":372},[41,916,917],{"class":47},"\u003C",[41,919,920],{"class":372},"Certificate",[41,922,178],{"class":47},[41,924,925],{"class":70},"\"id\"",[41,927,928],{"class":47},"> ",[41,930,931],{"class":365},"&",[41,933,492],{"class":47},[41,935,936,938,940],{"class":43,"line":122},[41,937,699],{"class":497},[41,939,501],{"class":365},[41,941,704],{"class":372},[41,943,944],{"class":43,"line":135},[41,945,334],{"class":47},[41,947,948],{"class":43,"line":148},[41,949,360],{"emptyLinePlaceholder":359},[41,951,952],{"class":43,"line":161},[41,953,954],{"class":353},"// Pour les filtres de recherche — tous les champs sont optionnels\n",[41,956,957,959,961,964,966,969,971,974,976,978,980,983,986,988],{"class":43,"line":192},[41,958,366],{"class":365},[41,960,369],{"class":365},[41,962,963],{"class":372}," CertificateFilters",[41,965,376],{"class":365},[41,967,968],{"class":372}," Partial",[41,970,917],{"class":47},[41,972,973],{"class":372},"Pick",[41,975,917],{"class":47},[41,977,920],{"class":372},[41,979,178],{"class":47},[41,981,982],{"class":70},"\"status\"",[41,984,985],{"class":47},">> ",[41,987,931],{"class":365},[41,989,492],{"class":47},[41,991,992,994,997,999,1001,1004],{"class":43,"line":217},[41,993,689],{"class":497},[41,995,996],{"class":365},"?:",[41,998,968],{"class":372},[41,1000,917],{"class":47},[41,1002,1003],{"class":372},"DateRange",[41,1005,892],{"class":47},[41,1007,1008,1010,1012],{"class":43,"line":225},[41,1009,608],{"class":497},[41,1011,996],{"class":365},[41,1013,613],{"class":372},[41,1015,1016,1018,1020],{"class":43,"line":238},[41,1017,618],{"class":497},[41,1019,996],{"class":365},[41,1021,623],{"class":372},[41,1023,1024],{"class":43,"line":251},[41,1025,334],{"class":47},[41,1027,1028],{"class":43,"line":262},[41,1029,360],{"emptyLinePlaceholder":359},[41,1031,1032],{"class":43,"line":268},[41,1033,1034],{"class":353},"// Pour les formulaires — on exclut les champs générés par l'API\n",[41,1036,1037,1039,1041,1044,1046,1049,1051,1053,1055,1057,1059,1061],{"class":43,"line":274},[41,1038,366],{"class":365},[41,1040,369],{"class":365},[41,1042,1043],{"class":372}," CertificateForm",[41,1045,376],{"class":365},[41,1047,1048],{"class":372}," Omit",[41,1050,917],{"class":47},[41,1052,920],{"class":372},[41,1054,178],{"class":47},[41,1056,925],{"class":70},[41,1058,204],{"class":365},[41,1060,882],{"class":70},[41,1062,892],{"class":47},[24,1064,1066],{"id":1065},"génériques-sur-les-composables-vue","Génériques sur les composables Vue",[15,1068,1069],{},"Un composable générique pour les appels API évite de réécrire la même logique de chargement/erreur pour chaque endpoint :",[32,1071,1073],{"className":344,"code":1072,"language":346,"meta":37,"style":37},"// composables/useApiQuery.ts\nimport { ref, Ref } from \"vue\"\n\ninterface ApiQueryState\u003CT> {\n  data: Ref\u003CT | null>\n  loading: Ref\u003Cboolean>\n  error: Ref\u003Cstring | null>\n  execute: () => Promise\u003Cvoid>\n}\n\nexport function useApiQuery\u003CT>(\n  fetcher: () => Promise\u003CT>,\n  options?: { immediate?: boolean },\n): ApiQueryState\u003CT> {\n  const data = ref\u003CT | null>(null) as Ref\u003CT | null>\n  const loading = ref(false)\n  const error = ref\u003Cstring | null>(null)\n\n  const execute = async () => {\n    loading.value = true\n    error.value = null\n    try {\n      data.value = await fetcher()\n    } catch (e) {\n      error.value = e instanceof Error ? e.message : \"Erreur inconnue\"\n    } finally {\n      loading.value = false\n    }\n  }\n\n  if (options?.immediate) execute()\n\n  return { data, loading, error, execute }\n}\n",[19,1074,1075,1080,1094,1098,1114,1135,1151,1170,1193,1197,1201,1218,1238,1258,1273,1318,1338,1363,1367,1385,1396,1406,1413,1429,1440,1467,1476,1486,1490,1494,1498,1511,1515,1524],{"__ignoreMap":37},[41,1076,1077],{"class":43,"line":44},[41,1078,1079],{"class":353},"// composables/useApiQuery.ts\n",[41,1081,1082,1085,1088,1091],{"class":43,"line":51},[41,1083,1084],{"class":365},"import",[41,1086,1087],{"class":47}," { ref, Ref } ",[41,1089,1090],{"class":365},"from",[41,1092,1093],{"class":70}," \"vue\"\n",[41,1095,1096],{"class":43,"line":61},[41,1097,360],{"emptyLinePlaceholder":359},[41,1099,1100,1103,1106,1108,1111],{"class":43,"line":77},[41,1101,1102],{"class":365},"interface",[41,1104,1105],{"class":372}," ApiQueryState",[41,1107,917],{"class":47},[41,1109,1110],{"class":372},"T",[41,1112,1113],{"class":47},"> {\n",[41,1115,1116,1119,1121,1124,1126,1128,1130,1133],{"class":43,"line":90},[41,1117,1118],{"class":497},"  data",[41,1120,501],{"class":365},[41,1122,1123],{"class":372}," Ref",[41,1125,917],{"class":47},[41,1127,1110],{"class":372},[41,1129,204],{"class":365},[41,1131,1132],{"class":54}," null",[41,1134,892],{"class":47},[41,1136,1137,1140,1142,1144,1146,1149],{"class":43,"line":101},[41,1138,1139],{"class":497},"  loading",[41,1141,501],{"class":365},[41,1143,1123],{"class":372},[41,1145,917],{"class":47},[41,1147,1148],{"class":54},"boolean",[41,1150,892],{"class":47},[41,1152,1153,1156,1158,1160,1162,1164,1166,1168],{"class":43,"line":107},[41,1154,1155],{"class":497},"  error",[41,1157,501],{"class":365},[41,1159,1123],{"class":372},[41,1161,917],{"class":47},[41,1163,527],{"class":54},[41,1165,204],{"class":365},[41,1167,1132],{"class":54},[41,1169,892],{"class":47},[41,1171,1172,1175,1177,1180,1183,1186,1188,1191],{"class":43,"line":116},[41,1173,1174],{"class":372},"  execute",[41,1176,501],{"class":365},[41,1178,1179],{"class":47}," () ",[41,1181,1182],{"class":365},"=>",[41,1184,1185],{"class":372}," Promise",[41,1187,917],{"class":47},[41,1189,1190],{"class":54},"void",[41,1192,892],{"class":47},[41,1194,1195],{"class":43,"line":122},[41,1196,334],{"class":47},[41,1198,1199],{"class":43,"line":135},[41,1200,360],{"emptyLinePlaceholder":359},[41,1202,1203,1205,1208,1211,1213,1215],{"class":43,"line":148},[41,1204,366],{"class":365},[41,1206,1207],{"class":365}," function",[41,1209,1210],{"class":372}," useApiQuery",[41,1212,917],{"class":47},[41,1214,1110],{"class":372},[41,1216,1217],{"class":47},">(\n",[41,1219,1220,1223,1225,1227,1229,1231,1233,1235],{"class":43,"line":161},[41,1221,1222],{"class":372},"  fetcher",[41,1224,501],{"class":365},[41,1226,1179],{"class":47},[41,1228,1182],{"class":365},[41,1230,1185],{"class":372},[41,1232,917],{"class":47},[41,1234,1110],{"class":372},[41,1236,1237],{"class":47},">,\n",[41,1239,1240,1243,1245,1248,1251,1253,1256],{"class":43,"line":192},[41,1241,1242],{"class":497},"  options",[41,1244,996],{"class":365},[41,1246,1247],{"class":47}," { ",[41,1249,1250],{"class":497},"immediate",[41,1252,996],{"class":365},[41,1254,1255],{"class":54}," boolean",[41,1257,189],{"class":47},[41,1259,1260,1263,1265,1267,1269,1271],{"class":43,"line":217},[41,1261,1262],{"class":47},")",[41,1264,501],{"class":365},[41,1266,1105],{"class":372},[41,1268,917],{"class":47},[41,1270,1110],{"class":372},[41,1272,1113],{"class":47},[41,1274,1275,1278,1281,1283,1286,1288,1290,1292,1294,1297,1300,1303,1306,1308,1310,1312,1314,1316],{"class":43,"line":225},[41,1276,1277],{"class":365},"  const",[41,1279,1280],{"class":54}," data",[41,1282,376],{"class":365},[41,1284,1285],{"class":372}," ref",[41,1287,917],{"class":47},[41,1289,1110],{"class":372},[41,1291,204],{"class":365},[41,1293,1132],{"class":54},[41,1295,1296],{"class":47},">(",[41,1298,1299],{"class":54},"null",[41,1301,1302],{"class":47},") ",[41,1304,1305],{"class":365},"as",[41,1307,1123],{"class":372},[41,1309,917],{"class":47},[41,1311,1110],{"class":372},[41,1313,204],{"class":365},[41,1315,1132],{"class":54},[41,1317,892],{"class":47},[41,1319,1320,1322,1325,1327,1329,1332,1335],{"class":43,"line":238},[41,1321,1277],{"class":365},[41,1323,1324],{"class":54}," loading",[41,1326,376],{"class":365},[41,1328,1285],{"class":372},[41,1330,1331],{"class":47},"(",[41,1333,1334],{"class":54},"false",[41,1336,1337],{"class":47},")\n",[41,1339,1340,1342,1345,1347,1349,1351,1353,1355,1357,1359,1361],{"class":43,"line":251},[41,1341,1277],{"class":365},[41,1343,1344],{"class":54}," error",[41,1346,376],{"class":365},[41,1348,1285],{"class":372},[41,1350,917],{"class":47},[41,1352,527],{"class":54},[41,1354,204],{"class":365},[41,1356,1132],{"class":54},[41,1358,1296],{"class":47},[41,1360,1299],{"class":54},[41,1362,1337],{"class":47},[41,1364,1365],{"class":43,"line":262},[41,1366,360],{"emptyLinePlaceholder":359},[41,1368,1369,1371,1374,1376,1379,1381,1383],{"class":43,"line":268},[41,1370,1277],{"class":365},[41,1372,1373],{"class":372}," execute",[41,1375,376],{"class":365},[41,1377,1378],{"class":365}," async",[41,1380,1179],{"class":47},[41,1382,1182],{"class":365},[41,1384,492],{"class":47},[41,1386,1387,1390,1393],{"class":43,"line":274},[41,1388,1389],{"class":47},"    loading.value ",[41,1391,1392],{"class":365},"=",[41,1394,1395],{"class":54}," true\n",[41,1397,1398,1401,1403],{"class":43,"line":280},[41,1399,1400],{"class":47},"    error.value ",[41,1402,1392],{"class":365},[41,1404,1405],{"class":54}," null\n",[41,1407,1408,1411],{"class":43,"line":288},[41,1409,1410],{"class":365},"    try",[41,1412,492],{"class":47},[41,1414,1415,1418,1420,1423,1426],{"class":43,"line":301},[41,1416,1417],{"class":47},"      data.value ",[41,1419,1392],{"class":365},[41,1421,1422],{"class":365}," await",[41,1424,1425],{"class":372}," fetcher",[41,1427,1428],{"class":47},"()\n",[41,1430,1431,1434,1437],{"class":43,"line":314},[41,1432,1433],{"class":47},"    } ",[41,1435,1436],{"class":365},"catch",[41,1438,1439],{"class":47}," (e) {\n",[41,1441,1442,1445,1447,1450,1453,1456,1459,1462,1464],{"class":43,"line":325},[41,1443,1444],{"class":47},"      error.value ",[41,1446,1392],{"class":365},[41,1448,1449],{"class":47}," e ",[41,1451,1452],{"class":365},"instanceof",[41,1454,1455],{"class":372}," Error",[41,1457,1458],{"class":365}," ?",[41,1460,1461],{"class":47}," e.message ",[41,1463,501],{"class":365},[41,1465,1466],{"class":70}," \"Erreur inconnue\"\n",[41,1468,1469,1471,1474],{"class":43,"line":331},[41,1470,1433],{"class":47},[41,1472,1473],{"class":365},"finally",[41,1475,492],{"class":47},[41,1477,1478,1481,1483],{"class":43,"line":767},[41,1479,1480],{"class":47},"      loading.value ",[41,1482,1392],{"class":365},[41,1484,1485],{"class":54}," false\n",[41,1487,1488],{"class":43,"line":772},[41,1489,271],{"class":47},[41,1491,1492],{"class":43,"line":784},[41,1493,328],{"class":47},[41,1495,1496],{"class":43,"line":795},[41,1497,360],{"emptyLinePlaceholder":359},[41,1499,1500,1503,1506,1509],{"class":43,"line":808},[41,1501,1502],{"class":365},"  if",[41,1504,1505],{"class":47}," (options?.immediate) ",[41,1507,1508],{"class":372},"execute",[41,1510,1428],{"class":47},[41,1512,1513],{"class":43,"line":819},[41,1514,360],{"emptyLinePlaceholder":359},[41,1516,1518,1521],{"class":43,"line":1517},33,[41,1519,1520],{"class":365},"  return",[41,1522,1523],{"class":47}," { data, loading, error, execute }\n",[41,1525,1527],{"class":43,"line":1526},34,[41,1528,334],{"class":47},[15,1530,1531],{},"Usage dans un composant Vue :",[32,1533,1535],{"className":344,"code":1534,"language":346,"meta":37,"style":37},"// Dans un composant\nconst {\n  data: certificates,\n  loading,\n  error,\n  execute,\n} = useApiQuery\u003CCertificatesResponse>(\n  () => $fetch(\"/api/certificates\", { params: filters.value }),\n  { immediate: true },\n)\n",[19,1536,1537,1542,1549,1560,1566,1572,1578,1594,1612,1622],{"__ignoreMap":37},[41,1538,1539],{"class":43,"line":44},[41,1540,1541],{"class":353},"// Dans un composant\n",[41,1543,1544,1547],{"class":43,"line":51},[41,1545,1546],{"class":365},"const",[41,1548,492],{"class":47},[41,1550,1551,1553,1555,1558],{"class":43,"line":61},[41,1552,1118],{"class":497},[41,1554,67],{"class":47},[41,1556,1557],{"class":54},"certificates",[41,1559,74],{"class":47},[41,1561,1562,1564],{"class":43,"line":77},[41,1563,1139],{"class":54},[41,1565,74],{"class":47},[41,1567,1568,1570],{"class":43,"line":90},[41,1569,1155],{"class":54},[41,1571,74],{"class":47},[41,1573,1574,1576],{"class":43,"line":101},[41,1575,1174],{"class":54},[41,1577,74],{"class":47},[41,1579,1580,1583,1585,1587,1589,1592],{"class":43,"line":107},[41,1581,1582],{"class":47},"} ",[41,1584,1392],{"class":365},[41,1586,1210],{"class":372},[41,1588,917],{"class":47},[41,1590,1591],{"class":372},"CertificatesResponse",[41,1593,1217],{"class":47},[41,1595,1596,1599,1601,1604,1606,1609],{"class":43,"line":116},[41,1597,1598],{"class":47},"  () ",[41,1600,1182],{"class":365},[41,1602,1603],{"class":372}," $fetch",[41,1605,1331],{"class":47},[41,1607,1608],{"class":70},"\"/api/certificates\"",[41,1610,1611],{"class":47},", { params: filters.value }),\n",[41,1613,1614,1617,1620],{"class":43,"line":122},[41,1615,1616],{"class":47},"  { immediate: ",[41,1618,1619],{"class":54},"true",[41,1621,189],{"class":47},[41,1623,1624],{"class":43,"line":135},[41,1625,1337],{"class":47},[15,1627,1628,1629,1632,1633,1636,1637,1640],{},"TypeScript infère automatiquement le type de ",[19,1630,1631],{},"data"," comme ",[19,1634,1635],{},"Ref\u003CCertificatesResponse | null>"," — pas de casting manuel, pas d'",[19,1638,1639],{},"as any",".",[24,1642,1644],{"id":1643},"typer-les-props-vue-avec-les-interfaces-api","Typer les props Vue avec les interfaces API",[15,1646,1647],{},"Une erreur fréquente : définir des props Vue avec des types locaux qui dupliquent les interfaces API. La bonne approche :",[32,1649,1651],{"className":344,"code":1650,"language":346,"meta":37,"style":37},"// components/CertificateCard.vue\n\u003Cscript setup lang=\"ts\">\nimport type { CertificateSummary } from '@/types/api'\n\nconst props = defineProps\u003C{\n  certificate: CertificateSummary\n  selected?: boolean\n}>()\n\nconst emit = defineEmits\u003C{\n  select: [id: string]\n  statusChange: [id: string, status: CertificateStatus]\n}>()\n\u003C/script>\n",[19,1652,1653,1658,1672,1686,1690,1705,1715,1725,1730,1734,1748,1768,1795,1799],{"__ignoreMap":37},[41,1654,1655],{"class":43,"line":44},[41,1656,1657],{"class":353},"// components/CertificateCard.vue\n",[41,1659,1660,1662,1665,1667,1670],{"class":43,"line":51},[41,1661,917],{"class":365},[41,1663,1664],{"class":47},"script setup lang",[41,1666,1392],{"class":365},[41,1668,1669],{"class":70},"\"ts\"",[41,1671,892],{"class":365},[41,1673,1674,1676,1678,1681,1683],{"class":43,"line":61},[41,1675,1084],{"class":365},[41,1677,369],{"class":365},[41,1679,1680],{"class":47}," { CertificateSummary } ",[41,1682,1090],{"class":365},[41,1684,1685],{"class":70}," '@/types/api'\n",[41,1687,1688],{"class":43,"line":77},[41,1689,360],{"emptyLinePlaceholder":359},[41,1691,1692,1694,1697,1699,1702],{"class":43,"line":90},[41,1693,1546],{"class":365},[41,1695,1696],{"class":54}," props",[41,1698,376],{"class":365},[41,1700,1701],{"class":372}," defineProps",[41,1703,1704],{"class":47},"\u003C{\n",[41,1706,1707,1710,1712],{"class":43,"line":101},[41,1708,1709],{"class":497},"  certificate",[41,1711,501],{"class":365},[41,1713,1714],{"class":372}," CertificateSummary\n",[41,1716,1717,1720,1722],{"class":43,"line":107},[41,1718,1719],{"class":497},"  selected",[41,1721,996],{"class":365},[41,1723,1724],{"class":54}," boolean\n",[41,1726,1727],{"class":43,"line":116},[41,1728,1729],{"class":47},"}>()\n",[41,1731,1732],{"class":43,"line":122},[41,1733,360],{"emptyLinePlaceholder":359},[41,1735,1736,1738,1741,1743,1746],{"class":43,"line":135},[41,1737,1546],{"class":365},[41,1739,1740],{"class":54}," emit",[41,1742,376],{"class":365},[41,1744,1745],{"class":372}," defineEmits",[41,1747,1704],{"class":47},[41,1749,1750,1753,1755,1758,1761,1763,1765],{"class":43,"line":148},[41,1751,1752],{"class":497},"  select",[41,1754,501],{"class":365},[41,1756,1757],{"class":47}," [",[41,1759,1760],{"class":372},"id",[41,1762,67],{"class":47},[41,1764,527],{"class":54},[41,1766,1767],{"class":47},"]\n",[41,1769,1770,1773,1775,1777,1779,1781,1783,1785,1788,1790,1793],{"class":43,"line":161},[41,1771,1772],{"class":497},"  statusChange",[41,1774,501],{"class":365},[41,1776,1757],{"class":47},[41,1778,1760],{"class":372},[41,1780,67],{"class":47},[41,1782,527],{"class":54},[41,1784,178],{"class":47},[41,1786,1787],{"class":372},"status",[41,1789,67],{"class":47},[41,1791,1792],{"class":372},"CertificateStatus",[41,1794,1767],{"class":47},[41,1796,1797],{"class":43,"line":192},[41,1798,1729],{"class":47},[41,1800,1801,1804,1807],{"class":43,"line":217},[41,1802,1803],{"class":365},"\u003C/",[41,1805,1806],{"class":47},"script",[41,1808,892],{"class":365},[15,1810,1811,1814,1815,1818,1819,1822],{},[19,1812,1813],{},"defineProps\u003CT>()"," et ",[19,1816,1817],{},"defineEmits\u003CT>()"," avec des types génériques : pas de ",[19,1820,1821],{},"PropType\u003CT>"," à importer, TypeScript valide les props à la compilation et dans le template.",[24,1824,1826],{"id":1825},"guards-de-type-pour-les-réponses-dapi","Guards de type pour les réponses d'API",[15,1828,1829],{},"Les APIs réelles ne sont pas toujours conformes à leur contrat. Un guard de type permet de valider à runtime sans sacrifier le typage statique :",[32,1831,1833],{"className":344,"code":1832,"language":346,"meta":37,"style":37},"function isCertificate(value: unknown): value is Certificate {\n  return (\n    typeof value === \"object\" &&\n    value !== null &&\n    \"id\" in value &&\n    \"volume\" in value &&\n    \"status\" in value &&\n    [\"ACTIVE\", \"CANCELLED\", \"TRANSFERRED\"].includes(\n      (value as Certificate).status,\n    )\n  )\n}\n\n// Usage\nconst raw = await $fetch(\"/api/certificates/GO-2024-001\")\nif (!isCertificate(raw)) {\n  throw new Error(\"Réponse API invalide\")\n}\n// Ici, TypeScript sait que raw est de type Certificate\nconsole.log(raw.volume)\n",[19,1834,1835,1867,1874,1891,1903,1915,1926,1937,1963,1975,1980,1985,1989,1993,1998,2018,2035,2052,2056,2061],{"__ignoreMap":37},[41,1836,1837,1840,1843,1845,1848,1850,1853,1855,1857,1860,1863,1865],{"class":43,"line":44},[41,1838,1839],{"class":365},"function",[41,1841,1842],{"class":372}," isCertificate",[41,1844,1331],{"class":47},[41,1846,1847],{"class":497},"value",[41,1849,501],{"class":365},[41,1851,1852],{"class":54}," unknown",[41,1854,1262],{"class":47},[41,1856,501],{"class":365},[41,1858,1859],{"class":497}," value",[41,1861,1862],{"class":365}," is",[41,1864,649],{"class":372},[41,1866,492],{"class":47},[41,1868,1869,1871],{"class":43,"line":51},[41,1870,1520],{"class":365},[41,1872,1873],{"class":47}," (\n",[41,1875,1876,1879,1882,1885,1888],{"class":43,"line":61},[41,1877,1878],{"class":365},"    typeof",[41,1880,1881],{"class":47}," value ",[41,1883,1884],{"class":365},"===",[41,1886,1887],{"class":70}," \"object\"",[41,1889,1890],{"class":365}," &&\n",[41,1892,1893,1896,1899,1901],{"class":43,"line":77},[41,1894,1895],{"class":47},"    value ",[41,1897,1898],{"class":365},"!==",[41,1900,1132],{"class":54},[41,1902,1890],{"class":365},[41,1904,1905,1907,1910,1912],{"class":43,"line":90},[41,1906,64],{"class":70},[41,1908,1909],{"class":365}," in",[41,1911,1881],{"class":47},[41,1913,1914],{"class":365},"&&\n",[41,1916,1917,1920,1922,1924],{"class":43,"line":101},[41,1918,1919],{"class":70},"    \"volume\"",[41,1921,1909],{"class":365},[41,1923,1881],{"class":47},[41,1925,1914],{"class":365},[41,1927,1928,1931,1933,1935],{"class":43,"line":107},[41,1929,1930],{"class":70},"    \"status\"",[41,1932,1909],{"class":365},[41,1934,1881],{"class":47},[41,1936,1914],{"class":365},[41,1938,1939,1942,1944,1946,1949,1951,1954,1957,1960],{"class":43,"line":116},[41,1940,1941],{"class":47},"    [",[41,1943,200],{"class":70},[41,1945,178],{"class":47},[41,1947,1948],{"class":70},"\"CANCELLED\"",[41,1950,178],{"class":47},[41,1952,1953],{"class":70},"\"TRANSFERRED\"",[41,1955,1956],{"class":47},"].",[41,1958,1959],{"class":372},"includes",[41,1961,1962],{"class":47},"(\n",[41,1964,1965,1968,1970,1972],{"class":43,"line":122},[41,1966,1967],{"class":47},"      (value ",[41,1969,1305],{"class":365},[41,1971,649],{"class":372},[41,1973,1974],{"class":47},").status,\n",[41,1976,1977],{"class":43,"line":135},[41,1978,1979],{"class":47},"    )\n",[41,1981,1982],{"class":43,"line":148},[41,1983,1984],{"class":47},"  )\n",[41,1986,1987],{"class":43,"line":161},[41,1988,334],{"class":47},[41,1990,1991],{"class":43,"line":192},[41,1992,360],{"emptyLinePlaceholder":359},[41,1994,1995],{"class":43,"line":217},[41,1996,1997],{"class":353},"// Usage\n",[41,1999,2000,2002,2005,2007,2009,2011,2013,2016],{"class":43,"line":225},[41,2001,1546],{"class":365},[41,2003,2004],{"class":54}," raw",[41,2006,376],{"class":365},[41,2008,1422],{"class":365},[41,2010,1603],{"class":372},[41,2012,1331],{"class":47},[41,2014,2015],{"class":70},"\"/api/certificates/GO-2024-001\"",[41,2017,1337],{"class":47},[41,2019,2020,2023,2026,2029,2032],{"class":43,"line":238},[41,2021,2022],{"class":365},"if",[41,2024,2025],{"class":47}," (",[41,2027,2028],{"class":365},"!",[41,2030,2031],{"class":372},"isCertificate",[41,2033,2034],{"class":47},"(raw)) {\n",[41,2036,2037,2040,2043,2045,2047,2050],{"class":43,"line":251},[41,2038,2039],{"class":365},"  throw",[41,2041,2042],{"class":365}," new",[41,2044,1455],{"class":372},[41,2046,1331],{"class":47},[41,2048,2049],{"class":70},"\"Réponse API invalide\"",[41,2051,1337],{"class":47},[41,2053,2054],{"class":43,"line":262},[41,2055,334],{"class":47},[41,2057,2058],{"class":43,"line":268},[41,2059,2060],{"class":353},"// Ici, TypeScript sait que raw est de type Certificate\n",[41,2062,2063,2066,2069],{"class":43,"line":274},[41,2064,2065],{"class":47},"console.",[41,2067,2068],{"class":372},"log",[41,2070,2071],{"class":47},"(raw.volume)\n",[24,2073,2075],{"id":2074},"ce-que-ça-change-en-pratique","Ce que ça change en pratique",[15,2077,2078],{},"Investir dans une modélisation TypeScript rigoureuse dès le début du projet, c'est :",[2080,2081,2082,2090,2096,2102],"ul",{},[2083,2084,2085,2089],"li",{},[2086,2087,2088],"strong",{},"Autocomplétion fiable"," dans l'IDE sur tous les objets API",[2083,2091,2092,2095],{},[2086,2093,2094],{},"Erreurs détectées à la compilation"," plutôt qu'en production",[2083,2097,2098,2101],{},[2086,2099,2100],{},"Refactoring sûr"," — renommer un champ dans l'interface propage l'erreur partout où il est utilisé",[2083,2103,2104,2107],{},[2086,2105,2106],{},"Documentation implicite"," — les types sont la source de vérité sur la forme des données",[15,2109,2110],{},"Le coût d'entrée est réel, surtout sur un projet existant avec du JavaScript à migrer. Mais sur un nouveau projet Vue.js + API tierce, partir en TypeScript strict depuis le premier commit est toujours la décision la plus rentable à moyen terme.",[2112,2113,2114],"style",{},"html pre.shiki code .sQ3_J, html code.shiki .sQ3_J{--shiki-dark:#E1E4E8;--shiki-default:#24292E}html pre.shiki code .s0DvM, html code.shiki .s0DvM{--shiki-dark:#79B8FF;--shiki-default:#005CC5}html pre.shiki code .sg6BJ, html code.shiki .sg6BJ{--shiki-dark:#9ECBFF;--shiki-default:#032F62}html pre.shiki code .seUEM, html code.shiki .seUEM{--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic;--shiki-default:#B31D28;--shiki-default-font-style:italic}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sryI4, html code.shiki .sryI4{--shiki-dark:#6A737D;--shiki-default:#6A737D}html pre.shiki code .scx8i, html code.shiki .scx8i{--shiki-dark:#F97583;--shiki-default:#D73A49}html pre.shiki code .s-Z4r, html code.shiki .s-Z4r{--shiki-dark:#B392F0;--shiki-default:#6F42C1}html pre.shiki code .sFbx2, html code.shiki .sFbx2{--shiki-dark:#FFAB70;--shiki-default:#E36209}",{"title":37,"searchDepth":51,"depth":51,"links":2116},[2117,2118,2119,2120,2121,2122,2123,2124],{"id":26,"depth":51,"text":27},{"id":337,"depth":51,"text":338},{"id":539,"depth":51,"text":540},{"id":824,"depth":51,"text":825},{"id":1065,"depth":51,"text":1066},{"id":1643,"depth":51,"text":1644},{"id":1825,"depth":51,"text":1826},{"id":2074,"depth":51,"text":2075},"2025-06-09","La plupart des tutoriels TypeScript montrent des exemples triviaux : interface User { name: string; age: number }. En production, la réalité est plus complexe. Les APIs externes renvoient des structures imbriquées, des champs optionnels selon le contexte, des unions de types selon l'état. Voici comment structurer tout ça proprement, à partir d'un cas réel.",null,"md",{},"/fr/blog/typescript-interfaces",{"title":5,"description":2126},"typescript-interfaces","fr/blog/typescript-interfaces",[2135,2136,2137,2138],"TypeScript","Vue.js","API","Frontend","HyPqkidmiGOMoAR3BKpXWG3rx-rDw3BW0i7r2togqm4",1774645635852]