Typescript: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
 
(16 intermediate revisions by the same user not shown)
Line 970: Line 970:


</syntaxhighlight>
</syntaxhighlight>
=Tips For TypeScript=
=More Typescript Stuff=
==Guards==
Revisiting the transformation aspects of typescript gave me probably some repeated noted to help my tired old brain. These appeal to me because my brain struggles with the problem and the solution is obvious. I am trying to write these down so that I can get the pattern spotting in my brain correctly.
We can check a type by looking for a property and use the in Operator. E.g.
==No Enough Generics==
This a struggle, as they all are but the issue was, of course, not enough parameters
<syntaxhighlight lang="ts">
<syntaxhighlight lang="ts">
const printDocument = (doc: DelimitedDocument | PlaintextDocument) => {
import { Equal, Expect } from "../helpers/type-utils";
   if("seperator" in doc) {
 
    printDeliminated(doc)
const getValue = <TObj>(obj: TObj, key: keyof TObj) => {
  }
   return obj[key];
  else {
    printPlaintext(doc)
  }
};
};
</syntaxhighlight>
We can provide a property (__typename) in the types to determine the type and use a predicate. E.g.
<syntaxhighlight lang="ts">
export type = Invoice = FinalInvoice | DraftInvoice


export const isTypeFinalInvoice = (invoice: Invoice): invoice is FinalInvoice => {
const obj = {
  return invoice.__typename === "FinalInvoice";
  a: 1,
}
  b: "some-string",
export const isTypeDaftInvoice = (invoice: Invoice): invoice is DaftInvoice => {
  c: true,
  return invoice.__typename === "DaftInvoice";
};
}
</syntaxhighlight>
Test using asserts not assert
<syntaxhighlight lang="ts">
function assertIsNumber(val: any): asserts val is number {
    if(typeof val !== "number") { 
        throw new AssertionError("Not a number"
    }
)
</syntaxhighlight>
==Don't Use Enums==
Got this from Matt Pocock. Not a fan and preferred as const object e.g.
<syntaxhighlight lang="ts">
const LOG_LEVEL = {
    DEBUG: 'DEBUG',
    WARNING: 'Warning',
    ERROR: 'Error'
} as const


type LogLevel = ObjectValues<typeof LOG_LEVEL>
const numberResult = getValue(obj, "a");
const stringResult = getValue(obj, "b");
const booleanResult = getValue(obj, "c");


type tests = [
  Expect<Equal<typeof numberResult, number>>,
  Expect<Equal<typeof stringResult, string>>,
  Expect<Equal<typeof booleanResult, boolean>>
];
export {};
</syntaxhighlight>
</syntaxhighlight>
==Union Type==
All of the tests fail as the return type is whatever types are in the object. In this case, number, string and boolean. The test is expecting a specific type and the result is a union of number | string | boolean. To be more specific we need to add another generic argument which when you see it it is obvious. What was not obvious to me was the thought of adding more arguments to a solution. By default felt this would make it more complicated
The union type can be for instance
<syntaxhighlight lang="ts">
<syntaxhighlight lang="ts">
type a = true | "" | 123 | "456"
const getValue = <TObj, TKey extends keyof TObj>(obj: TObj, key: TKey) => {
  return obj[key];
};
</syntaxhighlight>
</syntaxhighlight>
They cannot have duplicates
==Wrapping a Api==
<syntaxhighlight lang="ts">
Sometimes we want to apply a generic to the functions are inferred with the correct type.
// type a = true | "" | 123 | "456" | "456"
<syntaxhighlight lang="ts" highlight="16,21">
</syntaxhighlight>
const useStyled = <TTheme = {}>(func: (theme: TTheme) => CSSProperties) => {
Conversion to Unions can be done using T[number]
  // Imagine that this function hooks into a global theme
<syntaxhighlight lang="ts">
  // and returns the CSSProperties
type TupleToUnion<T extends any[]> = T[number]
  return {} as CSSProperties;
 
};
type a = TupleToUnion<['', 123, '456','456', true,  '', '', '']>
 
// type a = true | "" | 123 | "456"
</syntaxhighlight>
==Constraining Types Array==
To constrain an array you can do
<syntaxhighlight lang="ts">
type myType = number | boolean | string
type TupleToUnion<T extends myType[] = T[number]
</syntaxhighlight>
But we can do the same with round parameters too. Round was the answer and not
<syntaxhighlight lang="ts">
// type TupleToUnion<T extends number | boolean | string[]> = T[number]
// type TupleToUnion<T extends <number | boolean | string>[]> = T[number]
 
type TupleToUnion<T extends (number | boolean | string)[]> = T[number]
</syntaxhighlight>
 
==Unions with  Types==
This example shows how to make a type which has known types and type which is a super set of these known types
<syntaxhighlight lang="ts">
// This will hide "sm" and "xs" in the autocomplete
type IconSize = "sm" | "xs" | string
// Better to do
type IconSize = "sm" | "xs" | Omit<string, "xs" | "sm">
</syntaxhighlight>
 
==Mapped As==
===Introduction===
Needed this for the omit example further down. This is where you can map a value as. For example
<syntaxhighlight lang="ts">
type Person = {
  name:string
  age: number
}
 
type Setters = {
  [K in keyof Person as `set${Capitalize<K>}`]: (value: State[K]) => void
}
 
// type Setters = {
//    setName: (value: string) => void
//     setAge:  (value: number) => void
// }
</syntaxhighlight>
===Using Intersection types with Mapped As===
Here we only want the string keys for the object because we cannot Capitialize with other types. By specifying the intersection only strings are allowed.
<syntaxhighlight lang="ts">
type Setters<Person> = {
  [K in keyof Person & string as `set${Capitalize<K>}`]: (value: State[K]) => void
}
</syntaxhighlight>
 
==Conditional Types==
We can breakdown complex types in to simple types. This is useful when the incoming data is more complex than we want
<syntaxhighlight lang="ts">
type Unarray<T> = T extends Array<infer U> ? U:T
 
type Release = Unarry<typeof backlog["releases"]>
type Epic = Unarray<Release["epics"]>


const backlog = {
interface MyTheme {
    releases: [
  color: {
      name: "Sprint 1",
    primary: string;
      epic: [
  };
      {
  fontSize: {
          name: "Account Management",
    small: string;
          tasks: [
  };
            { "name": "Single Sign on", Priority.mustHave },
            { "name": "Email Notification", Priority.mustHave },
          ]
      }
      ]
    ]
}
}
</syntaxhighlight>


==Infer Keyword==
const buttonStyle = useStyled<MyTheme>((theme) => ({
The infer keyword is used to represent the outcome on type if a generic. That is a bit of a mouthful so lets give an example. In this example R is a type to represent the return type.
   color: theme.color.primary,
<syntaxhighlight lang="ts">
   fontSize: theme.fontSize.small,
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
}));
</syntaxhighlight>
In this example R represents the type to for the promise
<syntaxhighlight lang="ts">
type PromiseReturnType<T> = T extends Promise<infer R> ? R : T
</syntaxhighlight>
==Some Key examples==
===Pick===
Picks keys to include in an Object
<syntaxhighlight lang="ts">
MyPick<T, K extends keyof T> { [k in K] : T[k] }
 
interface Todo {
   title: string
   description: string
  completed: boolean
}
 
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
 
const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}


