Typescript: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
 
(105 intermediate revisions by the same user not shown)
Line 4: Line 4:
e.g.  
e.g.  


<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">


let myString = "fred";
let myString = "fred";
Line 16: Line 16:
Typescript supports classes and access modifiers
Typescript supports classes and access modifiers


<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">


class Person {
class Person {
Line 86: Line 86:
== No more var ==
== No more var ==
Don't use var but instead use  
Don't use var but instead use  
<syntaxhighlight lang="typescript">let</syntaxhighlight>  
<syntaxhighlight lang="ts">let</syntaxhighlight>  
or  
or  
<syntaxhighlight lang="typescript">const</syntaxhighlight>
<syntaxhighlight lang="ts">const</syntaxhighlight>


== Base Data Types ==
== Base Data Types ==
Line 108: Line 108:
Union types
Union types
This allows more than type e.g.  
This allows more than type e.g.  
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
let number_string : string | number
let number_string : string | number
</syntaxhighlight>
</syntaxhighlight>
Not good if you ask me. However maybe useful for strings e.g.
Not good if you ask me. However maybe useful for strings e.g.
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
let null_string : string | null
let null_string : string | null
</syntaxhighlight>
</syntaxhighlight>
Line 119: Line 119:
== Type assertions ==
== Type assertions ==
You can assert types in one of two ways
You can assert types in one of two ways
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
let value: any = 5;
let value: any = 5;


Line 126: Line 126:
</syntaxhighlight>
</syntaxhighlight>
or  
or  
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
let fixedString: string =  (value as number).toFixed(4);
let fixedString: string =  (value as number).toFixed(4);
</syntaxhighlight>
</syntaxhighlight>
Line 133: Line 133:
== Adding types ==
== Adding types ==
With typescript we can specify types e.g.
With typescript we can specify types e.g.
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
function funFunc(score: number, message1: string = "default ", message2?: string): string {
function funFunc(score: number, message1: string = "default ", message2?: string): string {
   return message1 + message2;
   return message1 + message2;
Line 145: Line 145:
e.g.
e.g.
For zero parameters
For zero parameters
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
  let greeting = () =>  console.log("Hello world");
  let greeting = () =>  console.log("Hello world");
  greeting(); // Hello world
  greeting(); // Hello world
Line 151: Line 151:


For 1 parameter
For 1 parameter
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
  let squareit = x => x * x;
  let squareit = x => x * x;
  let result = squareit(4); // 16
  let result = squareit(4); // 16
Line 157: Line 157:


For multiple parameters
For multiple parameters
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
  let adder = (a,b) = a + b;
  let adder = (a,b) = a + b;
  let sum = adder(2,3); // 5
  let sum = adder(2,3); // 5
Line 163: Line 163:


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.
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.
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
var scores = [70,125,85,110, 10000];
var scores = [70,125,85,110, 10000];


Line 185: Line 185:


Without  arrow function
Without  arrow function
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
function Book() {
function Book() {
   let self = this;
   let self = this;
Line 196: Line 196:


With arrow function
With arrow function
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
function Book() {
function Book() {
   this.publishDate = 2016;
   this.publishDate = 2016;
Line 208: Line 208:
You can assign functions with the same signatures to variables with typescript. E.g.
You can assign functions with the same signatures to variables with typescript. E.g.


<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">


function logError(err: string) : void {
function logError(err: string) : void {
Line 235: Line 235:
== Rest Parameters (params or variadic) ==
== Rest Parameters (params or variadic) ==
Example below
Example below
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
function GetBooksReadForCustomer(name: string, ...bookIDs: number[]) {
function GetBooksReadForCustomer(name: string, ...bookIDs: number[]) {


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


<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
function GetTitles(author: string) string[];
function GetTitles(author: string) string[];
function GetTitles(author: boolean) string[];
function GetTitles(author: boolean) string[];
Line 267: Line 267:
=== Standard ===
=== Standard ===
Basic interfaces
Basic interfaces
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
interface Employee {
interface Employee {
   name: string;
   name: string;
Line 292: Line 292:
=== Interface for Function types ===
=== Interface for Function types ===
Combining with function types
Combining with function types
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
// Simple function
// Simple function
function CreateCustomerID(name: string, id: number): string {
function CreateCustomerID(name: string, id: number): string {
Line 309: Line 309:
// Improved way
// Improved way
let IdGenerator = StringGenerator;
let IdGenerator = StringGenerator;
</syntaxhighlight>
Example
<syntaxhighlight lang="ts">
interface DamageLogger {
  (damage: string) : void;
}
let logDamage: DamageLogger;
logDamage = (damage: string) => console.log('Damage reported: ' + damage);
logDamage('coffee stains'); // Damage reported: coffee stains


</syntaxhighlight>
</syntaxhighlight>


== Classes ==
== Classes ==
=== Basic Stuff ===
Example below, default access is public
Example below, default access is public


<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
class Developer {
class Developer {


Line 344: Line 357:
       this.favoriteEditor = editor;
       this.favoriteEditor = editor;
   }
   }
}
// Abstract
class MyClass {
  abstract printStuff(): void;
}
</syntaxhighlight>
=== Non C# Stuff ===
====Initialise attribute without type====
<syntaxhighlight lang="ts">
// C# ish
class Author {
  name: string;
  constructor(inName: string) {
    name = inName;
  }
}
// Typescript
class Author {
  constructor(public name: string) {
  }
}
}


</syntaxhighlight>
====Class Expression====
You can create an expression of a class. E.g. implement an abstract on on the fly.
<syntaxhighlight lang="ts">
let Newspaper = class extends ReferenceItem {
  ImplementationOfAbstract: void {
    console.log('I am implemented now');
  }
}
let myPaper = new Newspaper('The Gazette', 2016);
myPaper.ImplementationOfAbstract();
</syntaxhighlight>
</syntaxhighlight>


== Importing ==
== Importing ==
To import typescript classes you can use the Triple-slash directive
To import typescript classes you can use the Triple-slash directive
<syntaxhighlight lang="typescript">
<syntaxhighlight lang="ts">
/// <reference path="player.ts" />
/// <reference path="player.ts" />
</syntaxhighlight>
</syntaxhighlight>
= Generics =
== Array ==
Array is a built in Generic e.g.
<syntaxhighlight lang="bash">
let Books : Book[]
// With Generic Array
let Books : Array<Book>
</syntaxhighlight>
== Functions ==
Much the same as c#
<syntaxhighlight lang="bash">
function LogAndReturn<T>)thing : T) : T {
  console.log(thing);
  return thing;
}
let someString : string = LogAndReturn<string>('log this');
</syntaxhighlight>
== Interfaces and Classes ==
Much the same as c# as well
<syntaxhighlight lang="ts">
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>();
</syntaxhighlight>
== Constraints ==
This is just for typescript I think
<syntaxhighlight lang="ts">
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.
}
</syntaxhighlight>
= TypeScript Declaration Files =
= TypeScript Declaration Files =
These are typescript wrappers for JavaScript libraries. This allows the typescript compiler to validate your usage.
These are typescript wrappers for JavaScript libraries. This allows the typescript compiler to validate your usage.
Line 364: Line 466:
<syntaxhighlight lang="bash">
<syntaxhighlight lang="bash">
npm install --save @types/lodash
npm install --save @types/lodash
</syntaxhighlight>
= Advanced =
== Destructuring ==
Like javascript
<syntaxhighlight lang="ts">
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
</syntaxhighlight>
== Spread Operator ==
Like javascript
Additional to other array
<syntaxhighlight lang="ts">
let newBookIDs = [10,20]
let allBookIDs = [1,2,3, ...newBookIDs] // 1,2,3,10,20
</syntaxhighlight>
== Intersection types ==
<syntaxhighlight lang="ts">
// 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'
}
</syntaxhighlight>
== 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"
<syntaxhighlight lang="ts">
// 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));
        });
    });
}
</syntaxhighlight>
== String Literal Types and Type Aliases == 
<syntaxhighlight lang="ts">
// Like enums
let empCategory: 'Manager' | 'Non-Manager'
// Type aliases
type EmployeeCategory = 'Manager' | 'Non-Manager'
</syntaxhighlight>
== Advanced Type Features ==
=== Polymorphic this ===
The this is referring to the type returned. e.g.
<syntaxhighlight lang="ts">
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
</syntaxhighlight>
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.
<syntaxhighlight lang="ts">
interface Book {
  id: number,
  author: string,
  category: Category
}
// By typing another they are merged by default
interface Book {
  identifier: number,
  writer: string
}
</syntaxhighlight>
Maybe this is a better example where an extension is built on an existing class
<syntaxhighlight lang="ts">
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)
}
</syntaxhighlight>
=== Type Guards ===
==== typeof type guard ====
Protect code for correct type
<syntaxhighlight lang="ts">
if ( typeof x === 'string')
{
}
else if ( typeof x === 'number')
{
}
</syntaxhighlight>
Allows types are
* string
* number
* boolean
* symbol
==== Custom type guard ====
You can write your own using
<syntaxhighlight lang="ts">
function isBook(text: Book | Magazine) : text is Book {
  return (<Book>text).author !== undefined
}
</syntaxhighlight>
=== 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()".
<syntaxhighlight lang="ts">
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
</syntaxhighlight>
May need to do some reading on this
=== Decorators ===
==== Introduction ====
<syntaxhighlight lang="ts">
// 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
}
</syntaxhighlight>
==== Class Decorator ====
This is the signature for a class decorator
<syntaxhighlight lang="ts">
// ClassDectorator Type
<TFunction extends Function>(target: TFunction) => TFunction | void
</syntaxhighlight>
Example where the constructor is not replaced
<syntaxhighlight lang="ts">
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 {
}
</syntaxhighlight>
Example where the constructor is replaced
<syntaxhighlight lang="ts">
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;
}
</syntaxhighlight>
==== Method Decorator ====
Example
<syntaxhighlight lang="ts">
export function readOnly(target : Object,
                        propertyKey: string,
                        descriptor: PropertyDescriptor) {
  console.log('Setting ${propertyKey}.');
  descriptor.writable = false;
}
</syntaxhighlight>
// Changing to a factory decorator
// i.e. remove export and replace with return, remove function name
<syntaxhighlight lang="ts">
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")
  }
}
..
</syntaxhighlight>
== Asyncronous Calls ==
=== Callback functions ===
<syntaxhighlight lang="ts">
// 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')
</syntaxhighlight>
=== Promises ===
So,
* Requires 2015
* Similar to Tasks in c#.
* You can chain promises togethe'Found titles: ${titles}'))r
* Simple API, then, catch
<syntaxhighlight lang="ts">
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)
})
</syntaxhighlight>
Taking callback example
<syntaxhighlight lang="ts">
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')
</syntaxhighlight>
===async await===
Example
<syntaxhighlight lang="ts">
async function doAsyncWork() {
  let results = await GetLongTask();
  console.log(results)
}
</syntaxhighlight>
Taking promise example
<syntaxhighlight lang="ts">
async function logSearchResult(bookCategory: Category) {
  let foundBooks = await getBooksByCategory(bookCategory)
  console.log(foundBooks)
}
console.log('Beginning')
logSearchResults(Category.Fiction);
console.log('Submitted')
</syntaxhighlight>
=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
<syntaxhighlight lang="ts">
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 {};
</syntaxhighlight>
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
<syntaxhighlight lang="ts">
const getValue = <TObj, TKey extends keyof TObj>(obj: TObj, key: TKey) => {
  return obj[key];
};
</syntaxhighlight>
==Wrapping a Api==
Sometimes we want to apply a generic to the functions are inferred with the correct type.
<syntaxhighlight lang="ts" highlight="16,21">
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,
}));
</syntaxhighlight>
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.
<syntaxhighlight lang="ts" highlight="1-7, 18">
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>();
</syntaxhighlight>
Now the exported functions knows we are using MyTheme and we can now write without specifying MyTheme
<syntaxhighlight lang="ts">
const buttonStyle = useStyled((theme) => ({
  color: theme.color.primary,
  fontSize: theme.fontSize.small,
}));
const divStyle = useStyled((theme) => ({
  backgroundColor: theme.color.primary,
}));
</syntaxhighlight>
==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.
<syntaxhighlight lang="ts">
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");
});
</syntaxhighlight>
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
<syntaxhighlight lang="ts">
function youSayGoodbyeISayHello(greeting: "goodbye"): "hello";
function youSayGoodbyeISayHello(greeting: "hello"): "goodbye";
function youSayGoodbyeISayHello(greeting: "goodbye" | "hello") {
  return greeting === "goodbye" ? "hello" : "goodbye";
}
</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";
}