Lesson 04: TypeScript - strvcom/frontend-academy-2022 GitHub Wiki
Speaker: Konstantin Lebedev
- Pull Request
- Recording (Gdrive)
- Slides (Google Slides)
- Slides (PDF)
type Config = {
api: {
url: string
version: number
}
}
const configObject = {
api: {
url: 'http://test.com/api',
version: 1,
},
}
By default, all properties are required, but we can choose to make them optional:
type Product = {
name?: string
expirationDate: Date
}
Readonly properties can be set when object is created, but afterwards any attempt to change their values is going to result in TS error:
type Product = {
readonly name: string
expirationDate: Date
}
// this is fine
const product: Product = { name: 'Milk', expirationDate: new Date() }
// will give a TS error
product.name = 'Shoes'
Allow to create new types by merging the definition of 2 or more existing types:
type InputProps = {
value: string
type: string
className: string
// ...
}
const Input = (props: InputProps) => {
return <input {...props} />
}
type EditProps = {
error?: string
}
// defining an intersection type
type Props = InputProps & EditProps
const InputWithValidation = ({ error, ...rest }: Props) => {
return (
<>
<Input {...rest} />
{error ? <small>{error}</small> : null}
</>
)
}
Union types define types for variables that can be of 2 or more different types:
type StringLike = string | string[]
// this is fine
const str: StringLike = 'test'
const str: StringLike = ['test', 'test']
// this is not
const str: StringLike = 1000
When we work with union types, at some point we will want to know the exact type of the variable. We can use JS constructs to hint TS as to what exact type variable holds.
type RenderFn = () => string
const renderText = (text: string | RenderFn) => {
if (typeof text === 'string') {
return text
} else {
return text()
}
}
type Square = {
width: number
height: number
}
type Circle = {
radius: number
}
type Shape = Square | Circle
const calculateArea = (shape: Shape) => {
if ('radius' in shape) {
return Math.PI * shape.radius ** 2
} else {
return shape.width * shape.height
}
}
type Payload =
| { kind: 'Error'; code: number }
| { kind: 'Success'; message: string }
| { kind: 'NotFound' }
| { kind: 'NotFound'; code: number }
const getMessage = (response: Payload) => {
switch (response.kind) {
case 'Success':
return `Success! ${response.message}`
case 'Error':
return `Error ocurred. Code: ${response.code}`
case 'NotFound':
return ''
}
}
Generic types allow us to define types with inner types, where the inner type can be dynamic.
type User = {
name: string
age: number
}
type Post = {
title: string
}
// inner type Data is dynamic, it's a parameter for ApiPayload type
type ApiPayload<Data> = {
statusCode: number
data: Data
}
// inner type is resolved to Post
const parseApi = (payload: ApiPayload<Post>) => {
payload.data.title
}
// inner type resolution is deferred
const filterErrors = <T>(payloads: ApiPayload<T>[]) => {
return payloads.filter((p) => p.statusCode !== 200)
}
// but it gets resolved later
const payloads: ApiPayload<User>[] = []
const responses = filterErrors(payloads)
TypeScript comes with a handful of useful utility types, here's a few worth mentioning:
type | use |
---|---|
Readonly<T> |
Applies readonly modifier to all of the type properties |
Partial<T> |
Applies optional (? ) modifier to all of the type properties |
Required<T> |
Removes optional (? ) modifier from all of the type properties |
ReturnType<T> |
Extracts the return type from a type representing a function signature |
Awaited<T> |
Return the inner type of a Promise type |
NonNullable<T> |
Removes null and undefined from a list of options in a union type |