const divStyle = useStyled<MyTheme>((theme) => ({
  backgroundColor: theme.color.primary,
}));
</syntaxhighlight>
</syntaxhighlight>
===Omit===
Passing the generic MyTheme can be tedious and lead to mistakes. We can solve this with a builder function. I.E. a function does this once for us.
Omits keys from an Object
<syntaxhighlight lang="ts" highlight="1-7, 18">
<syntaxhighlight lang="ts">
const makeUseStyled = <TTheme = {}>() => {
type MyOmit2<T, K extends keyof T> = { [P in keyof T as P extends K ? never: P] :T[P]}
  const useStyled = (func: (theme: TTheme) => CSSProperties) => {
    return {} as CSSProperties;
  };


interface Todo {
  return useStyled;
  title: string
    description: string
    completed: boolean
}
 
type TodoPreview = MyOmit<Todo, 'description' | 'title'>
 
const todo: TodoPreview = {
  completed: false,
}
</syntaxhighlight>
 
==Functional Overloads==
Did not know this was possible but it is<br>
[[File:Function Overload TS.jpg|500px]]
 
==Material UI==
This is new stuff probably should be with react
<syntaxhighlight lang="ts">
// This lets you omit variant as a Prop
import React from "react"
import {Button, ButtonProps } from "matierial-ui/core"
 
type Props = Omit<ButtonProps, "variant">
 
const BrandButton: React.FC<Props> = ({children, ...rest }) => {
  return <Button {...rest, children} />
};
};


export default BrandButton;
interface MyTheme {
</syntaxhighlight>
  color: {
=Typescript Challenges=
    primary: string;
==Introduction==
  };
This is a list of challenges to make a type the supports a question. [https://github.com/type-challenges/type-challenges challenges]. This was very difficult for me to understand, maybe dyslexia, maybe just me but what they are looking for is a type which supports the line highlighted.
   fontSize: {
==Example 1==
    small: string;
<syntaxhighlight lang="ts" highlight="7">
   };
interface Todo {
   title: string
  description: string
   completed: boolean
}
}


type TodoPreview = MyPick<Todo, 'title' | 'completed'>
export const useStyled = makeUseStyled<MyTheme>();
 
const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}
</syntaxhighlight>
===Example Left Side (Signature)===
So in this example we are look for a type on the left which = MyPick<Todo, 'title' | 'completed'>. Todo is a Type and 'title' and 'completed' are property  keys of that type. So maybe we look at this like a function. So the function signature we need to capture the Type and the keys.
<syntaxhighlight lang="ts">
type MyPick<T, K extends keyof T>
</syntaxhighlight>
</syntaxhighlight>
So T is the Type and K = keyof T will return a union of keys from T
Now the exported functions knows we are using MyTheme and we can now write without specifying MyTheme
<syntaxhighlight lang="ts">
<syntaxhighlight lang="ts">
type myType1 = {
const buttonStyle = useStyled((theme) => ({
    key1: string,
  color: theme.color.primary,
    key2: string,
  fontSize: theme.fontSize.small,
    key3: string
}));
}
 
type K = keyof myType1 // Returns type with values 'key1', 'key2' nad 'key3'
 
// So we can we can assigned these values
const myVar1: K = 'key1'
const myVar2: K = 'key2'
const myVar3: K = 'rubbish' // Error


const divStyle = useStyled((theme) => ({
  backgroundColor: theme.color.primary,
}));
</syntaxhighlight>
</syntaxhighlight>
===Right Side (Implementation)===
==Function Overloads==
So we need to make type
This probably was not new but seems new. For generics we had the say hello, wave goodbye - well almost. We solved this with generics.
<syntaxhighlight lang="ts">
<syntaxhighlight lang="ts">
MyPick<T, K extends keyof T>
import { expect, it } from "vitest";
</syntaxhighlight>
import { Equal, Expect } from "../helpers/type-utils";
Equal
<syntaxhighlight lang="ts">
const todo: TodoPreview = {
    title: 'Clean room',
    completed: false,
}
</syntaxhighlight>
So we need  to make an object that is a list (array) of keys title,completed and the a list (array) of values associated with that key.
<syntaxhighlight lang="text">
{
  array of keys : value for key for this type
  keys[]: values[]
}
</syntaxhighlight>
So give we have T as the type, K as the key we can do this
<syntaxhighlight lang="ts">
{
  [k in K] : T[k]
}
</syntaxhighlight>
==Example 2 Iterating over Keys==
Ok example 2 is to make a type which makes the all properties read-only
<syntaxhighlight lang="ts">
interface Todo {
  title: string
  description: string
}


const todo: MyReadonly<Todo> = {
function youSayGoodbyeISayHello<TGreeting extends "hello" | "goodbye">(
   title: "Hey",
   greeting: TGreeting,
   description: "foobar"
): TGreeting extends "hello" ? "goodbye" : "hello" {
   return (greeting === "goodbye" ? "hello" : "goodbye") as any;
}
}
</syntaxhighlight>
So the mistake I made with this was to think they wanted what they wanted before and so this would be the answer.
<syntaxhighlight lang="ts">
{
  readonly [k in K] : T[k]
}
</syntaxhighlight>
But this is wrong because the challenges tell you the arguments and in this challenge there is only one argument - the type, no list of keys. Read the question. Going with the left and the right
===Example Left Side (Signature)===
Define a type of name MyRead=Only and one argument '''not''' two.
<syntaxhighlight lang="ts">
type MyReadonly<T> =
</syntaxhighlight>


===Right Side (Implementation)===
it("Should return goodbye when hello is passed in", () => {
So the right-hand side is the same as the previous question except we do not have K. We don't care because we want all of the keys of type so.
  const result = youSayGoodbyeISayHello("hello");
<syntaxhighlight lang="ts">
{
  readonly [P in keyof T] : T[P]
}
</syntaxhighlight>


==Example 3 Iterating over Arrays transformation==
  type test = [Expect<Equal<typeof result, "goodbye">>];
<syntaxhighlight lang="ts">
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const


type result = TupleToObject<typeof tuple> // expected { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
  expect(result).toEqual("goodbye");
});


type TupleToObject<T extends readonly any[]> = any
it("Should return hello when goodbye is passed in", () => {
</syntaxhighlight>
   const result = youSayGoodbyeISayHello("goodbye");
===Example Left Side (Signature)===
So in this case the left hand side is '''almost''' provided see below
<syntaxhighlight lang="ts">
type TupleToObject<T extends readonly any[]> =
</syntaxhighlight>
===Right Side (Implementation)===
We need to, as ever identify the pattern which is result = array[0]: array[0], array[1]: array[1] ... array[n]: array[n]. So how do we access an array. We use an index. So
<syntaxhighlight lang="ts">
{  
   [P in T[number]] : P
}
</syntaxhighlight>
===Improving===
However in the challenge they have a test for a invalid assignment
<syntaxhighlight lang="ts">
type error = TupleToObject<[[1, 2], {}]>
</syntaxhighlight>
We need to stop the any in the left hand side. So lets put on some constraints. We constrain it to be an array.
<syntaxhighlight lang="ts">
type TupleToObject<T extends readonly (string | symbol | number)[] > = { [P in T[number]] : P }
</syntaxhighlight>
==Example 4 First Conditional Type==
<syntaxhighlight lang="ts">
  type arr1 = ['a', 'b', 'c']
  type arr2 = [3, 2, 1]
  type arr3 = []


  type test = [Expect<Equal<typeof result, "hello">>];


   type head1 = First<arr1> // expected to be 'a'
   expect(result).toEqual("hello");
  type head2 = First<arr2> // expected to be 3
});
  type head3 = First<arr3> // expected to be never
 
  type First<T extends any[]> = any
</syntaxhighlight>
</syntaxhighlight>
===Example Left Side (Signature)===
For Function overloads this is a lot easier. It seems more like function restrictions to me as the implementation is an amalgamation of the possible options and the function signatures are the permissible types
So left side was provided. In the examples we could be more specific to specify string | number for the array but we will see why not in the right hand side
<syntaxhighlight lang="ts">
<syntaxhighlight lang="ts">
type First<T extends any[]>
function youSayGoodbyeISayHello(greeting: "goodbye"): "hello";
</syntaxhighlight>
function youSayGoodbyeISayHello(greeting: "hello"): "goodbye";
===Right Side (Implementation)===
This is the first conditional type. For this we use the extends keyword which is in the form of SomeType extends OtherType ? x : y and defined by<br>
<syntaxhighlight lang="text">
If SomeType extends another given type OtherType, then ConditionalType is TrueType, otherwise it is FalseType
</syntaxhighlight>
This confused me at first as arr3 = [] is true and  arr2 = [3, 2, 1] is false. But this is because we are actually comparing
<syntaxhighlight lang="ts">
type answer1 = [] extends []            ? 'true' : 'false' // true
type answer2 = [] extends never[]      ? 'true' : 'false' // true
type answer3 = [1,2,3] extends number[] ? 'true' : 'false' // true
type answer4 = [1,2,3] extends []      ? 'true' : 'false' // false
</syntaxhighlight>
So this is why this works. I am a better type script person for it. And the answer there is
<syntaxhighlight lang="ts">
T extends [] ? never : T[0]
</syntaxhighlight>
==Example 5 typeof==
I do not know how this one works.
<syntaxhighlight lang="ts">
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
 
type teslaLength = Length<tesla>  // expected 4
type spaceXLength = Length<spaceX> // expected 5
 
type Length<T> = any
</syntaxhighlight>
===Example Left Side (Signature)===
This seemed easy and correct but it is the test cases I struggled with
<syntaxhighlight lang="ts">
type Length<T extends []>
</syntaxhighlight>
===Right Side (Implementation)===
This is what I came up with but the subsequent test cases made it not the right answer.
<syntaxhighlight lang="ts">
type Length<T extends string[]> = T['length']
</syntaxhighlight>
===Answer===
So the tests cases which failed were
<syntaxhighlight lang="ts">
  Expect<Equal<Length<typeof tesla>, 4>>,
  Expect<Equal<Length<typeof spaceX>, 5>>,
</syntaxhighlight>
Clearly we need to make the typeof bit work. So it appears that adding the readonly makes the type the first element in the array.
<syntaxhighlight lang="ts">
type Length<T extends readonly any[]> = T['length']
</syntaxhighlight>
==Example 6 Conditional Type 2==
This one is where I hoped to answer my first one without looking but sadly not
<syntaxhighlight lang="ts">
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
 
type MyExclude<T, U> = any
</syntaxhighlight>
But this is asking can you output types T which are not in U. We need to remember never mean omit
<syntaxhighlight lang="ts">
type MyExclude<T, U> = T extends U ? never : T
</syntaxhighlight>
==Example 7 Infer==
Infer allows you to name the thing you extracted and is used with conditional types.
<syntaxhighlight lang="ts">
 
  type ExampleType = Promise<string>
  type Result = MyAwaited<ExampleType> // string
 
// Make a type for this
type MyAwaited<T> = any
 
// Which solves the following
type X = Promise<string>
type Y = Promise<{ field: number }>
type Z = Promise<Promise<string | number>>
type Z1 = Promise<Promise<Promise<string | boolean>>>
type T = { then: (onfulfilled: (arg: number) => any) => any }
 
