仓库指标
- 星标
- (16,966 个星标)
- PR 合并指标
- (PR 指标待抓取)
描述
Should this be an RFC?
- This is not a substantial change
Which package is this a feature request for?
Lit Core (lit / lit-html / lit-element / reactive-element)
Description
Right now, asyncAppend types the parameter passed to the optional mapper function as unknown (async-append.d.ts):
export declare const asyncAppend: (value: AsyncIterable<unknown>, _mapper?: ((v: unknown, index?: number) => unknown) | undefined) => import("../directive.js").DirectiveResult<typeof AsyncAppendDirective>;
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^^
This is awkward, particularly with strict: true in TypeScript, if you want to do nearly anything with the value in the mapper, such as operate on it or use it as the value of a typed property.
Instead, if we make asyncAppend generic like map is, it's more convenient to use. Here's the change:
export declare const asyncAppend: <T>(value: AsyncIterable<T>, _mapper?: ((v: T, index?: number) => unknown) | undefined) => import("../directive.js").DirectiveResult<typeof AsyncAppendDirective>;
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^−−−−−−−−−−−−−−−−−−−−−−^−−−−−−−−−−−−−−−−−−^
I could be mistaken, but this should be very broadly backward-compatible with existing code, possibly entirely so.
I'm happy to provide a PR if it would be welcome.
Motivating Examples
Documentation Example
My first motivating example is in the documentation for asyncAppend, which produces an error if you have strict: true in tsconfig.json. Here's the documentation's example:
async function *tossCoins(count: number) {
for (let i=0; i<count; i++) {
yield Math.random() > 0.5 ? 'Heads' : 'Tails';
await new Promise((r) => setTimeout(r, 1000));
}
}
@customElement('my-element')
class MyElement extends LitElement {
@state()
private tosses = tossCoins(10);
render() {
return html`
<ul>${asyncAppend(this.tosses, (v: string) => html`<li>${v}</li>`)}</ul>`;
}
}
That produces the following error on the mapper callback in the asyncAppend call:
Argument of type '(v: string) => TemplateResult<1>' is not assignable to parameter of type '(v: unknown, index?: number | undefined) => unknown'. Types of parameters 'v' and 'v' are incompatible. Type 'unknown' is not assignable to type 'string'.ts(2345)
Here's that error in a TypeScript playground (you can ignore the "Cannot find global type 'AsyncIterableIterator'." error in that playground). Alternatively, you can see it by cloning this GitHub project and opening src/my-element.ts.
In that specific case, even with the existing definition you could just remove the type annotation on v in the mapper function, letting v be unknown, since all the mapper does is dump it out as text.
My Use Case
My second motivating example is what brought this problem to my attention: I wanted to pass the mapper value to a component with a typed property:
<div>
${asyncAppend(
this.values,
(v) => html`<example-element .someProperty=${v}></example-element>`
)}
</div>
That fails because someProperty on example-element is of type string, and I can't directly assign unknown to it. I'd have to use ${v as string} instead, which both is inconvenient and sacrifices type safety.
Please let me know if there's any further information etc. I can provide.
Thanks for Lit!
Alternatives and Workarounds
It's possible to work around issues with the parameter's type being unknown with type assertions and such, but type assertions tend to become maintenance hazards.[citation needed 😉]
Versions
Lit 3.2.1 TypeScript 5.6.3