3

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

9
  • I'm not sure I understand the question properly, but maybe you're wanting to extend CustomClass? Otherwise the customClass implementation has no relationship to the CustomClass type definition? class customClass extends CustomClass { ... } Commented Sep 1, 2021 at 10:57
  • Might be just an error in the question, but your type definition is for CustomClass while your js code is for customClass (JavaScript IS case sensitive) Commented Sep 1, 2021 at 22:29
  • @RobertDempsey I understand what you're saying. My thought is that the relationship exist because I give the props "customClass" to the component MyComponent. The type check works fine for the other props, why not with the class? Is it mandatory to extends the class so that inherit the type? Commented Sep 2, 2021 at 9:28
  • 1
    Thanks for taking the time to set this up. I'm afraid I still don't quite understand the question. Specfically, I'm looking at ActionProvider.ts. This is where you are defining the ActionProvider class, and there is no link between ActionProvider and your index.d.ts file, so it cannot inherit something you are not referencing at all. If you want your class to infer the types of its variables, you'll need to either do class ActionProvider extends SomeOtherClass or class ActionProvider implements SomeInterface Commented Sep 3, 2021 at 12:12
  • 1
    Out of curiosity, have you tried customClass: typeof CustomClass in the MyComponentProps definition? If you want to pass the class itself as a value then the type needs to be the type of the constructor not the type of the instance. Commented Sep 6, 2021 at 20:06

1 Answer 1

4
+50

I've forked and edited your code sandbox, check it out:

Edit React Typescript Playground (forked)

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.

Constructor

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 {}

Instance

Variable puppy is an instance of MyPuppet

// javascript
const puppy = new MyPuppet()

Instance Type vs Constructor Type

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.

  1. Declare a JS constructor of the name MyPuppy.
  2. Implicitly declare a TS instance type MyPuppy.
  3. On the "instance side", foo is an instance, and has a property species and a method poop.
  4. On the "static side", the 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 }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.