学习心得:一开始无法感知 TS 的好处,只是觉得编码更加麻烦,现在慢慢能体会到好处,想象你合作时使用他人创建的组件,组件加了类型定义的话,你写代码时知道哪些 props 是可选哪些是必写,写错了还会有报错提示。
H1Intro
TypeScript addresses the challenges of JavaScript, offering a strong and static type system by providing early error detection and improved tooling.
Benefits:
- Static typing
- Code completion
- Refactoring
- Shorthand notations
H1Basics
H2Static type-checking
Static types systems describe the shapes and behaviors of what our values will be when we run our programs. A type-checker like TypeScript uses that information and tells us when things might be going off the rails.
typescript
const message = "hello!";
message();
// This expression is not callable.
// Type 'String' has no call signatures.
H2Non-exception Failures
A static type system has to make the call over what code should be flagged as an error in its system, even if it’s “valid” JavaScript that won’t immediately throw an error. In TypeScript, the following code produces an error about location
not being defined:
typescript
const user = {
name: "Harris",
age: 23,
};
user.location;
// Property 'location' does not exist on type '{ name: string; age: number; }'.
H2Types for Tooling
TypeScript can catch bugs when we make mistakes in our code, and TypeScript can also prevent us from making those mistakes in the first place by providing code completion as you type in the editor.
H2Explicit Types
typescript
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", Date());
// Argument of type 'string' is not assignable to parameter of type 'Date'.
// Correct: greet("Maddison", new Date());
Keep in mind, we don’t always have to write explicit type annotations. In many cases, TypeScript can even just infer (or “figure out”) the types for us even if we omit them.
typescript
let msg = "hello there!";
// let msg: string
H2Erased Types
When we compile the above function greet
with tsc
to output JavaScript:
js
"use strict";
function greet(person, date) {
console.log("Hello ".concat(person, ", today is ").concat(date.toDateString(), "!"));
}
greet("Maddison", new Date());
Type annotations aren’t part of JavaScript (or ECMAScript to be pedantic), so there really aren’t any browsers or other runtimes that can just run TypeScript unmodified. That’s why TypeScript needs a compiler in the first place.
H2Downleveling
Why did rewriting the template string above happen?
Template strings are a feature from a version of ECMAScript called ECMAScript 2015 (a.k.a. ECMAScript 6, ES2015, ES6, etc. - don’t ask). TypeScript has the ability to rewrite code from newer versions of ECMAScript to older ones such as ECMAScript 3 or ECMAScript 5 (a.k.a. ES3 and ES5). This process of moving from a newer or “higher” version of ECMAScript down to an older or “lower” one is sometimes called downleveling.
By default TypeScript targets ES3, an extremely old version of ECMAScript. We could have chosen something a little bit more recent by using the target option. Running with --target es2015 changes TypeScript to target ECMAScript 2015, meaning code should be able to run wherever ECMAScript 2015 is supported.
While the default target is ES3, the great majority of current browsers support ES2015. Most developers can therefore safely specify ES2015 or above as a target, unless compatibility with certain ancient browsers is important.
H1Types
JS:
- number
- string
- boolean
- null
- undefined
- object
TS:
- any
- unknown
- never
- enum
- tuple
H2Tuples
typescript
let user: [number, string] = [2000, "harris"];
H2Enums
typescript
enum Size {
Small = 1,
Medium,
Large
}
console.log(Large); // 3, because all of the following members are auto-incremented from that point on.
H2Arrays
typescript
let arr: numbers[] = [1, 2, 3];
let arr1: Array<number> = [1, 2, 3];
let arr2: string[] = ["harris", "wong"];
H2Functions
Parameter & Return Type Annotations
typescript
function sum(a: number, b: number): number {
return a + b;
}
console.log(sum(1, 2)); // 3
H3Functions Which Return Promises
If you want to annotate the return type of a function which returns a promise, you should use the Promise
type:
typescript
async function getFavoriteNumber(): Promise<number> {
return 26;
}
H2Object Types
typescript
// The parameter's type annotation is an object type
function printCoord(pt: { x: number; y: number }) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });
H3Optional Properties
Add a ?
after the property name.
typescript
function printName(obj: { first: string; last?: string }) {/*...*/}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
H2Union Types
A union type is a type formed from two or more other types, representing values that may be any one of those types.
typescript
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
// Argument of type '{ myID: number; }' is not assignable to parameter of type 'string | number'.
H3Working with Union Types
typescript
function printId(id: number | string) {
console.log(id.toUpperCase());
// Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.
}
The solution is to narrow the union with code.
typescript
function printId(id: number | string) {
if (typeof id === "string") {
// In this branch, id is of type 'string'
console.log(id.toUpperCase());
} else {
// Here, id is of type 'number'
console.log(id);
}
}
Sometimes you’ll have a union where all the members have something in common. For example:
typescript
// Return type is inferred as number[] | string
function getFirstThree(x: number[] | string) {
return x.slice(0, 3);
}
H2Intersection Types
typescript
type Age = {age: number};
type Name = {name: string};
type User = Age & Name;
let user: User = {
age: 23,
name: "Harris"
};
H2Type Aliases
it’s common to use the same type more than once and refer to it by a single name.
typescript
type Point = {
x: number;
y: number;
};
type ID = number | string;
H2Interfaces
If you would like a heuristic, use
interface
until you need to use features fromtype
.Heuristic: 指得是探索启发式的学习方法,在这里指你有需求了,再去使用 type,也是一种学习新知识的方式。
An interface declaration is another way to name an object type:
typescript
interface Point {
x: number;
y: number;
}
H3Differences Between Interfaces & Type Aliases
Extending an interface:
Interfaces:
typescript
interface Animal {
name: string;
}
interface Bear extends Animal {
honey: boolean;
}
const bear = getBear();
bear.name;
bear.honey;
Type Aliases:
typescript
type Animal = {
name: string;
}
type Bear = Animal & {
honey: boolean;
}
Adding new fields to an existing interface, but a type cannot be changed after being created.
typescript
interface Window {
title: string;
}
interface Window {
ts: TypeScriptAPI;
}
H2Type Assertions
typescript
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
H2Literal Types
In addition to the general types string
and number
, we can refer to specific strings and numbers in type positions.
typescript
let x: "hello" = "hello";
// OK
x = "hello";
// ...
x = "howdy";
// Type '"howdy"' is not assignable to type '"hello"'.
It’s not much use to have a variable that can only have one value. But by combining literals into unions, you can express a much more useful concept.
typescript
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
function compare(a: string, b: string): -1 | 0 | 1 {
return a === b ? 0 : a > b ? 1 : -1;
}
H3Literal Inference
typescript
declare function handleRequest(url: string, method: "GET" | "POST"): void;
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
In the above example req.method
is inferred to be string
, not "GET"
.
You can use as const
to convert the entire object to be type literals:
const req = { url: "https://example.com", method: "GET" } as const;
H2null
and undefined
H3Non-null Assertion Operator (Postfix !
)
TypeScript also has a special syntax for removing null
and undefined
from a type without doing any explicit checking.
typescript
function liveDangerously(x?: number | null) {
// No error
console.log(x!.toFixed());
}
H1Narrowing
H2typeof
type guards
"string"
"number"
"bigint"
"boolean"
"symbol"
"undefined"
"object"
"function"
H2The in
operator narrowing
typescript
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
return animal.swim();
}
return animal.fly();
}
H2Discriminated unions
typescript
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
Updated Version:
typescript
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
}
}
H1More on Functions
H2Function Type Expressions
typescript
function greeter(fn: (a: string) => void) {
fn("Hello, World");
}
function printToConsole(s: string) {
console.log(s);
}
greeter(printToConsole);
// Of course, we can use a type alias to name a function type:
type GreetFunction = (a: string) => void;
function greeter(fn: GreetFunction) {
// ...
}
H1TSX
When you want to assign a React component type to a property of a TypeScript object, you can use React.ReactNode
as the type of property.
typescript
type MyObject = {
icon: React.ReactNode;
};
// or
import { ReactNode } from "react";
type MyObject = {
icon: ReactNode;
};
H1References
Videos:
Docs: