strukturell typning - Typescript typkompatibilitetstest
Stöder Typescript ett direkt test för strukturell typkompatibilitet?
C# stöder is-operatorn och typer erbjuder IsAssignableFrom(object instance)
if (foo is SomeType) ...
if (SomeType.IsAssignableFrom(foo)) ...
Finns det något direkt sätt att utföra denna typ av kontroll i Typescript eller måste jag undersöka varje nödvändig medlem?
instanceof kommer förmodligen att duga för det aktuella fallet men respekterar inte strukturell kompatibilitet, istället kontrollerar det prototypkedjan.
Ja ja, jag vet, TypeScripts instanceof är direkt motsvarande C#:s is-operator. Men jag började med att ange strukturell typning.
Kanske borde jag sätta en bluffmetod på Object
Svar #1
Jag tror att det lämpliga svaret här är
Nej, men!
Så för att bara få detta ur vägen, existerar Typescripts typinformation endast vid kompileringstid. Det finns inget sätt att göra en kontroll av strukturell likhet vid körningstid. Du kan naturligtvis göra denna kontroll vid kompileringstid men det kallas bara tilldelning och är förmodligen inte vad du frågar om.
Lång historia kort, du måste vanligtvis undersöka de nödvändiga medlemmarna, men det finns några sätt att vara smart med detta.
Från och med Typescript 1.6 stöder språket användardefinierade typvakter
Dessa är fantastiska som bekvämlighetsfunktioner och är ett sätt att säkert nedkasta/ange/förfina en typ (beroende på vilken terminologi du föredrar). Du kan manuellt kontrollera förekomsten av vissa typer om du vill på de vanliga sätten (.hasOwnProperty och liknande), enligt min erfarenhet är de mer effektiva som sätt att skicka vidare baserat på meddelandetyper. I de flesta meddelandehanteringssystem kan du ha en typ-egenskap. Du kan definiera en funktion
function isConfigGetMsg(msg: BaseMessage): msg is ConfigGetMsg {
return msg.name === MsgTypes.CONFIG_GET_MSG;
}
//and use it as
if (isConfigGetMsg(msg)){
console.log(msg.key); //key being a ConfigGetMsg only value
}
och detta kommer att vara helt okej så länge dina meddelanden är välformade. Men som du kan se behöver du testa efter specifik information. Om du vill ha en mer generell metod är detta inte det.
Som du nämnde kan du undersöka varje av de nödvändiga medlemmarna. Du kan göra det lite lättare för dig själv genom att använda den tidigare tekniken för att skapa något liknande detta:
interface One {
a: number
}
interface Two extends One {
b: string
}
function isAssignableTo<T>(potentialObj:Object, target:T) : potentialObj is T {
return Object.keys(target).every(key => potentialObj.hasOwnProperty(key))
}
let a: Object = {
a: 4,
b: 'hello'
};
let b: Two = undefined;
if(isAssignableTo(a, b)) {
b = a;
}
funktionen isAssignableTo är den viktiga punkten här. Vilket håll parametrarna bör vara i är debatterbart men också irrelevant, så sätt dem hur du vill. Detta är inte en idealisk lösning. Du kanske vill att saker ska kunna tilldelas baserat endast på icke-funktions-egenskaper, till exempel att antyda tillstånd och inte gemensam funktionalitet. Du skulle kunna försöka utöka kontrollen för att ta hänsyn till det, men det finns fallgropar oavsett.
Jag är ledsen att säga att jag tror att det enda "sanna" svaret är ett som ännu inte är genomförbart. Om du arbetar med klasser kan du använda dekoratörer för att fånga metadata om din applikation, inklusive typinformation som senare kan användas för reflektion/inspektion. Du kan skapa dina egna dekoratörer för att göra detta, men du kan också utnyttja TypeScripts egen metadata-generering för klassens egenskaper, som du kan aktivera med en tsconfig-flagga.
Detta förutsätter att metadatareflektions-API:et är närvarande. Du kommer att behöva använda detta för att extrahera den informationen. Du skulle sedan kunna skriva om funktionen isAssignableTo från tidigare för att arbeta utifrån metadata. Om jag minns rätt lider detta fortfarande av problemet att typer lagras som strängar och om du sedan har subtyper kommer de inte att matcha.
Hur som helst, jag hoppas att det hjälper. Förlåt att jag inte kunde ge dig ett enkelt ja. Med min tur letade du faktiskt bara efter uppgiften ;).
Svar #2
Det är faktiskt möjligt att implementera en version av detta, åtminstone med klasser:
export function isAssignableFrom(object:any, cls:any) {
let proto = object.prototype
while (proto != null) {
if (proto instanceof cls) {
return true
}
proto = proto.prototype
}
return false
}
med den här metoden kan du göra saker som:
class Foo {}
class Bar extends Foo {}
isAssignableFrom(Bar, Foo)
// --> true
Som diskuterat i det länkade inlägget är det lite nischat, men vi använder detta på några ställen i JsPlumb-kodbasen.
https://jsplumbtoolkit.com/talking-tech/2024/05/10/is-assignable-from