type cases = [
  Expect<Equal<MyAwaited<X>, string>>,
  Expect<Equal<MyAwaited<Y>, { field: number }>>,
  Expect<Equal<MyAwaited<Z>, string | number>>,
  Expect<Equal<MyAwaited<Z1>, string | boolean>>,
  Expect<Equal<MyAwaited<T>, number>>,
]
</syntaxhighlight>
This might get tricky as the answer came from [[https://www.youtube.com/watch?v=wON6MCS0NkE&list=PL_L_J_Lv0U2olJYiw2lI7SZCQrWuxmZto&index=1 youtube]]. So the first thing was to identify that from the tests they all take a generic parameter (X, Y, Z, Z1 and T). Looking at the types they are all Promises (except T) so lets make the generic and unknown promise
<syntaxhighlight lang="ts">
type MyAwaited<T extends Promise<unknown>> = any
</syntaxhighlight>
Now we can use the infer keyword. This will solve the first two test cases X and Y.
<syntaxhighlight lang="ts">
type MyAwaited<T extends Promise<unknown>> = T extends Promise<infer V> ? V : never
</syntaxhighlight>
For the next two cases Z and Z1 this is not resolved because they unwrap promises within promises, the first one promise, the second two promises, luckily we have type to unwrap a promise so we can call this recursively. We check if the value returned from the inter is a Promise<unknown>, if it is we use our type to remove the Promise, if it isn't we return our value.
<syntaxhighlight lang="ts">
type MyAwaited<T extends Promise<unknown>> =
        T extends Promise<infer V> ?
          (V extends Promise<unknown> ? MyAwaited<V>: V) :
          never 
</syntaxhighlight>
I could not solve the final test but in the end found an answer where they used the recursive calling of then.
<syntaxhighlight lang="ts">
type MyAwaited<T> = T extends { then: (onfulfilled: (arg: infer U) => unknown) => unknown; }
  ? MyAwaited<U>
  : T;
</syntaxhighlight>
Would have like to have seen the equivalent solution for this.
==Example 8 Recursion II==
This is apparently easy. So depressed doing these but youtube had the answer [[https://www.youtube.com/watch?v=ipwWXQQFTPU youtube]] and explained here because I need it explaining. This was the question, write an includes function.
<syntaxhighlight lang="ts">
type isPillarMen = Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'> // expected to be `false`
</syntaxhighlight>
I did actually know the answer for the first attempt but my it was not enough.
<syntaxhighlight lang="ts">
type Includes<T extends any[], U> = U extends T[number] ? true: false
 
// Many of the tests passed
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Kars'>, true>>,
Expect<Equal<Includes<['Kars', 'Esidisi', 'Wamuu', 'Santana'], 'Dio'>, false>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 7>, true>>,
Expect<Equal<Includes<[1, 2, 3, 5, 6, 7], 4>, false>>,
Expect<Equal<Includes<[1, 2, 3], 2>, true>>,
Expect<Equal<Includes<[1, 2, 3], 1>, true>>,
 
// But many did not
Expect<Equal<Includes<[{}], { a: 'A' }>, false>>,
Expect<Equal<Includes<[boolean, 2, 3, 5, 6, 7], false>, false>>,
Expect<Equal<Includes<[true, 2, 3, 5, 6, 7], boolean>, false>>,
Expect<Equal<Includes<[{ a: 'A' }], { readonly a: 'A' }>, false>>,
Expect<Equal<Includes<[{ readonly a: 'A' }], { a: 'A' }>, false>>,
Expect<Equal<Includes<[1], 1 | 2>, false>>,
Expect<Equal<Includes<[1 | 2], 1>, false>>,
</syntaxhighlight>
The issue is with how to compare. If we look at this test case, test is equal to true and therefore fails.
<syntaxhighlight lang="ts">
type test = Includes<[boolean, 2, 3, 5, 6, 7], false> // true
</syntaxhighlight>
We need to use something better to compare. He googled and found this
<syntaxhighlight lang="ts">
type test2 = Equal<boolean, false> // false
</syntaxhighlight>
So now all we need to do is to iterate using our new approach to compare. But first a reminder. With type script we can obtain the first or second element and the rest using the spread operator. This is the same for types
<syntaxhighlight lang="ts">
let [first, ...rest] = [1, 2, 3, 4];
let [first,second ...rest] = [1, 2, 3, 4];
</syntaxhighlight>
So what we do below is we separate the first element in First off from the Rest of the elements. If equal all good, if not call recursively with the rest until there are no more elements. One last thing, the youtuber also thought this was not a simple challenge
<syntaxhighlight lang="ts">
type Includes<T extends any[], U> = T extends [infer First, ...infer Rest] ?
  (Equal<U, First> extends true  ? true : Includes<Rest, U>) : false
</syntaxhighlight>
==Example 9 Parameters==
So Given
<syntaxhighlight lang="ts">
const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: { a: 'A' }): void => {}
const baz = (): void => {}
const buz = (arg1: number): number => { return 0}
 
type cases = [
  Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
  Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,
  Expect<Equal<MyParameters<typeof baz>, []>>,
  Expect<Equal<MyParameters<typeof buz>, [number]>>,
]
 
// Solve
type MyParameters<T extends (...args: any[]) => any> = any
</syntaxhighlight>
So we are passing (): void => {} so we should be able to capture the input as '''(...args: T) => any'''
<syntaxhighlight lang="ts">
type MyParameters<T> =  T extends ( (...args: infer U) => any ) ? U : never
</syntaxhighlight>
==Example 9 Parameters==
So Given
<syntaxhighlight lang="ts">
const foo = (arg1: string, arg2: number): void => {}
const bar = (arg1: boolean, arg2: { a: 'A' }): void => {}
const baz = (): void => {}
const buz = (arg1: number): number => { return 0}
 
type cases = [
  Expect<Equal<MyParameters<typeof foo>, [string, number]>>,
  Expect<Equal<MyParameters<typeof bar>, [boolean, { a: 'A' }]>>,
  Expect<Equal<MyParameters<typeof baz>, []>>,
  Expect<Equal<MyParameters<typeof buz>, [number]>>,
]
 
// Solve
type MyParameters<T extends (...args: any[]) => any> = any
</syntaxhighlight>
So we are passing (): void => {} so we should be able to capture the input as '''(...args: T) => any'''
<syntaxhighlight lang="ts">
type MyParameters<T> =  T extends ( (...args: infer U) => any ) ? U : never
</syntaxhighlight>
==Example 10 Return Value==
Possibly getting easier. Copying is getting quicker and now I used my learning today so not a waste.
<syntaxhighlight lang="ts">
const fn = (v: boolean) => {
    if (v)
      return 1
    else
      return 2
  }
 
type MyReturnType<T> = any
 
type cases = [
  Expect<Equal<string, MyReturnType<() => string>>>,
  Expect<Equal<123, MyReturnType<() => 123>>>,
  Expect<Equal<ComplexObject, MyReturnType<() => ComplexObject>>>,
  Expect<Equal<Promise<boolean>, MyReturnType<() => Promise<boolean>>>>,
  Expect<Equal<() => 'foo', MyReturnType<() => () => 'foo'>>>,
  Expect<Equal<1 | 2, MyReturnType<typeof fn>>>,
  Expect<Equal<1 | 2, MyReturnType<typeof fn1>>>,
]
 
</syntaxhighlight>
If we look at the examples for the test cases and switching them around we see a pattern
<syntaxhighlight lang="ts">
MyReturnType<() => string
MyReturnType<() => 123
MyReturnType<() => Promise<boolean>
MyReturnType<() => () => 'foo'


MyReturnType<typeof fn>
function youSayGoodbyeISayHello(greeting: "goodbye" | "hello") {
MyReturnType<typeof fn1>
   return greeting === "goodbye" ? "hello" : "goodbye";
</syntaxhighlight>
The last two look slightly different but they are not. typeof fn just means
<syntaxhighlight lang="ts">
fn() => number
fn1() => number
</syntaxhighlight>
So the answer is
<syntaxhighlight lang="ts">
type MyReturnType<T extends Function> =
  T extends (...args: any) => infer R
    ? R
    : never
</syntaxhighlight>
==Example 11 MyReadonly2==
<syntaxhighlight lang="ts">
interface Todo1 {
  title: string
  description?: string
  completed: boolean
}
 
interface Todo2 {
  readonly title: string
  description?: string
  completed: boolean
}
 
interface Expected {
  readonly title: string
  readonly description?: string
  completed: boolean
}
 
Expect<Alike<MyReadonly2<Todo1>, Readonly<Todo1>>>,
Expect<Alike<MyReadonly2<Todo1, 'title' | 'description'>, Expected>>,
Expect<Alike<MyReadonly2<Todo2, 'title' | 'description'>, Expected>>,
Expect<Alike<MyReadonly2<Todo2, 'description' >, Expected>>,
 
type MyReadonly2<T, K> = any
 
</syntaxhighlight>
This was though. The first thing to notice is the first test case. For this we need to remember we can have default arguments in typescript
<syntaxhighlight lang="ts">
type MyReadonly2<T, K extends keyof T = keyof T> = any
</syntaxhighlight>
Next we need to understand intersection which is the & sign. This will combine the sets in the braces.
<syntaxhighlight lang="ts">
type MyReadonly2<T, K extends keyof T = keyof T> =
  { SET A } & 
   { SET B }
</syntaxhighlight>
Set A is all of the keys specified.
<syntaxhighlight lang="ts">
type MyReadonly2<T, K extends keyof T = keyof T> =  
  { readonly [P in K]      : T[P] } &
  { SET B }
</syntaxhighlight>
Set B is just excluding the value which are not in K. This is the same as the Omit. Understand the Mapped As and this is easy
<syntaxhighlight lang="ts">
type MyReadonly2<T, K extends keyof T = keyof T> =
  { readonly [P in K]      : T[P] } &
  {          [P in keyof T as P extends K ? never : P]: T[P] }
</syntaxhighlight>
==Example 12 Deep Readonly and Recursion==
This was going so well as I understood recursion but needed something like
  { if !object ?  readonly [P in keyof T]: DeepReadonly<T[P]> : DeepReadonly< T[P]> }
<syntaxhighlight lang="ts">
type X1 = {
  a: () => 22
  b: string
  c: {
    d: boolean
    e: {
      g: {
        h: {
          i: true
          j: 'string'
        }
        k: 'hello'
      }
      l: [
        'hi',
        {
          m: ['hey']
        },
      ]
    }
  }
}
 
type Expected1 = {
  readonly a: () => 22
  readonly b: string
  readonly c: {
    readonly d: boolean
    readonly e: {
      readonly g: {
        readonly h: {
          readonly i: true
          readonly j: 'string'
        }
        readonly k: 'hello'
      }
      readonly l: readonly [
        'hi',
        {
          readonly m: readonly ['hey']
        },
      ]
    }
  }
}
</syntaxhighlight>
So the extends never says is it not enumerable so we have out solution almost...
<syntaxhighlight lang="ts">
type DeepReadonly<T> = keyof T extends never
  ? T // Object
  : { readonly [k in keyof T]: DeepReadonly<T[k]> }; // Not an Object
</syntaxhighlight>
Almost because of course there are many things we can put in an object, functions or maybe we have not an object but a union
<syntaxhighlight lang="ts">
type X2 = { a: string } | { b: number }
 
type Expected2 = { readonly a: string } | { readonly b: number }
</syntaxhighlight>
Like a lot of these you can go on youtube and get the answer from Michigan Typescript. They actually used the answer above but below is what solved this completely for me. So the author decided to look at the value not the key. i.e. the right hand side. If this is not enumerable
<syntaxhighlight lang="ts">
type DeepReadonly<T> = {
  readonly [P in keyof T]:
    keyof T[P] extends never
    ? T[P]
    : DeepReadonly<T[P]>
}
}
</syntaxhighlight>
</syntaxhighlight>

Latest revision as of 22:56, 6 November 2024

Introduction

TypeScript is a typed language which produces javascript.

e.g.

let myString = "fred";
let myBoolean = true;

function createMessage(name:string) {

}

Typescript supports classes and access modifiers

class Person {
   
   name: string
   lastName: string
   
   public Person(name:string) {
       this.name = name;
   }

   public void setLastName(lastName: string) {
       this.lastName = lastName;
   }
}

Configuration

tsconfig

You can set the options for the compiler you can specify a tsconfig.json file. By using

tsc --init

you get a default file.

You can inherit tsconfigs from parent directories. This compiles all *.ts files in this directory and child directories.

{
  "extends": "../tsconfig.base",
  "compilerOptions": {
    "removeComments": true
  },
  "include": [
    "./**/*.ts"
  ]
}

Webpack Configuration

The ts-loader module allows recompiling of the type script and you need to install it if using.

module.exports = {
  entry: './app/app.ts',
  devtool: 'inline-source-map'
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: ['.tsx', '.ts', 'js']
  },
  output: {
    filename: 'bundle.js'
  },
  devServer: {
    inline: false
  }
};

Data Types

No more var

Don't use var but instead use

let

or

const

Base Data Types

The following types are available

  • Boolean
  • Number
  • String
  • Array
  • Enum (not in javascript e.g. enum Category {biology, Poetry, Fiction})
  • Tuple (e.g. let myTuple: [number, string] = [25,'truck'] not other elements can have only number of string e.g. myTuple[2] = 'fred')

Other types

  • void
  • null
  • undefined
  • Never (e.g. for infinite loop return types)
  • Any (e.g. for when using types not guaranteed from other libraries)

Union types This allows more than type e.g.

let number_string : string | number

Not good if you ask me. However maybe useful for strings e.g.

let null_string : string | null

By default the null is not allowed to be assigned without a union declaration.

Type assertions

You can assert types in one of two ways

let value: any = 5;

let fixedString: string =  (<number>value).toFixed(4);
console.log(fixedString); // 5.0000

or

let fixedString: string =  (value as number).toFixed(4);

Functions

Adding types

With typescript we can specify types e.g.

function funFunc(score: number, message1: string = "default ", message2?: string): string {
   return message1 + message2;
}

The ? means the parameters is option and the final colon shows the return value of the function

Arrow Functions or lamdas

These take the format of

parameters  => function body

e.g. For zero parameters

 let greeting = () =>  console.log("Hello world");
 greeting(); // Hello world

For 1 parameter

 let squareit = x => x * x;
 let result = squareit(4); // 16

For multiple parameters

 let adder = (a,b) = a + b;
 let sum = adder(2,3); // 5

The example below shows a function on array (filter) which takes a function as an argument where the arguments are element, index and original array.

var scores = [70,125,85,110, 10000];

var highscores = scores.filter((element, index, array) => {
    var result = false
    if (index === 0) {
        console.log("arrrrayyy", array)
        result = true;
    }
	if(element > 100) {
  	    result = true;
    }
    return result;
});

console.log("test");
console.log("iain", highscores);

Another example,

Without arrow function

function Book() {
  let self = this;
  self.publishDate = 2016;
  setInterval(function() {
    console.log(self.publishDate);
  }, 1000)  
}

With arrow function

function Book() {
  this.publishDate = 2016;
  setInterval(() = > {
    console.log(this.publishDate);
  }, 1000)  
}

function types (delegates)

You can assign functions with the same signatures to variables with typescript. E.g.

function logError(err: string) : void {
   console.error(err);
}

function logLog(err: string) : void {
   console.log(err);
}

let logger : (value: string) => void;

if(x === 1)
{
  logger = logError;
}
else
{
  logger = logLog;
}

logger('Score: ${x}');

Rest Parameters (params or variadic)

Example below

function GetBooksReadForCustomer(name: string, ...bookIDs: number[]) {

}

let books = GetBooksReadForCustomer('Bob', 1,2,3);

Function Overloads

You can declare several overloads for a function but implement just once. Not quite sure of the benefit but there you go.

function GetTitles(author: string) string[];
function GetTitles(author: boolean) string[];

function GetTitles(author: any) string[] {

  if(typeof bookProperty == 'string') {
    // do stuff
  }
  else if(typeof bookProperty == 'boolean') {
    // do stuff
  }
  return 'stuff';  
}

Custom types

Typescript supports classes and interfaces

Interfaces

Standard

Basic interfaces

interface Employee {
  name: string;
  title: string;
}

interface Manager extends Employee {
  department : string;
  numberOfEmployees: number;

  scheduleMeeting: (topic: string) => void;
}

let developer = {
  name: 'iain',
  title: 'GDB',
  editor: 'Visual Studio Code'
}

let newEmployee: Employee = developer;

Interface for Function types

Combining with function types

// Simple function
function CreateCustomerID(name: string, id: number): string {
  return name + id;
}

// Define an interface
interface StringGenerator {
  (chars: string, nums: number): string;
}

// Old way
let IdGenerator: (chars: string, nums: number) => string;
IdGenerator = CreateCustomerID;

// Improved way
let IdGenerator = StringGenerator;

Example

interface DamageLogger {
  (damage: string) : void;
}

let logDamage: DamageLogger;
logDamage = (damage: string) => console.log('Damage reported: ' + damage);
logDamage('coffee stains'); // Damage reported: coffee stains

Classes

Basic Stuff

Example below, default access is public

class Developer {

  department: string;
  private _title: string;
  
  get title(): string {
    return this._title;
  }

  set title(newTitle: string) {
    this._title = newTitle.toUppperCase();
  }

  // Static members and attributes exist
  static could_be_a_const: string = 'Hello me';
  static logMe() {
      console.log('Hello');
  }
}

// Extending
class WebDeveloper extends Developer {

   readonly favoriteEditor: string
   constructor(editor: string) {
      super();
      this.favoriteEditor = editor;
   }
}

// Abstract
class MyClass {

   abstract printStuff(): void;
}

Non C# Stuff

Initialise attribute without type

// C# ish
class Author {
  name: string;
  constructor(inName: string) {
     name = inName;
  }
}

// Typescript
class Author {
  constructor(public name: string) {
  }
}

Class Expression

You can create an expression of a class. E.g. implement an abstract on on the fly.

let Newspaper = class extends ReferenceItem {
  ImplementationOfAbstract: void {
    console.log('I am implemented now');
  }
}

let myPaper = new Newspaper('The Gazette', 2016);
myPaper.ImplementationOfAbstract();

Importing

To import typescript classes you can use the Triple-slash directive

/// <reference path="player.ts" />

Generics

Array

Array is a built in Generic e.g.

let Books : Book[]
// With Generic Array
let Books : Array<Book>

Functions

Much the same as c#

function LogAndReturn<T>)thing : T) : T {
   console.log(thing);
   return thing;
}

