Workshop
TypeScript Introduction
workshops.de
Hint for trainers
workshops.de
Task: Test your knowledge
@ts-ignore
Interfaces
Type Alias
strictNullChecks
any
Enum
Utility types
Type inference
Generics
TypeScript
workshops.de
TypeScript
JavaScript with syntax for types
workshops.de
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
workshops.de
TypeScript is a superset
ES5
ES20XX
TypeScript
workshops.de
Why TypeScript
workshops.de
Why TypeScript
The result: better maintenance for long-living projects
workshops.de
Types
workshops.de
Types in TypeScript - Variables
Types exist for primitive types.
let isDone: boolean = true;�
let size: number = 42;�
let firstName: string = 'Lena';
<code>
workshops.de
Types in TypeScript - Variables
Types exist for reference types.
const attendees: string[] = ['Elias', 'Anna'];
const attendees: Array<string> = ['Elias', 'Anna'];
const attendees: ReadonlyArray<string> = ['Elias', 'Anna'];
const myPair: [string, number] = ['Elias', 22];
<code>
workshops.de
Types - any
any takes any type. You are in JavaScript-Land.
let question: any = 'Can be a string';�
question = 6 * 7;�question = false;
<code>
workshops.de
Types - unknown
unknown takes any type - but you cannot access any property unless you check for its existence
let vAny: any = 10; // We can assign anything to any
let vUnknown: unknown = 10; // We can assign anything to unknown just like any
let s1: string = vAny; // value of type any is assignable to anything
let s2: string = vUnknown; // Invalid: value of type unknown (vUnknown) can't be assigned to any other type (without an explicit assertion)
vAny.method(); // ok anything goes with any
vUnknown.method(); // not ok, we don't know anything about this variable
<code>
workshops.de
The type unknown is more defensive than any.
workshops.de
Type inference
workshops.de
Type inference
You don’t have to set the type of a variable if the compiler can infer it.
let firstName = 'Max'; // string
let age = 30; // number
let isEmployed = true; // boolean
let friends = ['Stefan', 'Frederike']; // string[]
let dayOfBirth = Date.parse('...'); // Date
<code>
workshops.de
null and undefined
workshops.de
By default each data type in TypeScript does not accept null and undefined.
workshops.de
Compiler Flag: strict
Strict is a shorthand activating multiple rules
strict
strictNullChecks
strictProperty-
Initialisation
strictFunction-�Types
strict-
BindCallApply
<code>
workshops.de
Compiler Flag: strict
// tsconfig.json
{
"compilerOptions": {
"strict": "true"
// ...
}
// ...
}
<code>
workshops.de
Compiler Flag: strict
In strict null checking mode, the null and undefined values are not in the domain of every type.
let firstName : string | null = null;
let age : number | undefined = undefined;
let isEmployed : boolean | undefined;
// TypeScript Errors
let firstName : string = null;
let age : number = undefined;
let isEmployed : boolean; // undefined
<code>
workshops.de
Functions
workshops.de
Functions - Types
Add types to function parameters and return values.
function sayHi(firstName: string): void {� console.log(firstName);�}
<code>
workshops.de
Functions - Optional parameters
Parameters can be optional. Use a question mark.
�function buildName(firstName: string, lastName?: string) {� if (lastName) {� return firstName + ' ' + lastName;� } else {� return firstName;� }�}
<code>
workshops.de
Functions - Default parameters
Function arguments can have defaults for arguments.
// type Inference: lastName is a string
function buildName(firstName: string, lastName?: string = 'Bond') {� return firstName + ' ' + lastName;�}
<code>
workshops.de
Object property checks:
Interfaces & Type Aliases
workshops.de
There are two syntactical flavors to type objects and values:
Interface, Type-Alias
workshops.de
Interfaces and Type Aliases
interface Book {� isbn: string;
title: string;�}
type Book = {
isbn: string;
title: string;
}
Interface
Type Alias
Give the structure of an object a name
let book: Book;
book = {
isbn: '978-1593272821',
title: 'Eloquent JavaScript'
};
const book: Book = {
isbn: '978-1593272821',
title: 'Eloquent JavaScript'
};
<code>
workshops.de
Optional properties
interface Book {� isbn: string;
title: string;� pages?: number;�}
type Book = {
isbn: string;
title: string;
pages?: number;
}
Interface
Type Alias
Properties can be optional.
const book: Book = {
isbn: 'Goethe, Johann Wolfgang: Faust. Der Tragödie Erster Teil',
title: '978-3-15-000001-4',
}
<code>
workshops.de
Optional properties
interface Book {� isbn: string;
title: string;� pages?: number;�}
interface Book {
isbn: string;
title: string;
pages: number | undefined;
}
Optional property is not equivalent to property which may be undefined.
const book: Book = {
isbn: 'Goethe, Johann Wolfgang: Faust. Der Tragödie Erster Teil',
title: '978-3-15-000001-4',
}
// Property 'pages' is missing in type '{ isbn: string; title: string; }' but required in type 'Book'.
<code>
workshops.de
Nesting
interface Profile {
id: number;
gender: string;
name: string;
pictureUrl?: string;
address: {
street: string,
zipCode: string,
city: string,
}
}
Interface
Type Alias
Interfaces and Type Aliases can be nested
type Profile = {
id: number;
gender: string;
name: string;
pictureUrl?: string;
address: {
street: string;
zipCode: string;
city: string;
}
}
<code>
workshops.de
Extends & Intersection Type
interface Book {� isbn: string;
title: string;� pages?: number;�}
type Book = {
isbn: string;
title: string;
pages?: number;
}
Interface
Type Alias
interface Magazine extends Book {
coverUrl: string;
}
type Magazine = Book & {
coverUrl: string;
};
The properties of Book are merged into Magazine.
<code>
workshops.de
Union Type
type Style = {
position?: string;
padding?: string | number;
margin?: string | number;
// other style properties
}
Interface
Type Alias
interface Style {
position?: string;
padding?: string | number;
margin?: string | number;
// other style properties
}
const style: Style = {
padding: '15px'
}
Expect a property to be either one or the other
Alternative 1
Alternative 2
const style: Style = {
padding: 15
}
<code>
workshops.de
Union Type
interface Book {
title: string;� isbn: string;
}
interface Magazine {
title: string;
issn: string;
}
type ReadingMaterial = Book | Magazine;
const readingMaterial: ReadingMaterial[] = [
{
title: 'Vogue',
issn: '0042-8000',
},
{
title: 'Robinson Crusoe',
isbn: '978-3401002569'
}
]
Common Pattern: Discriminating Unions
<code>
workshops.de
Union Types
interface Book {� title: string;
isbn: string;
}
interface Magazine {
title: string;
issn: string;
}
type ReadingMaterial = Book | Magazine | { id: string } | A ;
const getBook = (title: string): ReadingMaterial | undefined =>
readingMaterial.find(rM => rM.title === title)
The properties of Book are merged into Magazine.
<code>
workshops.de
Interfaces
Give an interface a name and use it as a type for variables.
interface Book {� isbn: string;
title: string;�}��const book: Book;
book = {
isbn: '978-1593272821',
title: 'Eloquent JavaScript'
};
<code>
workshops.de
Type Aliases
Declare type alias by giving it a name & define its shape
type Book = {� isbn: string;
title: string;�}��const book: Book;
book = {
isbn: '978-1593272821',
title: 'Eloquent JavaScript'
};
<code>
workshops.de
When to use Interfaces
workshops.de
Interfaces
An Interface can be implemented by a class.
class Biography implements Book {
isbn: string;
title: string;
}
<code>
workshops.de
Interface Merging
interface Book {
isbn: string;
title: string;
}
// other code...
interface Book {
pages?: number;
}
When re-declaring interfaces, its properties merge
interface Book {
isbn: string;
title: string;
pages?: number;
}
<code>
workshops.de
Interfaces - Class types
Forgetting to implement flipPage throws a compile error.
interface CanFlip {� flipPage: Function;�}
class BookListComponent implements CanFlip {��}
Class 'BookListComponent' incorrectly implements interface 'CanFlip'.
Property 'flipPage' is missing in type 'BookListComponent' but required in type 'CanFlip'.
<code>
workshops.de
When to use Type Aliases
workshops.de
Type Alias for unions of type literals
You can allow certain valid values for a type
// Example 1
type HttpStatusCodes = 200 | 201 | 400;
// Example 2
type TextAlignOptions = 'left' | 'right' | 'center' | 'auto' | 'justify';
<code>
workshops.de
Type Alias for unions of interfaces
interface Book {
title: string;� isbn: string;
}
interface Magazine {
title: string;
issn: string;
}
type ReadingMaterial = Book | Magazine;
const readingMaterial: ReadingMaterial[] = [
{
title: 'Vogue',
issn: '0042-8000',
},
{
title: 'Robinson Crusoe',
isbn: '978-3401002569'
}
]
Common Pattern: Discriminating Unions
<code>
workshops.de
Type alias for primitive types
type ContractId = string;
type CustomerId = string;
export interface Contract {
id: ContractId;
customerId: CustomerId;
}
Use type aliases to provide more context (as a form of documentation)
<code>
workshops.de
Interfaces vs. Type Aliases
workshops.de
Interface vs. Type Alias
“Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable..”
TypeScript Docs
<code>
workshops.de
Function Types
workshops.de
Function Types as Type Alias
type OnSuccess = (result: string) => void;
function doSomething(onSuccess: OnSuccess) {
// Some async code calls the callback
}
const onSuccess: OnSuccess = (result: string) => console.log(result);
doSomething(onSuccess);
The type of a function can be given as type alias
<code>
workshops.de
Function Types as Interface
interface OnSuccess {
(result: string): void;
}
function doSomething(onSuccess: OnSuccess) {
// Some async code
}
const onSuccess: OnSuccess = (result: string) => console.log(result);
doSomething(onSuccess);
The type of a function can be given as an interface (it’s an object).
<code>
workshops.de
TypeScript Generics
workshops.de
TypeScript Generics
The Array-Type is implemented as a generic.
const books: Array<number>= []
const list: ReadonlyArray<string> = ["foo", "bar"];
const books: Array<{title: string, isbn: string}> = []
const books: Array<Book> = []
<code>
workshops.de
TypeScript Generics
Generic object
type Response<T> = {
id: number;
data: T[];
createdAt: number;
modifiedAt: number;
};
const response: Response<Book> = {
id: 1,
data: [{ isbn: 'abc', title: 'Faust' }],
createdAt: 12345678,
modifiedAt: 12345678,
}
<code>
workshops.de
Generic Functions
Input argument is of same type as return value
const logIdentity = <T>(arg: T): T => {
console.log(arg);
return arg;
};
<code>
workshops.de
Generic Functions
Reverse a list: what gets passed into the function should be of the same type of what gets returned
const reverse = <T>(items: T[]): T[] => {
let result = [];
for (let i = items.length - 1; i >= 0; i--) {
result.push(items[i]);
}
return result;
}
<code>
workshops.de
Generic Functions
Reverse a list: what gets passed into the function should be of the same type of what gets returned
const reversed = reverse([3, 2, 1]) // [1, 2, 3]
// Safety!
reversed[0] = '1'; // Error!
const reverse = <T>(items: T[]): T[] => {
let result = [];
for (let i = items.length - 1; i >= 0; i--) {
result.push(items[i]);
}
return result;
}
<code>
workshops.de
Generic Functions
Placement of the generic identifier in fat-arrow functions
function reverse<T>(items: T[]): T[] { /* ... */ }
// vs
const reverse = <T>(items: T[]): T[] => { /* ... */ }
<code>
workshops.de
Update the TypeScript
definitions
Task
workshops.de
Advanced Types
workshops.de
Enums
workshops.de
Enum
Using enums can make it easier to document intent, or create a set of distinct cases.
enum Progress {
min = 0,
max = 100,
}
<code>
workshops.de
Enum
Using enums can make it easier to document intent, or create a set of distinct cases.
enum LogLevel {
ERROR,
WARN,
INFO,
DEBUG
}
enum LogLevel {
ERROR = 0,
WARN = 1,
INFO = 2,
DEBUG = 3,
}
Is equivalent
<code>
workshops.de
Enum
Named enum
enum Gender {
Female = 'FEMALE',
Male = 'MALE',
Other = 'OTHER',
}
<code>
workshops.de
Records
workshops.de
Record
Define type of all key and value pairs of object
const months: Record<string, number> = {
january: 1,
february: 2,
march: 3,
april: 4,
mai: 5,
june: 6,
july: 7,
august: 8,
september: 9,
october: 10,
november: 11,
december: 12,
};
<code>
workshops.de
Record
Define type of all key and value pairs of object
const arrayToObject = (values: number[]): Record<string, number> => {
// ...
};
const getNumberFormatSettings = (): Record<string, string> => ({
decimalSeparator: '.',
groupingSeparator: ',',
});
<code>
workshops.de
Excess Property Checks
workshops.de
Excess Property Checks
Not defined properties cause a warning when directly giving a type to an object literal.
interface Car {
wheels: number;
engine: string;
}
const tesla: Car = {
wheels: 4,
engine: 'electric',
cameras: 20, // Error
}
<code>
workshops.de
Excess Property Checks
Not defined properties cause an error when directly giving a type to an object literal.
interface Car {
wheels: number;
engine: string;
}
const tesla = {
wheels: 4,
engine: 'electric',
cameras: 20,
}
const myTesla: Car = tesla;
No TypeScript Error
<code>
workshops.de
Indexable Types
Object with certain properties but arbitrary additional properties.
interface Car {
wheels: number;
engine: string;
[key: string]: unknown;
}
const myTesla: Car = {
wheels: 4,
engine: 'electric',
cameras: 20,
}
<code>
workshops.de
Utility types
workshops.de
Partial
Make all properties optional
type FormValues = {
firstName: string;
surname: string;
dateOfBirth: Date;
gender: Gender;
};
const initialValues: Partial<FormValues> = { ...storedUserData };
type FormValuesPartial = {
firstName?: string;
surname?: string;
dateOfBirth?: Date;
gender?: Gender;
};
<code>
workshops.de
Omit and Pick
omit certain properties of an object or pick only certain properties
interface Book {
id: number;
isbn: string;
title: string;
pages?: number;
}
type BookId = Pick<Book, 'id'>
type BookId = { id: Book['id'] };
type BookId = Omit<Book, 'isbn' | 'title' | 'pages'>;
const Id = {
id: 12,
}
Three solutions which all lead to the same type.
<code>
workshops.de
Omit and Pick
omit certain properties of an object or pick only certain properties
interface Book {
id: number;
isbn: string;
title: string;
pages?: number;
}
type BookDetails = Pick<Book, 'title' | 'pages'>;
type BookDetails = Omit<Book, 'id' | 'isbn'>;
type BookDetails = {
title: Book['title'];
pages?: Book['pages'];
}
const details: BookDetails = {
title: 'Hamlet',
pages: 67,
}
Three solutions which all lead to the same type.
<code>
workshops.de
Combining Utility types
Combine utility types with other types to create more powerful types.
interface Book {
id: string,
title: string,
isbn: string,
}
// Example 1
type OptionalBookPropsWithoutId = Omit<Partial<Book>, 'id'>;
// Example 2
type BookWithNumberId = Omit<Book, 'id'> & { id: number };
<code>
workshops.de
Utility types
Use utility types to remain rename refactoring safe
interface Titles {
bookTitles: Array<Pick<Book, 'title'>>
videoTitles: Array<Pick<Video, 'title'>>
}
<code>
workshops.de
Compose TypeScript’s utility types
workshops.de
Partial<T> makes all properties of a type optional. What if we want to make only a few properties optional?
We can create our own utility type Optional<>.
workshops.de
How to read a complex type definition
This is how you can use it to make a property optional.
interface Customer {
id: string;
firstName: string;
lastName: string;
}
const optionalCustomer: Optional<Customer, 'firstName' | 'lastName'> = {
id: "12",
}
<code>
workshops.de
How to read a complex type definition
Solution: This is how Optional can look like.
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
Let’s break this type down into pieces and learn how we can read & understand complex custom types.
<code>
workshops.de
How to read a complex type definition
We create a new type and sharing it with other modules.
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
<code>
workshops.de
How to read a complex type definition
We name the type “Optional”.
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
<code>
workshops.de
How to read a complex type definition
“Optional” has two parameters:
K has to be a property name of the Object.
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
<code>
workshops.de
How to read a complex type definition
Make the Object completely optional.
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
<code>
workshops.de
How to read a complex type definition
Take only the optional properties being specified in K.
interface AandB { a: string, b: string };
Pick<Partial<AandB>, "b"> // Result: { b?: string }
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
<code>
workshops.de
How to read a complex type definition
Combine the result from Pick<...> with the result form Omit<...>.
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
<code>
workshops.de
How to read a complex type definition
Use the given object T but remove all properties specified by K.
interface AandB { a: string, b: string };
Omit<AandB, "b"> // { a: string }
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
<code>
workshops.de
How to read a complex type definition
Put both results together:
// { b?: string } // { a: string }
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
<code>
workshops.de
How to read a complex type definition
The specified Key has been made optional.
{
a: string,
b?: string
}
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
<code>
workshops.de
Type Guards
workshops.de
Type Guards | built-in
With the JavaScript typeof operator we can check for a type
interface MayBeExecutable {
work?: () => void; // possibly undefined
}
function executer(unit: MayBeExecutable) {
if (typeof unit.work === 'function') {
unit.work();
}
}
<code>
workshops.de
Type Guards | built-in
Check for class instance with instanceof
const a = new Greeting(); // has method hi
const b = new Farewell(); // has method bye
function do(interaction: Greeting | Farewell) {
if (interaction instanceof Greeting) {
interaction.hi(); // type Greeting is inferred
} else {
interaction.bye(); // type Farewell is inferred,
// since no other type is possible
}
}
<code>
workshops.de
Type Guards | Custom
Help the TypeScript compiler to understand your types better.
// Example 1
function isNumber(value: string | number): value is number {
return !isNaN(value);
}
// Example 2
function isGoldCustomer(customer: NormalCustomer | GoldCustomer)
: customer is GoldCustomer
{
return customer.type === 'Gold';
}
<code>
workshops.de
Type Assertion
(Type Cast)
workshops.de
Type Assertions (Type Casts)
Override the compiler’s type checker. It comes in two syntax forms:
// Casting object literal to Person type: `as` syntax
const person = {
name: 'Victoria',
age: 28,
} as Person
// Casting object literal to Person: `ang-bracket` syntax
const person = <Person>{
name: 'Franz',
age: 34,
};
<code>
workshops.de
Type Assertions (Type Casts)
Dangerous use of type assertion: Can you be certain that persons array is not empty?
type Person = { name: string, age?: number };
// const persons = [ ... array of persons retrieved from API ];
// TS Error: Type 'undefined' is not assignable to type 'Person'.
const getPerson = (name: string): Person =>
persons.find(person => person.name === name);
// Solution 1: Union with undefined
const getPerson = (name: string): Person | undefined =>
persons.find(person => person.name === name);
// Solution 2: Type assertion (Use with caution! Can cause bugs!)
const getPerson = (name: string): Person =>
persons.find(person => person.name === name) as Person;
<code>
workshops.de
Type Assertions (Type Casts)
Valid use of type assertion: Help the compiler to understand what it could not do itself, but which you as the developer know for sure.
const deserialize = <T>(data: string): T => JSON.parse(data) as T;
const victoria = deserialize<Person>(
'{"name":"Victoria", "age":28}'
);
<code>
workshops.de
keyof Operator
workshops.de
keyof Operator
Takes an object type and produces a literal union of its keys
type Point = { x: number; y: number };
type P = keyof Point;
// Same as: type P = 'x' | 'y';
const x: P = 'x';
const y: P = 'y';
const z: P = 'z'; // Error: Type '"z"' is not assignable to type 'keyof Point'.
<code>
workshops.de
keyof with Enums
Create type out of enum
enum LogLevel {
ERROR,
WARN,
INFO,
DEBUG
}
enum LogLevel {
ERROR = 0,
WARN = 1,
INFO = 2,
DEBUG = 3,
}
type LogLevelStrings = keyof typeof LogLevel;
// This is equivalent to:
// type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
Equivalent enums
<code>
workshops.de
TypeScript & React
workshops.de
Generic functions
and TSX
workshops.de
Generic Functions
Recap: Reverse list example
const reverse = <T>(items: T[]): T[] => {
let result = [];
for (let i = items.length - 1; i >= 0; i--) {
result.push(items[i]);
}
return result;
}
<code>
workshops.de
Generic Functions & React
In .tsx files you have to differentiate between JSX syntax and TypeScript generics.
const reverse = <T,>(items: T[]): T[] => {
let result = [];
for (let i = items.length - 1; i >= 0; i--) {
result.push(items[i]);
}
return result;
}
<code>
workshops.de
Generic Functions & React
In .tsx files you have to differentiate between JSX syntax and TypeScript generics - Alternative.
const reverse = <T extends any>(items: T[]): T[] => {
let result = [];
for (let i = items.length - 1; i >= 0; i--) {
result.push(items[i]);
}
return result;
}
<code>
workshops.de
Utility Types
of the React Library
workshops.de
FunctionComponent (before v18)
Utility Type which adds children prop to provided type of props (note: This changed in the type definitions of React 18!)
interface ButtonProps {
variant?: string;
onPress: (event: Event) => void;
};
const Button: FunctionComponent<ButtonProps> = ({
children,
variant = 'solid',
onPress,
}) => (
<Wrapper variant={variant} onPress={onPress}>
<Title>{children}</Title>
</Wrapper>
);
‘children’ prop access is allowed.
<code>
workshops.de
FunctionComponent (v18)
Utility Type which adds children prop to provided type of props (note: This changed in the types of React 18!)
import { ReactNode } from 'react'
interface ButtonProps {
variant?: string;
onPress: (event: Event) => void;
children: ReactNode
};
const Button: ButtonProps = ({
children,
variant = 'solid',
onPress,
}) => (
...
);
‘children’ prop access is allowed.
<code>
workshops.de
FunctionComponent (before v18)
Interface FunctionComponent in node_modules/@types/react/index.d.ts
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
type FC<P = {}> = FunctionComponent<P>;
shorter name for the same type
<code>
workshops.de
FunctionComponent (v18)
Interface FunctionComponent in node_modules/@types/react/index.d.ts
interface FunctionComponent<P = {}> {
(props: P, context?: any): ReactElement<any, any> | null;
propTypes?: WeakValidationMap<P>;
contextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
type FC<P = {}> = FunctionComponent<P>;
shorter name for the same type
<code>
workshops.de
Type useState hook
Initial value does not give enough information for a strong type
const [items , setItems] = useState<string[]>([]);
<code>
workshops.de
Tips
workshops.de
Hint
Press F8 in VSCode to jump from type error to type error to fix them.
workshops.de
Hint
Don’t try to type everything perfectly right from the start.
It’s ok to have some curly underlines
workshops.de
workshops.de
We teach.
workshops.de