Typescript: Difference between revisions
Line 1,199: | Line 1,199: | ||
===Right Side (Implementation)=== | ===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> | 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 a given type SomeType extends another given type OtherType, then ConditionalType is TrueType, otherwise it is FalseType | |||
</syntaxhighlight> | |||
So we have | So we have | ||
<syntaxhighlight lang="typescript"> | <syntaxhighlight lang="typescript"> | ||
T extends [] ? never : T[0] | T extends [] ? never : T[0] | ||
</syntaxhighlight> | </syntaxhighlight> |
Revision as of 02:54, 16 July 2023
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')
Tips For TypeScript
Guards
We can check a type by looking for a property and use the in Operator. E.g.
const printDocument = (doc: DelimitedDocument | PlaintextDocument) => {
if("seperator" in doc) {
printDeliminated(doc)
}
else {
printPlaintext(doc)
}
};
We can provide a property (__typename) in the types to determine the type and use a predicate. E.g.
export type = Invoice = FinalInvoice | DraftInvoice
export const isTypeFinalInvoice = (invoice: Invoice): invoice is FinalInvoice => {
return invoice.__typename === "FinalInvoice";
}
export const isTypeDaftInvoice = (invoice: Invoice): invoice is DaftInvoice => {
return invoice.__typename === "DaftInvoice";
}
Test using asserts not assert
function assertIsNumber(val: any): asserts val is number {
if(typeof val !== "number") {
throw new AssertionError("Not a number"
}
)
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
// 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">
Conditional Types
We can breakdown complex types in to simple types. This is useful when the incoming data is more complex than we want
type Unarray<T> = T extends Array<infer U> ? U:T
type Release = Unarry<typeof backlog["releases"]>
type Epic = Unarray<Release["epics"]>
const backlog = {
releases: [
name: "Sprint 1",
epic: [
{
name: "Account Management",
tasks: [
{ "name": "Single Sign on", Priority.mustHave },
{ "name": "Email Notification", Priority.mustHave },
]
}
]
]
}
Infer Keyword
Functional Overloads
Did not know this was possible but it is
Material UI
This is new stuff probably should be with react
// 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;
Typescript Challenges
Introduction
This is a list of challenges to make a type the supports a question. 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.
Example 1
interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = MyPick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
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.
type MyPick<T, K extends keyof T>
So T is the Type and K = keyof T will return a union of keys from T
type myType1 = {
key1: string,
key2: string,
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
Right Side (Implementation)
So we need to make type
MyPick<T, K extends keyof T>
Equal
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}
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.
{
array of keys : value for key for this type
keys[]: values[]
}
So give we have T as the type, K as the key we can do this
{
[k in K] : T[k]
}
Example 2 Iterating over Keys
Ok example 2 is to make a type which makes the all properties read-only
interface Todo {
title: string
description: string
}
const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}
So the mistake I made with this was to think they wanted what they wanted before and so this would be the answer.
{
readonly [k in K] : T[k]
}
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.
type MyReadonly<T> =
Right Side (Implementation)
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.
{
readonly [P in keyof T] : T[P]
}
Example 3 Iterating over Arrays transformation
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'}
type TupleToObject<T extends readonly any[]> = any
Example Left Side (Signature)
So in this case the left hand side is almost provided see below
type TupleToObject<T extends readonly any[]> =
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
{
[P in T[number]] : P
}
Improving
However in the challenge they have a test for a invalid assignment
type error = TupleToObject<[[1, 2], {}]>
We need to stop the any in the left hand side. So lets put on some constraints. We constrain it to be an array.
type TupleToObject<T extends readonly (string | symbol | number)[] > = { [P in T[number]] : P }
Example 4 First Conditional Type
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
type First<T extends any[]> = any
Example Left Side (Signature)
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
type First<T extends any[]>
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
If a given type SomeType extends another given type OtherType, then ConditionalType is TrueType, otherwise it is FalseType
So we have
T extends [] ? never : T[0]