9

I'm writing a library and I want to port it to TypeScript.

Currently it looks something like this:

File index.js

const is = value => {
    // ... Do some returns here
}

is.number = x => typeof x === 'number'
is.bla = x => typeof x === 'bla'

And so on.

I have wrote an interface describing is and all its methods.

type TypeQueryMethod = (val: any) => boolean;

interface Is {
    (val: any): string;
    undefined: TypeQueryMethod;
    null: TypeQueryMethod;
    // ...
}

When I try to mark is with the type: const is: Is = value => ...

It throws an error:

Type '(value: any) => string' is not assignable to type 'Is'.
Property 'undefined' is missing in type '(value: any) => string'.

Which makes sense because the declaration of the object is split.

How do you construct such an object that is both a method, and has methods?

3 Answers 3

10

Update for TypeScript 3.1+

TypeScript 3.1 introduced support for declaring properties on functions, to allow you to do this the way you were doing it to begin with:

const is: Is = (val: any) => typeof val; // okay
is.undefined = (val: any) => typeof val === 'undefined'; 
is.null = (val: any) => (val === null)

For TypeScript versions before 3.1:

If you want to make the type checker happy, you can use Object.assign() to return a fully-formed Is object without building it in stages:

const is: Is = Object.assign(
  (val: any) => typeof val,
  {
    undefined: (val: any) => typeof val === 'undefined',
    null: (val: any) => (val === null)
    // ...
  }
);

Of course, if you don't want to change the structure of your code, then you can do as @Saravana suggests and use a type assertion to just inform the type checker that is is definitely an Is, even though it technically isn't one until you're done building it.

Either way works, but I prefer the Object.assign() method because the type checker will warn you if you neglect to implement something:

// error, "undefined" is missing
const is: Is = Object.assign(
  (val: any) => typeof val,
  {
    null: (val: any) => (val === null)
    // ...
  }
);

while the type assertion method will not:

const is = ((val: any) => typeof val) as any as Is;
is.null = (val) => val === null;
// no error

whereas the type assertion method will not. It's up to you.


Playground link to code

Sign up to request clarification or add additional context in comments.

2 Comments

The Object.assign approach is just infinitely better than the type assertion version.
I don't get it... this answer just got accepted after four years, even though it and the question were obsolete since TypeScript 3.1. 🤷‍♂️ Oh well!
4

You cannot implement a function as well as its properties at the same time. You can define the function first and assert it to Is and define the rest of the methods:

const is = ((val: any) => typeof (val)) as any as Is;

is.null = (val) => true;
is.undefined = (val) => true;

Or use a factory function to create Is:

function createIs(): Is {
    const is = ((val: any) => {
        return "";
    }) as Is;
    is.null = (val) => true;
    is.undefined= (val) => true;
    return is;
}

const is: Is = createIs();

2 Comments

You should be able to use Object.assign() to create an Is with no type assertions.
@jcalz Object.assign is only available when the target version is es6 or higher.
2

All you need to do, is to provide the properties as optional:

type TypeQueryMethod = (val: any) => boolean;

interface Is {
    (val: any): string;
    undefined?: TypeQueryMethod;
    null?: TypeQueryMethod;
    ...
}

And now you can safely define it as Is:

const is: Is = value => ...

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.