let someString : string = LogAndReturn<string>('log this');

Interfaces and Classes

Much the same as c# as well

interface Inventory<T> {
  getNewestItem:() => T;
  addItem: (newItem: T) => void;
  getAllItems: () => Array<T>;
}

class Catalog<T> implements Inventory<T> {
   private catalogItems = new Array<T>();
   addItem(newItem: T)_ {
     this.catalogItems.push(newItem);
   }
...
}

let bookCatalog = new Catalog<Book>();

Constraints

This is just for typescript I think

class Catalog<T extends CatalogItem> implements Inventory<T> {

  // Only types satisfying the extends constraint CatalogItem 
  // are allowed at compile time. Seems a bit constraining to me.
}

TypeScript Declaration Files

These are typescript wrappers for JavaScript libraries. This allows the typescript compiler to validate your usage.

These will have the extension .d.ts and you can find these on GitHub at definitely typed. Note these may sometimes be out of date.

Search here

npm allows you to install these using

npm install --save @types/lodash


Advanced

Destructuring

Like javascript

let medals : string[] = ['gold', 'silver', 'bronze']
let [first, second, third] = medals;

let person = {
  name: 'Audrey',
  address: '123 Main St',
  phone: '555:1212'
}

let {name, address, phone} = person

Spread Operator

