C
C
ColdSpirit2021-06-03 20:50:07
Data types
ColdSpirit, 2021-06-03 20:50:07

How to overwrite HTMLElement fields with fields from input object in Typescript?

Good afternoon! The goal is to create a function that takes a tag, options to rewrite fields, and returns a ready-to-use object. But the typescript throws spokes into the wheels, I don’t understand why. I am not familiar with the typescript, I googled the type that pulls out the fields allowed for writing from the object, it seems like it should work, but still a strange error is issued. How to fix the error?

type IfEquals<X, Y, A=X, B=never> =
  (<T>() => T extends X ? 1 : 2) extends
  (<T>() => T extends Y ? 1 : 2) ? A : B;

type WritableKeys<T> = {
  [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P>
}[keyof T];

type ReadonlyKeys<T> = {
  [P in keyof T]-?: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, never, P>
}[keyof T];

type WritableFields = Pick<HTMLElement, WritableKeys<HTMLElement>>

export function createElement(tag: string, options?: WritableFields): HTMLElement {
    let element = document.createElement(tag)

    if (options) {
        // assign options
        let option: keyof WritableFields
        for (option in options) {
            element[option] = options[option] // Type 'undefined' is not assignable to type 'never'
        }
    }

    return element
}


The same example in the sandbox: link

PS I know about Object.assign, I'm interested in how to solve this particular problem.

Answer the question

In order to leave comments, you need to log in

1 answer(s)
A
Aetae, 2021-06-03
@ColdSpirit

Yab did this:

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
}

If you need it with iteration, then you can do this:
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
}

PS In general, all the difficulties with iterating over the passed object in typescript come from the fact that the typing in it is structural: for example, having a parameter interface, for example,
interface Foo {
  foo: number;
}
there's nothing stopping you from telling him
{
  foo: 1,
  bar: 2
}
, because this object extends the Foo interface and, accordingly, satisfies it. It turns out that inside the typescript function there is no way to be sure that the keys specified in the interface are ALL keys, which means that it cannot be sure that something superfluous will not be assigned when iterating and assigning. (This is not to mention prototypes, getters, etc.)
That's why a lot of dancing is required, alas.

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question