Typescript
Typescript is a superset of Javascript that is strongly typed and values specificity.
Casting
You can cast one type as another in a couple different ways.
// In these examples, query selector will naturally return an HTMLElement. To
// access the `value`, we will need to cast it as an HTMLInputElement.
let input;
// using 'as'
input = document.querySelector('input[type="text"]') as HTMLInputElement;
// using <>
input = <HTMLInputElement>document.querySelector('input[type="text"]');
When using TypeScript with JSX, only as
-style assertions are allowed.
Non-null Assertion Operator
The bang (!
) in Typescript, used preceding a property, is meant to tell the compiler that this value cannot be null
or undefined
[15], so don't complain that it could be.
A new ! post-fix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact. Specifically, the operation x! produces a value of the type of x with null and undefined excluded. Similar to type assertions of the forms
x and x as T, the ! non-null assertion operator is simply removed in the emitted JavaScript code.[16]
The operation a! produces a value of the type of a with null and undefined excluded.[18]
// Compiled with --strictNullChecks
function validateEntity(e?: Entity) {
// Throw exception if e is null or invalid entity
}
function processEntity(e?: Entity) {
validateEntity(e);
let s = e!.name; // Assert that e is non-null and access name
}
Type Annotations
Type annotations are done at the declaration of a variable with a colon followed by the type or interface.
// Primitives
const greeting: string = "Hello there!";
// Arrays
const numbers: number[] = [1, 2, 3, 4];
// Generics
const uniqueCharacters: Set<string> = new Set(['a', 'b', 'c']);
// Interfaces/Types
interface Person {
name: string,
age: number,
sayHi: () => string,
}
const johnSmith: Person = {
name: "John Smith",
age: 40,
sayHi: () => return greeting;
};
// Functions
// Param Types Output type
const add = (a: number, b: number): number => {
return a + b;
};
Annotations with Object Destructuring
// without annotation
const getNameAndAge = ({ person, job }) => {
const { name, age } = person;
return `${name}: ${age}, ${job}`;
};
// with annotation
const getNameAndAge = ({
person,
job,
}: {
person: { name: string; age: number };
job: string;
}) => {
const { name: string, age: number } = person;
return `${name}: ${age}, ${job}`;
};
// with type or interface
type Person = {
name: string;
age: number;
};
const getNameAndAge = ({ person: Person, job: string }) => {
const { name: string, age: number } = person;
return `${name}: ${age}, ${job}`;
};
Using any
In general, you want to avoid using any
as a type. ESLint has it disallowed by default, since it defeats the purpose of Typescript altogether. But what do you do if you actually don't know what data you will be receiving?
The issue is that using any
will allow a false sense of security with TypeScript, as any
types will allow compilation but sometimes will fail in the actual execution of the application, which is exactly what we don't want.
The solution here is to use the unknown
type. This will not allow compilation and will enforce the strict typing that makes TypeScript what it is and not JavaScript.
NOTE: My personal opinion is that you should always strive to find out what your data is that is going through your system, and if a type or interface does not exist for your own objects or with an external library you are using, one should be created to accommodate. Though I understand this is not always possible, it should be strived for.
Typing React
onChange Events
For React onChange handlers, you can use the React.ChangeEvent<>
generic, filling it in with whatever type of element is being changed.
<input
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
handleEmail(event);
handleEmailValidation();
}}
placeholder="Enter Email"
type="email"
/>
Unique Typing Troubleshooting
Property '***' does not exist on type 'never'
This means that the target variable has not been typed correctly and often will show up when something has not been given a type at all.
// incorrect
const thing = "Oh yeah!";
const things = [1, 2, 3, 4];
const specificThing = { name: "John", age: 9 };
// correct
const thing: string = "Oh yeah!";
const things: number[] = [1, 2, 3, 4];
const specificThing: InterfaceName = { name: "John", age: 9 };
useState
With useState
in React hooks, you will need to use a special syntax in creation of the hooks themselves. Since useState is a generic function, the typing happens within the <>
characters.
type HashtagListener {
...
}
const [editedHashtagListeners, setEditedHashtagListeners] = useState<HashtagListener[]>([]);
Interface and Type
Interfaces and types are two ways to define a type of object, declaring the types of the contained properties.
Differences
Type
// Basic objects
type Person = {
name: string;
age: number;
pets: string[];
};
const john: Person = {
name: "John",
age: 40,
pets: ["Mary", "Doug"],
};
const household: Person[] = [john];
// Using list of specific keys
type AllowedKeys = "name" | "age";
type Person = Record<AllowedKeys, unknown>;
const Human: Person = {
name: "Steve",
age: 42,
};
// Using mapped objects[17]
type Language = "en" | "es";
type WeekStartDays = 0 | 1 | 2 | 3 | 4 | 5 | 6;
type Translation<Type> = {
[Property in Language]: Type;
};
type MessageTranslations = Translation<string>;
// Equivalent to:
// type MessageTranslations = {
// en: string;
// es: string;
// }
type WeekStartDayTranslations = Translation<WeekStartDays>;
// Equivalent to:
// type WeekStartDayTranslations = {
// en: WeekStartDays;
// es: WeekStartDays;
// }
Interface
interface Person {
name: string;
age: number;
pets: string[];
}
const john: Person = {
name: "John",
age: 40,
pets: ["Mary", "Doug"],
};
const household: Person[] = [john];
Creating Fixed Values
type Roles = "owner" | "admin";
interface User {
name: string;
// value must be an array containing values within Roles
roles: Roles[];
}
Defining Unknown Property Keys
type Roles = "owner" | "admin";
interface User {
name: string;
channels: {
// channel at `index` must be a string
// value must be an array containing values within Roles
[index: string]: Roles[];
};
}
Defining Types in External Packages[19, 20]
If you are using a third-party package that either has no typings or has typings you want to change, you can create a types
folder and add an "ambient module". This defines the shape of the data and not the functionality.
Let's say you have a module called simple-package
that you want to add typings to. It outputs a function that creates an object that holds two numbers.
declare module "simple-package" {
export interface SimplePackageNumbers {
first: number;
second: number;
}
export function SimplePackage(): SimplePackageNumbers;
}
References:
- https://stackoverflow.com/questions/52423842/what-is-not-assignable-to-parameter-of-type-never-error-in-typescript
- https://stackoverflow.com/questions/53598449/react-hooks-and-typescript-property-does-not-exist-on-type-never
- https://stackoverflow.com/questions/41443242/how-to-correct-flow-warning-destructuring-missing-annotation
- https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces
- https://lzomedia.com/blog/how-to-apply-type-annotations-to-functions-in-typescript/
- https://stackoverflow.com/questions/12989741/the-property-value-does-not-exist-on-value-of-type-htmlelement
- https://stackoverflow.com/questions/61851004/describe-interface-fixed-values-in-array-element-of-typescript
- https://stackoverflow.com/questions/23914271/typescript-interface-definition-with-an-unknown-property-key
- https://basarat.gitbook.io/typescript/type-system
- https://dev.to/mattzgg_94/get-started-with-using-typescript-and-tdd-to-solve-leetcode-problems-in-vs-code-26d
- https://www.udemy.com/course/understanding-typescript/
- https://stackoverflow.com/questions/33256274/typesafe-select-onchange-event-using-reactjs-and-typescript
- https://www.cstrnt.dev/blog/three-typescript-tricks
- Cheatsheet for Typescript with React
- Non-null assertion operator
- Typescript notes on the non-null assertion operator
- TypeScript: Documentation - Mapped Type
- typescript - Safe navigation operator (?.) or (!.) and null property paths - Stack Overflow
- Writing Typescript Typings Files for Third Party Module
- Typescript ambient module declarations | Mourtada.se
Last modified: 202401040446