Like javascript

Additional to other array

let newBookIDs = [10,20]
let allBookIDs = [1,2,3, ...newBookIDs] // 1,2,3,10,20

Intersection types

// Previously we had union types  on functions e.g.
function test(inArg : number | string) : void {

}

// Now we have Intersection types where all the members
// of the types are combined
function test() : Book & Magazine {
 
}

// So without publish which is a member of magazine it 
// will not compile as it checks all members exist
let serialNovel: Book & Magazine = {

  id; 100,
  title: 'The Gradual Tale',
  author: 'Occasional Pen'
  // publisher: 'Serial Press'
}

Mixins

Not sure if this is worthwhile. It seems to be a form of multiple inheritance similar to C++ which most people hate. The key thing is the applyMixins which copies the functions from the base classes to the new class and is "Magic"

// Disposable Mixin
class Disposable {
    isDisposed: boolean;
    dispose() {
        this.isDisposed = true;
    }

}

// Activatable Mixin
class Activatable {
    isActive: boolean;
    activate() {
        this.isActive = true;
    }
    deactivate() {
        this.isActive = false;
    }
}

class SmartObject {
    constructor() {
        setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
    }

    interact() {
        this.activate();
    }
}

interface SmartObject extends Disposable, Activatable {}
applyMixins(SmartObject, [Disposable, Activatable]);

