salesforce / lwc

⚡️ LWC - A Blazing Fast, Enterprise-Grade Web Components Foundation
https://lwc.dev
Other
1.63k stars 394 forks source link

Type definition for `createElement` is inaccurate #4292

Open wjhsf opened 3 months ago

wjhsf commented 3 months ago

Description

The element returned by createElement is an HTML element with all of the @api-decorated properties from the provided component class. And we see, from usage, that it is very common for projects to use these additional props in their code. To improve the TypeScript developer experience in LWC v7, we updated the type signature to include the props in addition to the base HTMLElement interface. However, due to a limitation of TypeScript, it is not possible to detect which component props have been decorated. Consequently, we made the decision to include all component props on the return type, even though they don't all exist on the runtime object. This facilitates authoring TypeScript code when using the @api props, but it may cause confusion if users try to access the other props that don't actually exist.

Steps to Reproduce

// x/foo/foo.ts
import { api, LightningElement } from 'lwc';

export default class extends LightningElement {
  @api exposedProp = 'hello';
  privateProp = 'secret';
}

// app.ts
import { createElement } from 'lwc';
import Foo from 'x/foo';

const foo = createElement('x-foo', { is: Foo });
console.log(foo.exposedProp) // ok
console.log(foo.privateProp)
//              ^^^ should be a type error, but TypeScript thinks it's a string

Expected Results

The code should result in a type error: "Property 'privateProp' does not exist on type 'LightningHTMLElement'."

Actual Results

TypeScript thinks that foo.privateProp is a string, even though it doesn't actually exist at runtime.

Workaround

Users who want to have truly accurate types can provide an explicit generic parameter to createElement, and the returned type will include only the props exposed.

// These will have no additional properties, i.e. be just `HTMLElement`
const foo = createElement<object>('x-foo', { is: Foo });
const foo = createElement<LightningElement>('x-foo', { is: Foo });
// This will have only `exposedProp` available, *not* `privateProp`
const foo = createElement<{ exposedProp: string }>('x-foo', { is: Foo });
console.log(foo.exposedProp) // ok
console.log(foo.privateProp) // type error, as desired!

Users Affected

This bug only impacts projects written in TypeScript.

Version