Reputation: 681
I'm writing a Typescript module for an external library.
Here's a piece of my index.d.ts
declare module 'my-external-library' {
export class MyComponent extends React.Component<MyComponentProps> {}
export class CustomClass {
constructor(
firstParam: string,
secondParam: string,
)
}
interface MyComponentProps {
config: string
customClass: CustomClass
text?: string
customFn?: (input: string) => boolean
}
}
This works fine except for the customClass
prop.
Custom Class is a class that has to be like this:
class customClass {
constructor(firstParam, secondParam) {
this.firstParam = firstParam
this.secondParam = secondParam
}
... all the methods you want
}
export default customClass
In a separate file I give the customClass
props to MyComponent
, like this:
import customClass from './customClass'
<MyComponent
config: "test"
customClass: customClass
/>
customClass
actually is not receiving the "constructor types" declared in index.d.ts
, but all other props of the component works well.
So, the class definition I've made is correct? I have to export the class with another approach?
Here's a CodeSandbox: https://codesandbox.io/s/react-typescript-playground-forked-nrfk2
Thanks for any help, forgive my inexperience
Upvotes: 3
Views: 1568
Reputation: 20007
I've forked and edited your code sandbox, check it out:
Now I'm gonna explain a few things about class in typescript, which is indeed quite confusing.
It's in part due to the fact that people use the term "class" to refer to diff things in diff contexts. To disambiguate, let's use more accurate terms.
MyPuppy
is often called a "class", but it's also a "constructor function" in JS terms. So let's stick to constructor throughout this discussion.
// javascript
class MyPuppy {}
Variable puppy
is an instance of MyPuppet
// javascript
const puppy = new MyPuppet()
In typescript, when you declare a class like below, you'll implicitly declare an instance type of the same name!
This behaviour confuses a lot of people. In short, MyPuppy
as a JS variable holds a constructor, whereas MyPuppy
as a TS type variable actually holds an instance type.
Now how do we refer to the type of MyPuppy
constructor, aka the constructor type? You should literally use typeof MyPuppy
.
// typescript
class MyPuppet {}
type MyPuppetInstance = MyPuppet
type MyPuppetConstructor = typeof MyPuppet
We've done disambiguating terms, let's do some case study.
There's this concept of the "instance side" and "static side" of a class, once documented in the old TS handbook. Basically "instance side" corresponds to instance type, "static side" to constructor type.
class MyPuppy {
static getSpecies(dog: MyPuppy) {
return dog.species
}
constructor(species: string) { this.species = species }
species: string
// [side note] above two lines can be combined as one:
// constructor(public species: string) {}
poop() { return '💩' }
}
type MyPuppetInstance = MyPuppet
type MyPuppetConstructor = typeof MyPuppet
var bar: MyPuppetConstructor = MyPuppy
var foo: MyPuppetInstance = new bar("shiba_inu")
The above snippet does several things.
MyPuppy
.MyPuppy
.foo
is an instance, and has a property species
and a method poop
.bar
is a constructor, it's a "newable function" which can be invoked as new bar("shiba_inu")
, and also has a static method getSpecies
.Now in a .d.ts
file, how can we declare the above types? You can use either the interface
keyword or the type
keyword. They make almost no difference.
// index.d.ts
interface MyPuppyInstance {
species: string
poop(): string
}
interface MyPuppyConstructor {
new (species: string): MyPuppyInstance
getSpecies(dog: { species: string }): string
}
type MyPuppyInstance2 = {
species: string
poop(): string
}
type MyPuppyConstructor2 =
(new (species: string) => MyPuppyInstance)
& { getSpecies(dog: { species: string }): string }
Upvotes: 4