Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: type assist for function argument

I've written a little library and now I'm trying to add type support for it through creating custom d.ts files.

To give an example (my library is called layerCompose):

const C = layerCompose(
  {
     _($) { /* function body */ },
     execute($) { /* function body */ }
  }
)
const c = C()
c._()

You will notice that both _ and execute function take a parameter $. This parameter (in our example) is an object with _ and execute properties (similar to this within a class)

So, I'd like to add type support to this $ parameter. Through experimenting I found that having this definition inside my d.ts file gives type support within the _ function.

export function layerCompose<
  T extends [
    A extends {} 
      ? {
        _($: {test: () => void})
      } 
      : never
  ]
  , A
>(...layers: T): lcConstructor<Spread<T>>

In other words Webstorm can see that $ parameter in _ function has type {test: () => void}

Obviously, that's not what I'm looking for. However, changing the signature to (what seems proper to me)

export function layerCompose<
  T extends [
    A extends {} 
      ? { 
        [K in keyof A]: ($: {test: () => void}) => any 
      } 
      : never
  ]
  , A
>(...layers: T): lcConstructor<Spread<T>>

and Webstorm looses the ability to tell that $ is an object with properties _ and execute.


EDIT: Included an example on typescript playground. It's missing the return type (non-trivial to set that up quickly), but otherwise gives an insight into what layerCompose approximately does.


EDIT 2:

Playing around further, having simplified to 1 parameter scenario, this works:

export function layerCompose<L1>(l1: {_: ($: {test: () => void}) => void} & L1): void

yet this does not:

export function layerCompose<L1>(l1: {[K in keyof L1]: ($: {test: () => void}) => void} & L1): void
like image 436
Anton Avatar asked Dec 07 '21 18:12

Anton


People also ask

How do you pass functions as TypeScript arguments?

Similar to JavaScript, to pass a function as a parameter in TypeScript, define a function expecting a parameter that will receive the callback function, then trigger the callback function inside the parent function.

What is the TypeScript type for a function?

In TypeScript, there are multiple syntaxes for declaring the type of a function: Method signatures. Function type literals. Object type literals with call/construct signatures.

What does () => void mean TypeScript?

Introduction to TypeScript void type The void type denotes the absence of having any type at all. It is a little like the opposite of the any type. Typically, you use the void type as the return type of functions that do not return a value.

What is @param in TypeScript?

Params enable methods to receive a variable number of parameters. Basically, if you are using params (... variable name) in TypeScript, the argument passed to the method are changed by the compiler into an element in a temporary array and this array is then used in the receiving methods.

What are the two parts of a function in typescript?

Introduction to TypeScript function types A function type has two parts: parameters and return type. When declaring a function type, you need to specify both parts with the following syntax: (parameter: type, parameter:type,...) => type

Can typescript infer the type of a type argument?

Specifying Type Arguments TypeScript can usually infer the intended type arguments in a generic call, but not always. For example, let’s say you wrote a function to combine two arrays: function combine < Type > (arr1: Type [], arr2: Type []): Type [] {

How do I assign a function to a variable in typescript?

Once annotating a variable with a function type, you can assign the function with the same type to the variable. TypeScript compiler will match the number of parameters with their types and the return type. The following example shows how to assign a function to the add variable:

What can typescript do for You?

Typescript can be incredibly helpful when we use some of its more advanced features. In this post, we specify the type of one function argument based on the type of another. Let’s say we have a change handler for an object’s properties. In plain JavaScript, this change handler might look something like this: Simple enough!


2 Answers

Is this what you're looking for?

export function layerCompose(...layers: Layer[]): lcConstructor<Spread<T>>;

Since you haven't defined neither lcConstructor or Spread I can't make any real guesses as to what the return type of layerCompose is supposed to be. So for the purpose of this answer those types have been replaced with the identity type.

// See playground below
type ComposedLayer = {
  _(): void;
  execute(): void;
}
type Layer = {
  _(v: Layer): void;
  execute(v: Layer): void;
}

/* dummy type */ type lcConstructor<T> = ComposedLayer;
/* dummy type */ type Spread<T> = T;

declare function layerCompose(...layers: Layer[]): lcConstructor<Spread<Layer>>;

const C = layerCompose(
  {
     _($) {},
     execute($) {
       $._({
         _($) {},
         execute($) {}
       })
     }
  }
);

C._();

playground

like image 182
Olian04 Avatar answered Oct 22 '22 01:10

Olian04


According personal experiences, find something as generic type first, to avoid circular dependencies. Please check following definitions.

type Layer<K extends string> = Partial<Record<K, ($: Record<K, Function>) => void>>

function layerCompose<K extends string>(...layers: Layer<K>[]): Record<K, Function>
  // ignore how do you implement to match types
  return {} as Record<K, Function>
}
like image 2
chinesedfan Avatar answered Oct 22 '22 02:10

chinesedfan