I'm new to TypeScript and I'm trying to convert my own way of binding (vanilla) JS components to DOM elements into TypeScript, but I'm struggling hard. Here's what I have so far. While I understand why the compiler is complaining, I don't know how to solve it.
index.ts (entry point)
import App from './App'
import bindings from './bindings'
const app = new App();
app.bind(bindings);
bindings.ts
import Foo from './components/Foo';
type Binding = {
component: Function;
name: string;
options?: object;
}
export default [
{ component: Foo, name: 'foo' }, // Would get bound to .js-foo elements
] as Binding[];
App.ts (truncated for brevity)
export default class App
{
bind(bindings: Binding[] = []) {
for (const binding of bindings) {
document.body.querySelectorAll(`.js-${binding.name}`).forEach($el => {
// Initialize a custom object on the DOM element to store component instance
const app: { [key: string]: object } = {};
$el.app = $el.app ?? app
// What I'm trying to accomplish in non type checking code:
$el.app[binding.name] = new binding.component(binding.options ?? {});
// Left-hand error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'.
// No index signature with a parameter of type 'string' was found on type '{}'.ts(7053)
// Right-hand error: This expression is not constructable.
// Type 'Function' has no construct signatures.ts(2351)
});
}
}
}
Any help is very much appreciated!
CodePudding user response:
Left-hand error:
TS is complaining because $el.app
does not necessarily have a key with the arbitrary name from binding.name
. You are setting a valid type for this usecase when defining the const app
, but there's not a guarantee that $el.app
will take that value, due to the nullish operator.
The brute-force solution is to simply cast $el.app
:
$el.app = ($el.app ?? {}) as { [key: string]: object };
Right-hand error:
Function
and classes are handled differently in typescript. If you want a generic class, you can use this type:
type ClassConstructor = new (...args: any[]) => unknown;
Or better yet, create a base class, and use it as the type:
class BaseBindingComponent {}
. . .
class Foo extends BaseBindingComponent {}
. . .
type Binding = {
component: BaseBindingComponent;
name: string;
options?: object;
}