let smartObj = new SmartObject();
setTimeout(() => smartObj.interact(), 1000);

////////////////////////////////////////
// In your runtime library somewhere
////////////////////////////////////////

function applyMixins(derivedCtor: any, baseCtors: any[]) {
    baseCtors.forEach(baseCtor => {
        Object.getOwnPropertyNames(baseCtor.prototype).forEach(name => {
            Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name));
        });
    });
}

String Literal Types and Type Aliases

// Like enums
let empCategory: 'Manager' | 'Non-Manager'

// Type aliases
type EmployeeCategory = 'Manager' | 'Non-Manager'

Advanced Type Features

Polymorphic this

The this is referring to the type returned. e.g.

class Vehicle {
   Drive() {
      return this
   }
}

class Car extends Vehicle {
   CarryPeople() {
      return this
   }
}

class Truck extends Vehicle {
   CarryCargo() {
      return this
   }
}


let t = new Truck();
t.Drive() // returns a Truck object

Basically we are to understand that the this is the this of the type originally declared not the this of the function in this case Vehicle.

Declaration Merging

This looks like bad news. You can merge things which you declare without saying you are doing it but by just clashing with names e.g.

interface Book {
  id: number,
  author: string,
  category: Category
}

// By typing another they are merged by default

interface Book {
  identifier: number,
  writer: string
}

Maybe this is a better example where an extension is built on an existing class

import {UniversityLibrarian} from './classes'

declare module './classes' {
  interface UniversityLibrarian {
    phone: string;
    hostSeminar(topic: string): void;
  }
}

UniversityLibrarian.prototype.hostSeminar = function(topic) {
  console.log('Hosting a seminar on ' + topic)
}

Type Guards

typeof type guard

Protect code for correct type

if ( typeof x === 'string')
{
}
else if ( typeof x === 'number')
{
}

Allows types are

  • string
  • number
  • boolean
  • symbol

Custom type guard

You can write your own using

function isBook(text: Book | Magazine) : text is Book {
   return (<Book>text).author !== undefined
}

Symbols

The data type symbol is a primitive data type. The Symbol() function returns a value of type symbol, has static properties that expose several members of built-in objects, has static methods that expose the global symbol registry, and resembles a built-in object class, but is incomplete as a constructor because it does not support the syntax "new Symbol()".

let mySymbol = Symbol('first_symbol');

const CLASS_INFO = Symbol();

class myClass 
{
  [CLASS_INFO](): void {
     console.log('This is my class');
  }

  static [Symbol.hasInstance](obj: Object) : boolean {
       ...
  }
}

let aClass = new myClass(0;
aClass[CLASS_INFO]() // This is my class

May need to do some reading on this

Decorators

Introduction

// Class decorator
// target = Constructor function for the class
function ui_element(target: Function) { // do ui stuff}

// Method decorator
// Parameters are
// t = constructor function for a static method 
       or prototype for the class if it is an instance member
// p = name of the decorated member
// d = Property descriptor for the member
function my_deprecated(t: any, p: string, d: PropertyDescriptor)
{
  console.log('This method will go away soon...');
}

@ui_element
class {

 @my_deprecated
 someOldMethod() { }
  
}

// Decorator Factories
function ui_element(element: string) {
  return function(target: Function) {
    console.log('Create new element : ${element}');
  }
}

// Usage
@ui_element('Simple Form')
class ContactForm {

 // contact properties
}


Class Decorator

This is the signature for a class decorator

// ClassDectorator Type
<TFunction extends Function>(target: TFunction) => TFunction | void


Example where the constructor is not replaced

export function sealed(name: string ) {

  return function(target: Function): void {
    console.log('Sealing the constructor: ${name}')
    Object.seal(target);
    Object.seal(target.prototype)
  }
}

@sealed('Class Library')
class Boris {

}

Example where the constructor is replaced

export function logger<TFunction extends Function>(target: TFunction): TFunction {

  // Create a Function type
  let newConstructor: Function = function() {
    console.log('Creating new instance')
    console.log(target);
  }

  // Assign protype and constructor from original
  newConstructor.prototype = Object.create(target.prototype);
  newConstructor.prototype.constructor = target;

  // Return the new function
  return <TFunction>newConstructor;
}


Method Decorator

Example

export function readOnly(target : Object,
                         propertyKey: string,
                         descriptor: PropertyDescriptor) {

  console.log('Setting ${propertyKey}.');
  descriptor.writable = false;
}

// Changing to a factory decorator // i.e. remove export and replace with return, remove function name

export function writable(isWritable : boolean) {
   return function (target : Object,
                    propertyKey: string,
                    descriptor: PropertyDescriptor) {

     console.log('Setting ${propertyKey}.');
     descriptor.writable = isWritable;
   }
}

..

class aClass
{
   @writable(false);
   testMethod() :void {
     console.log("I am a test method")
   }
}

..

Asyncronous Calls

Callback functions

// Create Interface (not required by nicer
interface LibMgrCallBack {
  // pararameters : return args
  (err: Error, titles: string[]) : void
}

function getBooksByCategory(cat: Category, inCallBack: LibMgrCallBack) : void {

  // Fake function
  setTimeout( () => {

    try {

      let foundBooks: string[] = Util.GetBooks(cat);
      if(foundBooks.length > 0) { 
         callback(nuT^ll, foundBooks);
      } 
      else {
         throw new Error('No Books Found');
      }
    }
    catch(error) {
    }

  }, 2000);
}

function logCategorySearch(err: Error, titles: string[]) : void {
  if(err) {
     console.log('Error Message: ${err.message}');
  }
  else {
     console.log('Found following titles');
     console.log(titles);
  }
}

console.log('Begin')'Found titles: ${titles}'))
getBooksByCategory(Category.Fiction, logCategorySearch);
console.log('Submmitted')

