Skip to content

Instantly share code, notes, and snippets.

@emilien-jegou
Created February 28, 2025 05:04
Show Gist options
  • Save emilien-jegou/4ce3e69fb0bcfc047274bda6535df408 to your computer and use it in GitHub Desktop.
Save emilien-jegou/4ce3e69fb0bcfc047274bda6535df408 to your computer and use it in GitHub Desktop.
typed class inheritance through functions in typescript
///// Example usage of inheritance using class functions
///// Demonstrates how object-oriented programming
///// can be achieved through clever use of functional
///// programming patterns.
type Ty = { symbol: symbol; parent?: Ty };
type Class = { __ty: Ty };
type Constructor = (...args: any) => Record<string, any>;
type ClassInterface<C extends Constructor> = ((...args: Parameters<C>) => ReturnType<C> & Class) & {
isInstance(obj: Class): obj is ReturnType<C> & Class;
};
const checkIsInstance = (ty: Ty, symbol: symbol): boolean =>
ty.symbol === symbol || (!!ty.parent && checkIsInstance(ty.parent, symbol));
const cls = <C extends Constructor>(createFn: C): ClassInterface<C> => {
const symbol = Symbol(/* you could give the symbol a name tied to the class for debugging */);
const constructorFn = ((...args: any) => {
const obj = createFn(...args);
obj.__ty = { symbol, parent: obj.__ty ?? undefined };
return obj;
}) as ClassInterface<C>;
constructorFn.isInstance = (obj: Class): obj is ReturnType<C> & Class =>
checkIsInstance(obj.__ty, symbol);
return constructorFn;
};
///// Usage
const buildHtmlTag = (tagName: string, attributes: Record<string, string>, childHtml: string) =>
`<${tagName} ${Object.entries(attributes)
.map(([key, value]) => `${key}="${value}"`)
.join(' ')}>${childHtml}</${tagName}>`;
type Elem = {
tagName: string;
childrens: Elem[];
attributes: Record<string, string>;
getHTML(): string;
};
const createElem = cls(
(tagName: string): Elem => ({
tagName,
childrens: [],
attributes: {},
getHTML() {
const childHtml = this.childrens.map((c) => c.getHTML()).join('');
return buildHtmlTag(tagName, this.attributes, childHtml);
},
}),
);
const createAnchorElem = cls((href: string) => {
const elem = createElem('a');
elem.attributes['href'] = href;
return elem;
});
type DownloadLinkElem = Elem & { loading: boolean };
const createDownloadLinkElem = cls((href: string, download: string) => {
const elem = createAnchorElem(href) as unknown as DownloadLinkElem;
elem.attributes['download'] = download;
elem.loading = false;
return elem;
});
const elem = createDownloadLinkElem('localhost', 'file.pdf');
console.log(elem.getHTML());
console.log(createDownloadLinkElem.isInstance(elem)); // true
console.log(createElem.isInstance(elem)); // true
const divElem = createElem('div');
divElem.childrens.push(elem);
console.log(divElem.getHTML());
console.log(createDownloadLinkElem.isInstance(divElem)); // false
console.log(createElem.isInstance(divElem)); // true
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment