Яб сделал так:
type WritableFields<T> = Partial<Pick<T, WritableKeys<T>>>;
export function createElement<K extends keyof HTMLElementTagNameMap>(tag: K, options?: WritableFields<HTMLElementTagNameMap[K]>): HTMLElementTagNameMap[K] {
let element = document.createElement(tag)
if (options) {
// assign options
Object.assign(element, options);
}
return element
}
Если нужно именно с перебором, то можно так:
type WritableFields<T> = Partial<Pick<T, WritableKeys<T>>>;
export function createElement<K extends keyof HTMLElementTagNameMap>(tag: K, options?: WritableFields<HTMLElementTagNameMap[K]>): HTMLElementTagNameMap[K] {
let element = document.createElement(tag)
if (options) {
// assign options
let option: keyof typeof options;
for (option in options) {
element[option] = options[option]!; // ! - потому что теоретически ты можешь передать undefined явно, но морочиться с этим не стоит
}
}
return element
}
P.S. Вообще все сложности с перебором переданного объекта в typescript происходят из того, что типизация в нём структураная: например имея интерфейс параметра, например,
interface Foo {
foo: number;
}
тебе ничего не мешает передать ему
{
foo: 1,
bar: 2
}
, т.к. данный объект расширяет интерфейс Foo и, соответственно, удовлетворяет ему. Получается, что внутри функции typescript никак не может быть уверен, что ключи указанные в интерфейсе - это ВСЕ ключи, а значит не может быть уверен, что при переборе и присвоении не будет присвоено что-то лишнее. (Это не говоря уже о прототипах, геттерах и прочем)
Потому и требуется куча плясок, увы.