Promises

So,

  • Requires 2015
  • Similar to Tasks in c#.
  • You can chain promises togethe'Found titles: ${titles}'))r
  • Simple API, then, catch
function doAsyncWork(resolve, reject) {

   // Perform Async work
   if(success) resolve(data)'Found titles: ${titles}'))
   else reject(reason)
}

let p: Promise<string> = new Promise(doAsyncWork);

// Alternate and more realistic
let p: Promise<string> = new Promise( (resolve, reject) => {
   // Perform Async work
   if(success) resolve(data)
   else reject(reason)
})

Taking callback example

function getBooksByCategory(cat: Category): Promise<string[]> {
   
  let p: Promise<string[]> = new Promise((resolve, reject) => {

    setTimeout( () => {

      let foundBooks: string[] = Util.GetBooks(cat);

      if(foundBooks.length > 0) { 
         resolve(foundBooks);
      } 
      else {
         reject('No Books Found');
      }

    }, 2000);

  });
}

console.log('Begin')

getBooksByCategory(Category.Fiction)
 .then(
  titles => {
     console.log('Found titles: ${titles}');
     throw 'something bad happened'; // Force exception
     return titles.length;
  }, reason = { return 0;})
 .then(numOfBooks => console'Found titles: ${titles}')).log('Number Of Books found: ${numOfBooks}')) // Chained
 .catch(reason => console.log('Found titles: ${reason}'))

console.log('Beginning')
logSearchResults(Category.Fiction);
console.log('Submitted')

async await

Example

async function doAsyncWork() {
  let results = await GetLongTask();
  console.log(results)
}

Taking promise example

async function logSearchResult(bookCategory: Category) {
  let foundBooks = await getBooksByCategory(bookCategory)
  console.log(foundBooks)
}


console.log('Beginning')
logSearchResults(Category.Fiction);
console.log('Submitted')

More Typescript Stuff

Revisiting the transformation aspects of typescript gave me probably some repeated noted to help my tired old brain. These appeal to me because my brain struggles with the problem and the solution is obvious. I am trying to write these down so that I can get the pattern spotting in my brain correctly.

No Enough Generics

This a struggle, as they all are but the issue was, of course, not enough parameters

import { Equal, Expect } from "../helpers/type-utils";

const getValue = <TObj>(obj: TObj, key: keyof TObj) => {
  return obj[key];
};

const obj = {
  a: 1,
  b: "some-string",
  c: true,
};

const numberResult = getValue(obj, "a");
const stringResult = getValue(obj, "b");
const booleanResult = getValue(obj, "c");

type tests = [
  Expect<Equal<typeof numberResult, number>>,
  Expect<Equal<typeof stringResult, string>>,
  Expect<Equal<typeof booleanResult, boolean>>
];
export {};

All of the tests fail as the return type is whatever types are in the object. In this case, number, string and boolean. The test is expecting a specific type and the result is a union of number | string | boolean. To be more specific we need to add another generic argument which when you see it it is obvious. What was not obvious to me was the thought of adding more arguments to a solution. By default felt this would make it more complicated

const getValue = <TObj, TKey extends keyof TObj>(obj: TObj, key: TKey) => {
  return obj[key];
};

Wrapping a Api

Sometimes we want to apply a generic to the functions are inferred with the correct type.

const useStyled = <TTheme = {}>(func: (theme: TTheme) => CSSProperties) => {
  // Imagine that this function hooks into a global theme
  // and returns the CSSProperties
  return {} as CSSProperties;
};

interface MyTheme {
  color: {
    primary: string;
  };
  fontSize: {
    small: string;
  };
}

const buttonStyle = useStyled<MyTheme>((theme) => ({
  color: theme.color.primary,
  fontSize: theme.fontSize.small,
}));

const divStyle = useStyled<MyTheme>((theme) => ({
  backgroundColor: theme.color.primary,
}));

Passing the generic MyTheme can be tedious and lead to mistakes. We can solve this with a builder function. I.E. a function does this once for us.

const makeUseStyled = <TTheme = {}>() => {
  const useStyled = (func: (theme: TTheme) => CSSProperties) => {
    return {} as CSSProperties;
  };

  return useStyled;
};

interface MyTheme {
  color: {
    primary: string;
  };
  fontSize: {
    small: string;
  };
}

export const useStyled = makeUseStyled<MyTheme>();

Now the exported functions knows we are using MyTheme and we can now write without specifying MyTheme

const buttonStyle = useStyled((theme) => ({
  color: theme.color.primary,
  fontSize: theme.fontSize.small,
}));

const divStyle = useStyled((theme) => ({
  backgroundColor: theme.color.primary,
}));

Function Overloads

This probably was not new but seems new. For generics we had the say hello, wave goodbye - well almost. We solved this with generics.

import { expect, it } from "vitest";
import { Equal, Expect } from "../helpers/type-utils";

function youSayGoodbyeISayHello<TGreeting extends "hello" | "goodbye">(
  greeting: TGreeting,
): TGreeting extends "hello" ? "goodbye" : "hello" {
  return (greeting === "goodbye" ? "hello" : "goodbye") as any;
}

it("Should return goodbye when hello is passed in", () => {
  const result = youSayGoodbyeISayHello("hello");

  type test = [Expect<Equal<typeof result, "goodbye">>];

  expect(result).toEqual("goodbye");
});

it("Should return hello when goodbye is passed in", () => {
  const result = youSayGoodbyeISayHello("goodbye");

  type test = [Expect<Equal<typeof result, "hello">>];

  expect(result).toEqual("hello");
});

For Function overloads this is a lot easier. It seems more like function restrictions to me as the implementation is an amalgamation of the possible options and the function signatures are the permissible types

function youSayGoodbyeISayHello(greeting: "goodbye"): "hello";
function youSayGoodbyeISayHello(greeting: "hello"): "goodbye";

function youSayGoodbyeISayHello(greeting: "goodbye" | "hello") {
  return greeting === "goodbye" ? "hello" : "goodbye";
}