Как правильно расширять классы через generic-интерфейсы
У меня есть условный базовый класс с реализацией:
interface IMessageStruct<T> {
name: keyof T;
type: number;
}
abstract class Base<T> {
protected abstract readonly types: Array<IMessageStruct<T>>;
toJSON(): T {
return (this.types.reduce((memo, { name }) => {
memo[name] = this[name];
return memo;
}, {} as Partial<T>)) as T;
}
// other code
}
Есть множество дочерних классов, которые выглядят следующим образом
class Init extends Base<IInit> implements IInit {
// some code
}
Подскажите, можно ли как-то переписать Base класс таким образом, чтобы в каждом дочернем не было необходимости наследоваться от интерфейса и в методе toJSON не выводилась ошибка на this[name]
Если это изначально не правльный подход, буду рад, если покажите, в каком направлении двигаться.
Ответы (1 шт):
Автор решения: qwabra
как не писать
→ Ссылка
- declaration-merging - http://www.typescriptlang.org/docs/handbook/declaration-merging.html#basic-concepts
- omit - http://www.typescriptlang.org/docs/handbook/utility-types.html#omittk
как не писать implements у класса?
назвать интерфейс так же как и класс (смотри declaration-merging )
interface $type<T> {
name: keyof T;
type: number;
}
type $typeList<T> = $type<T>[];
abstract class Base {
protected abstract readonly types: $typeList<this>;
toJSON(): Omit<this, 'toJSON' | 'types'> { return null as any }
}
// ------------
interface Next {
name: string;
surname: string
age: number
}
class Next extends Base {
types = [
Next.genType('name', 1),
Next.genType('age', 1),
Next.genType('surname', 1),
]
}
namespace Next {
type $genType<T> = (name: keyof T, type: number) => $type<T>
type $exclude = 'toJSON' | 'types'
export const genType: $genType<Omit<Next, $exclude>> = (name, type) => ({ name, type })
}
const next = new Next
const json = next.toJSON() // Pick<Next, "name" | "surname" | "age">
если брать ваш код, то можно написать так:
interface IMessageStruct<T> {
name: keyof T;
type: number;
}
abstract class Base<T> {
protected abstract readonly types: Array<IMessageStruct<T>>;
toJSON(): T { return null as any}
}
// ------------
interface Next {
name: string;
surname: string
age: number
}
class Next extends Base<Next> {
types = [
Next.genType('name', 1),
Next.genType('surname', 1),
]
}
namespace Next {
type $genType<T> = (name: keyof T, type: number) => IMessageStruct<T>
export const genType: $genType<Next> = (name, type) => ({ name, type })
}
рекомендую подумать над таким вариантом
разделить данные и методы (например age и setAge)
то есть все данные вынести в переменную, например data
interface $type<T> {
name: keyof T;
type: number;
}
type $typeList<T> = $type<T>[];
abstract class Base {
abstract data
protected abstract readonly types: $typeList<this['data']>;
toJSON(): this['data'] { return null as any }
}
// ------------
interface $data {
name: string;
surname: string
age: number
}
class Next extends Base {
readonly data: $data
types = [
Next.genType('name', 1),
Next.genType('age', 1),
Next.genType('surname', 1),
]
setAge(age: number) { this.data.age = age }
}
namespace Next {
type $genType<T> = (name: keyof T, type: number) => $type<T>
export const genType: $genType<Next['data']> = (name, type) => ({ name, type })
}
const next = new Next
next.setAge(22)
const json = next.toJSON() // Pick<Next, "name" | "surname" | "age">