본문 바로가기
개발/typescript, web

[21-06-15 사내모각코] Trait typescript로 구현해보기

by 꾸루루 2021. 6. 16.

Rust Trait
https://doc.rust-lang.org/rust-by-example/trait.html 이것에 대한 개념 구현(컨셉카 같은 느낌으루다가..)
trait은 공동으로 구현할 것을 추상화 해놓은 것.

해당 trait으로 지정한 프로퍼티는 오직 해당 trait만 알아야 하기 때문에 symbol로 내부적으로 생성하여 symbol로 지정하려는 구조체의 프로퍼티를 작성하려고 하였음.
이렇게 하면 해당 trait은 해당 구조체가 trait을 구현하였는지 알 수 있음.

간단하게 구현하는데는 성공했지만 실제로 사용하려니까 unique symbol 취급을 받지 못하여 해당 심볼로 프로퍼티를 선언할 수 없어서 실패함.
→ 적용하려면 nanoid 같은걸로 해서 유니크한 아이디를 만들어서 해야겠음.

*unique symbol과 symbol을 구분하고 unique symbol이 아니면 프로퍼티를 정의할 수 없게 해놓은 것은, 사용자가 심볼로 프로퍼티를 만들었다가 해당 심볼을 잃어버리게 되면 정의해놓은 프로퍼티에 접근하지 못해서 이런 실수를 방지하고자 막아놓은 것으로 추정됨.

 

export class Trait<T extends Record<string, any>> {
#keyToSym: = new Map<keyof T, symbol>()
#symToTrait = new Map<symbol, any>()
constructor(traitData: T) {
for (const traitDataKey in traitData) {
const sym = Symbol(traitDataKey)
this.#keyToSym.set(traitDataKey, sym)
this.#symToTrait.set(sym, traitData[traitDataKey])
}
}
get syms() {
return Object.fromEntries(this.#keyToSym) as { [key in keyof T]: symbol }
}
private get traits() {
return Object.fromEntries(
[...this.#keyToSym.entries()].map(([key, sym]) => [
key,
this.#symToTrait.get(sym),
])
) as { [key in keyof T]: T[key] }
}
static merge<T extends Trait<any>[]>(...traits: [...T]) {
return new Trait({
...traits.reduce((obj, trait) => {
return Object.assign(obj, { ...Object.entries(trait.traits) })
}, {}),
})
}
sym(name: keyof T): symbol {
const _sym = this.#keyToSym.get(name)
if (!_sym) throw Error('symbol not found.')
return _sym
}
isImpl(t: any) {
const obj = t.prototype?.constructor ? t.prototype : t
return [...this.#keyToSym.values()].every((sym) => obj[sym])
}
}
// 밑에는 걍 테스트 하던거...
const t = new Trait({
name: 'ss',
}) /*?*/
t.sym('name') /*?*/
t.syms /*?*/
class A {}
t.isImpl(A) /*?*/
const s = Symbol()
class B {
[t.sym('name')]: 'dd'; // ERROR!!
[s]: 'aaa'
} /*?*/
view raw Trait.ts hosted with ❤ by GitHub

 

reference)

https://github.com/adobe/ferrum#traits

구현 방식을 참고함.