Created
February 28, 2025 05:04
-
-
Save emilien-jegou/4ce3e69fb0bcfc047274bda6535df408 to your computer and use it in GitHub Desktop.
typed class inheritance through functions in typescript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
///// 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