Begränsa egenskapsdekorerare till specifik konfigurerad typ i TypeScript
Notera: denna fråga utökar endast Property-dekoratorn för en specifik egenskapstyp
--
Mitt mål är att designa en enkel ORM, där en modell kan definieras på följande sätt:
@model.model()
class User extends model.Model {
@model.attr(model.StringType)
user: string;
@model.attr(model.StringType)
age: number;
}
I den här modellen vill jag att kompilatorn ska generera ett typfel, eftersom StringType inte kan tillämpas på ett attribut av typen number. Jag kan dock inte få detta att fungera korrekt -- inget fel genereras.
Min kod hittills:
interface Constructor<T> {
new (...args: any[]): T;
}
abstract class BaseType<T> {}
class StringType extends BaseType<string> {}
type RecordKey = string | symbol;
export function attr<T>(type: Constructor<BaseType<T>>) {
return <K extends RecordKey, C extends Record<K, T>>(ctor: C, key: K) => {
console.log(ctor, key);
}
}
Några tips på hur denna kod inte fungerar skulle vara otroligt användbara här.
Svar #1
Typescript är strukturellt typat, vilket är nyckeln till hela detta svar. I det här fallet betyder det att vilket vanligt objekt som helst kan vara en BaseType<T> eftersom det inte har några fält/metoder. Observera att den härledda typen rapporterar {} för T i @model.attr(model.StringType) i lekplatsen.
Följande tilldelning är helt okej (och number kan vara vad som helst):
abstract class BaseType<T> { }
const t: BaseType<number> = { };
För att kräva att den generiska anges, försök:
function attr<T = never>(type: Constructor<BaseType<T>>) { ... }
// ...
@model.attr<string>(model.StringType)
user: string
men notera att det kommer att tillåta @model.attr<number>(model.StringType) eftersom återigen, varje objekt är en BaseType<number>.
Om ditt BaseType-gränssnitt ännu inte är färdigt, fortsätt fylla i det och du kanske upptäcker att det finns tillräckligt mycket för att TypeScript ska kunna hänga sig fast vid och härleda från. Till exempel (helt godtyckligt men så att du får idén och kan testa härledningen):
abstract class BaseType<T> {
deserialize: (s: string) => T;
}
