TypeScript 终极初学者指南

大家好,我是若川。持续组织了8个月源码共读活动,感兴趣的可以点此加我微信 ruochuan12 参与,每周大家一起学习200行左右的源码,共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列

在过去的几年里 TypeScript 变得越来越流行,现在许多工作都要求开发人员了解 TypeScript,各大厂的大型项目基本都要求使用 TypeScript 编写。

如果你已经对 JavaScript 很熟了, TypeScript 基本上也能快速上手,下面是我整理的一些初学者必备的一些知识点,如果你已经是个 TS 高手了,可以期待我后续的文章了~

Typescript 简介

4641491375007387da72e0015d16cd4b.png

据官方描述:TypeScriptJavaScript 的超集,这意味着它可以完成 JavaScript 所做的所有事情,而且额外附带了一些能力。

JavaScript 本身是一种动态类型语言,这意味着变量可以改变类型。使用 TypeScript 的主要原因是就是为了给 JavaScript 添加静态类型。静态类型意味着变量的类型在程序中的任何时候都不能改变。它可以防止很多bug !

Typescript 值得学吗?

下面是学习 Typescript 的几个理由:

  • 研究表明,TypeScript 可以发现 15% 的常见 bug

  • TypeScript 可以让代码的可读性更好,你可以更好的理解代码是在做什么。

  • TypeScript 可以你申请到更多好工作。

  • 学习 TypeScript 可以使你对 JavaScript 有更好的理解和新的视角。

当然,使用 Typescript 也有一些缺点:

  • TypeScript 的编写时间比 JavaScript 要长,因为你必须要指定类型,对于一些较小的独立项目,可能不值使用。

  • TypeScript 需要编译,项目越大消耗时间越长。

但是,相比于提前发现更多的 bug,花更长的时间也是值得的。

TypeScript 中的类型

原始类型

JavaScript 中,有 7 种原始类型:

  • string

  • number

  • bigint

  • boolean

  • undefined

  • null

  • symbol

原始类型都是不可变的,你可以为原始类型的变量重新分配一个新值,但不能像更改对象、数组和函数一样更改它的值。可以看下面的例子:

let name = 'ConardLi';
name.toLowerCase();
console.log(name); // ConardLi - 字符串的方法并没有改变字符串本身let arr = [1, 3, 5, 7];
arr.pop();
console.log(arr); // [1, 3, 5] - 数组的方法改变了数组

回到 TypeScript ,我们可以在声明一个变量之后设置我们想要添加的类型 :type (我们一般称之为“类型注释”或“类型签名”):

let id: number = 5;
let firstname: string = 'ConardLi';
let hasDog: boolean = true;let unit: number; // 声明变量而不赋值
unit = 5;

但是,如果变量有默认值的话,一般我们也不需要显式声明类型,TypeScript  会自动推断变量的类型(类型推断):

let id = 5; // number 类型
let firstname = 'ConardLi'; // string 类型
let hasDog = true; // boolean 类型hasDog = 'yes'; // ERROR

我们还可以将变量设置为联合类型(联合类型是可以分配多个类型的变量):

let age: string | number;
age = 17;
age = '17';

TypeScript 中的数组

TypeScript 中,你可以定义数组包含的数据类型:

let ids: number[] = [1, 2, 3, 4, 5]; // 只能包含 number
let names: string[] = ['ConardLi', 'Tom', 'Jerry']; // 只能包含 string
let options: boolean[] = [true, false, false]; 只能包含 true false
let books: object[] = [{ name: 'Tom', animal: 'cat' },{ name: 'Jerry', animal: 'mouse' },
]; // 只能包含对象
let arr: any[] = ['hello', 1, true]; // 啥都行,回到了 JSids.push(6);
ids.push('7'); // ERROR: Argument of type 'string' is not assignable to parameter of type 'number'.

你也可以使用联合类型来定义包含多种类型的数组:

let person: (string | number | boolean)[] = ['ConardLi', 1, true];
person[0] = 100;
person[1] = {name: 'ConardLi'} // Error - person array can't contain objects

如果数组有默认值, TypeScript 同样也会进行类型推断:

let person = ['ConardLi', 1, true]; // 和上面的例子一样
person[0] = 100;
person[1] = { name: 'ConardLi' }; // Error - person array can't contain objects

TypeScript 中可以定义一种特殊类型的数组:元组(Tuple)。元组是具有固定大小和已知数据类型的数组,它比常规数组更严格。

let person: [string, number, boolean] = ['ConardLi', 1, true];
person[0] = 17; // Error - Value at index 0 can only be a string

TypeScript 中的对象

TypeScript 中的对象必须拥有所有正确的属性和值类型:

// 使用特定的对象类型注释声明一个名为 person 的变量
let person: {name: string;age: number;isProgrammer: boolean;
};// 给 person 分配一个具有所有必要属性和值类型的对象
person = {name: 'ConardLi',age: 17,isProgrammer: true,
};person.age = '17'; // ERROR: should be a numberperson = {name: 'Tom',age: 3,
}; 
// ERROR: missing the isProgrammer property

在定义对象的类型时,我们通常会使用 interface。如果我们需要检查多个对象是否具有相同的特定属性和值类型时,是很有用的:

interface Person {name: string;age: number;isProgrammer: boolean;
}let person1: Person = {name: 'ConardLi',age: 17,isProgrammer: true,
};let person2: Person = {name: 'Tom',age: 3,isProgrammer: false,
};

我们还可以用函数的类型签名声明一个函数属性,通用函数(sayHi)和箭头函数(sayBye)都可以声明:

interface Animal {eat(name: string): string;speak: (name: string) => string;
}let tom: Animal = {eat: function (name: string) {return `eat ${name}`;},speak: (name: string) => `speak ${name}`,
};console.log(tom.eat('Jerry'));
console.log(tom.speak('哈哈哈'));

需要注意的是,虽然 eat、speak 分别是用普通函数和箭头函数声明的,但是它们具体是什么样的函数类型都可以,Typescript 是不关心这些的。

TypeScript 中的函数

我们可以定义函数参数和返回值的类型:

// 定义一个名为 circle 的函数,它接受一个类型为 number 的直径变量,并返回一个字符串
function circle(diam: number): string {return '圆的周长为:' + Math.PI * diam;
}console.log(circle(10)); // 圆的周长为:31.41592653589793

ES6 箭头函数的写法:

const circle = (diam: number): string => {return '圆的周长为:' + Math.PI * diam;
};

我们没必要明确声明 circle 是一个函数,TypeScript 会进行类型推断。TypeScript 还会推断函数的返回类型,但是如果函数体比较复杂,还是建议清晰的显式声明返回类型。

我们可以在参数后添加一个?,表示它为可选参数;另外参数的类型也可以是一个联合类型:

const add = (a: number, b: number, c?: number | string) => {console.log(c);return a + b;
};console.log(add(5, 4, '可以是 number、string,也可以为空'));

如果函数没有返回值,在 TS 里表示为返回 void,你也不需要显式声明,TS 一样可以进行类型推断:

const log = (msg: string): void => {console.log('打印一些内容: ' + msg);
};

any 类型

使 any 类型,我们基本上可以将 TypeScript 恢复为 JavaScript

let name: any = 'ConardLi';
name = 17;
name = { age: 17 };

如果代码里使用了大量的 any,那 TypeScript 也就失去了意义,所以我们应该尽量避免使用 any

DOM 和类型转换

TypeScript 没办法像 JavaScript 那样访问 DOM。这意味着每当我们尝试访问 DOM 元素时,TypeScript 都无法确定它们是否真的存在。

const link = document.querySelector('a');console.log(link.href); // ERROR: Object is possibly 'null'. TypeScript can't be sure the anchor tag exists, as it can't access the DOM

使用非空断言运算符 (!),我们可以明确地告诉编译器一个表达式的值不是 nullundefined。当编译器无法准确地进行类型推断时,这可能很有用:

// 我们明确告诉 TS a 标签肯定存在
const link = document.querySelector('a')!;console.log(link.href); // conardli.top

这里我们没必要声明 link 变量的类型。这是因为 TypeScript 可以通过类型推断确认它的类型为 HTMLAnchorElement

但是如果我们需要通过 classid 来选择一个 DOM 元素呢?这时 TypeScript 就没办法推断类型了:

const form = document.getElementById('signup-form');console.log(form.method);
// ERROR: Object is possibly 'null'.
// ERROR: Property 'method' does not exist on type 'HTMLElement'.

我们需要告诉 TypeScript form 确定是存在的,并且我们知道它的类型是  HTMLFormElement。我们可以通过类型转换来做到这一点:

const form = document.getElementById('signup-form') as HTMLFormElement;console.log(form.method); // post

TypeScript 还内置了一个 Event 对象。如果我们在表单中添加一个 submit 的事件侦听器,TypeScript 可以自动帮我们推断类型错误:

const form = document.getElementById('signup-form') as HTMLFormElement;form.addEventListener('submit', (e: Event) => {e.preventDefault(); // 阻止页面刷新console.log(e.tarrget); // ERROR: Property 'tarrget' does not exist on type 'Event'. Did you mean 'target'?
});

TypeScript 中的类

我们可以定义类中每条数据的类型:

class Person {name: string;isCool: boolean;age: number;constructor(n: string, c: boolean, a: number) {this.name = n;this.isCool = c;this.age = a;}sayHello() {return `Hi,我是 ${this.name} ,我今年 ${this.age} 岁了`;}
}const person1 = new Person('ConardLi', true, 17);
const person2 = new Person('Jerry', 'yes', 20); // ERROR: Argument of type 'string' is not assignable to parameter of type 'boolean'.console.log(person1.sayHello()); // Hi, 我是 ConardLi,我今年 17 岁了

我们可以创建一个仅包含从 Person 构造的对象数组:

let People: Person[] = [person1, person2];

我们可以给类的属性添加访问修饰符,TypeScript 还提供了一个新的 readonly 访问修饰符。

class Person {readonly name: string; // 不可以变的private isCool: boolean; // 类的私有属性、外部访问不到protected email: string; // 只能从这个类和子类中进行访问和修改public age: number; // 任何地方都可以访问和修改constructor(n: string, c: boolean, a: number) {this.name = n;this.isCool = c;this.age = a;}sayHello() {return `Hi,我是 ${this.name} ,我今年 ${this.age} 岁了`;}
}const person1 = new Person('ConardLi', true, 'conard@xx.com', 17);
console.log(person1.name); // ConardLi
person1.name = 'Jerry'; // Error: read only

我们可以通过下面的写法,属性会在构造函数中自动分配,我们类会更加简洁:

class Person {constructor(readonly name: string,private isCool: boolean,protected email: string,public age: number) {}
}

如果我们省略访问修饰符,默认情况下属性都是 public,另外和 JavaScript 一样,类也是可以 extends 的。

TypeScript 中的接口

接口定义了对象的外观:

interface Person {name: string;age: number;
}function sayHi(person: Person) {console.log(`Hi ${person.name}`);
}sayHi({name: 'ConardLi',age: 17,
}); // Hi ConardLi

你还可以使用类型别名定义对象类型:

type Person = {name: string;age: number;
};

或者可以直接匿名定义对象类型:

function sayHi(person: { name: string; age: number }) {console.log(`Hi ${person.name}`);
}

interfacetype 非常相似,很多情况下它俩可以随便用。比如它们两个都可以扩展:

扩展 interface

interface Animal {name: string
}interface Bear extends Animal {honey: boolean
}const bear: Bear = {name: "Winnie",honey: true,
}

扩展 type

type Animal = {name: string
}type Bear = Animal & {honey: boolean
}const bear: Bear = {name: "Winnie",honey: true,
}

但是有个比较明显的区别,interface 是可以自动合并类型的,但是 type 不支持:

interface Animal {name: string
}interface Animal {tail: boolean
}const dog: Animal = {name: "Tom",tail: true,
}

类型别名在创建后无法更改:

type Animal = {name: string
}type Animal = {tail: boolean
}
// ERROR: Duplicate identifier 'Animal'.

一般来说,当你不知道用啥的时候,默认就用 interface 就行,直到 interface 满足不了我们的需求的时候再用 type

类的 interface

我们可以通过实现一个接口来告诉一个类它必须包含某些属性和方法:

interface HasFormatter {format(): string;
}class Person implements HasFormatter {constructor(public username: string, protected password: string) {}format() {return this.username.toLocaleLowerCase();}
}let person1: HasFormatter;
let person2: HasFormatter;person1 = new Person('ConardLi', 'admin123');
person2 = new Person('Tom', 'admin123');console.log(person1.format()); // conardli

确保 people 是一个实现 HasFormatter 的对象数组(确保每 people 都有 format 方法):

let people: HasFormatter[] = [];
people.push(person1);
people.push(person2);

泛型

泛型可以让我们创建一个可以在多种类型上工作的组件,它能够支持当前的数据类型,同时也能支持未来的数据类型,这大大提升了组件的可重用性。我们来看下面这个例子:

addID 函数接受一个任意对象,并返回一个新对象,其中包含传入对象的所有属性和值,以及一个 01000 之间随机的 id 属性。

const addID = (obj: object) => {let id = Math.floor(Math.random() * 1000);return { ...obj, id };
};let person1 = addID({ name: 'John', age: 40 });console.log(person1.id); // 271
console.log(person1.name); // ERROR: Property 'name' does not exist on type '{ id: number; }'.

当我们尝试访问 name 属性时,TypeScript 会出错。这是因为当我们将一个对象传递给 addID 时,我们并没有指定这个对象应该有什么属性 —— 所以 TypeScript 不知道这个对象有什么属性。因此,TypeScript 知道的唯一属性返回对象的 id

那么,我们怎么将任意对象传递给 addID,而且仍然可以告诉 TypeScript 该对象具有哪些属性和值?这种场景就可以使用泛型了, <T>T 被称为类型参数:

// <T> 只是一种编写习惯 - 我们也可以用 <X> 或 <A>
const addID = <T>(obj: T) => {let id = Math.floor(Math.random() * 1000);return { ...obj, id };
};

这是啥意思呢?现在当我们再将一个对象传递给 addID 时,我们已经告诉 TypeScript 来捕获它的类型了 —— 所以 T 就变成了我们传入的任何类型。addID 现在会知道我们传入的对象上有哪些属性。

但是,现在有另一个问题:任何东西都可以传入 addIDTypeScript 将捕获类型而且并不会报告问题:

let person1 = addID({ name: 'ConardLi', age: 17 });
let person2 = addID('Jerry'); // 传递字符串也没问题console.log(person1.id); // 188
console.log(person1.name); // ConardLiconsole.log(person2.id);
console.log(person2.name); // ERROR: Property 'name' does not exist on type '"Jerry" & { id: number; }'.

当我们传入一个字符串时,TypeScript 没有发现任何问题。只有我们尝试访问 name 属性时才会报告错误。所以,我们需要一个约束:我们需要通过将泛型类型 T 作为 object 的扩展,来告诉 TypeScript 只能接受对象:

const addID = <T extends object>(obj: T) => {let id = Math.floor(Math.random() * 1000);return { ...obj, id };
};let person1 = addID({ name: 'John', age: 40 });
let person2 = addID('Jerry'); // ERROR: Argument of type 'string' is not assignable to parameter of type 'object'.

错误马上就被捕获了,完美…… 好吧,也不完全是。在 JavaScript 中,数组也是对象,所以我们仍然可以通过传入数组来逃避类型检查:

let person2 = addID(['ConardLi', 17]); // 传递数组没问题console.log(person2.id); // 188
console.log(person2.name); // Error: Property 'name' does not exist on type '(string | number)[] & { id: number; }'.

要解决这个问题,我们可以这样说:object 参数应该有一个带有字符串值的 name 属性:

const addID = <T extends { name: string }>(obj: T) => {let id = Math.floor(Math.random() * 1000);return { ...obj, id };
};let person2 = addID(['ConardLi', 17]); // ERROR: argument should have a name property with string value

泛型允许在参数和返回类型提前未知的组件中具有类型安全。

TypeScript 中,泛型用于描述两个值之间的对应关系。在上面的例子中,返回类型与输入类型有关。我们用一个泛型来描述对应关系。

另一个例子:如果需要接受多个类型的函数,最好使用泛型而不是 any 。下面展示了使用 any 的问题:

function logLength(a: any) {console.log(a.length); // No errorreturn a;
}let hello = 'Hello world';
logLength(hello); // 11let howMany = 8;
logLength(howMany); // undefined (but no TypeScript error - surely we want TypeScript to tell us we've tried to access a length property on a number!)

我们可以尝试使用泛型:

function logLength<T>(a: T) {console.log(a.length); // ERROR: TypeScript isn't certain that `a` is a value with a length propertyreturn a;
}

好,至少我们现在得到了一些反馈,可以帮助我们持续改进我们的代码。

解决方案:使用一个泛型来扩展一个接口,确保传入的每个参数都有一个 length 属性:

interface hasLength {length: number;
}function logLength<T extends hasLength>(a: T) {console.log(a.length);return a;
}let hello = 'Hello world';
logLength(hello); // 11let howMany = 8;
logLength(howMany); // Error: numbers don't have length properties

我们也可以编写这样一个函数,它的参数是一个元素数组,这些元素都有一个 length 属性:

interface hasLength {length: number;
}function logLengths<T extends hasLength>(a: T[]) {a.forEach((element) => {console.log(element.length);});
}let arr = ['This string has a length prop',['This', 'arr', 'has', 'length'],{ material: 'plastic', length: 17 },
];logLengths(arr);
// 29
// 4
// 30

泛型是 TypeScript 的一个很棒的特性!

泛型接口

当我们不知道对象中的某个值是什么类型时,可以使用泛型来传递该类型:

// The type, T, will be passed in
interface Person<T> {name: string;age: number;documents: T;
}// We have to pass in the type of `documents` - an array of strings in this case
const person1: Person<string[]> = {name: 'ConardLi',age: 17,documents: ['passport', 'bank statement', 'visa'],
};// Again, we implement the `Person` interface, and pass in the type for documents - in this case a string
const person2: Person<string> = {name: 'Tom',age: 20,documents: 'passport, P45',
};

枚举

枚举是 TypeScriptJavaScript 带来的一个特殊特性。枚举允许我们定义或声明一组相关值,可以是数字或字符串,作为一组命名常量。

enum ResourceType {BOOK,AUTHOR,FILM,DIRECTOR,PERSON,
}console.log(ResourceType.BOOK); // 0
console.log(ResourceType.AUTHOR); // 1// 从 1 开始
enum ResourceType {BOOK = 1,AUTHOR,FILM,DIRECTOR,PERSON,
}console.log(ResourceType.BOOK); // 1
console.log(ResourceType.AUTHOR); // 2

默认情况下,枚举是基于数字的 — 它们将字符串值存储为数字。但它们也可以是字符串:

enum Direction {Up = 'Up',Right = 'Right',Down = 'Down',Left = 'Left',
}console.log(Direction.Right); // Right
console.log(Direction.Down); // Down

当我们有一组相关的常量时,枚举就可以派上用场了。例如,与在代码中使用非描述性数字不同,枚举通过描述性常量使代码更具可读性。

枚举还可以防止错误,因为当你输入枚举的名称时,智能提示将弹出可能选择的选项列表。

TypeScript 严格模式

建议在 tsconfig.json 中启用所有严格的类型检查操作文件。这可能会导致 TypeScript 报告更多的错误,但也更有助于帮你提前发现发现程序中更多的 bug

// tsconfig.json"strict": true

严格模式实际上就意味着:禁止隐式 any 和 严格的空检查。

禁止隐式 any

在下面的函数中,TypeScript 已经推断出参数 aany 类型的。当我们向该函数传递一个数字,并尝试打印一个 name 属性时,没有报错:

function logName(a) {// No error??console.log(a.name);
}logName(97);

打开 noImplicitAny 选项后,如果我们没有显式地声明 a 的类型,TypeScript 将立即标记一个错误:

// ERROR: Parameter 'a' implicitly has an 'any' type.
function logName(a) {console.log(a.name);
}

严格的空检查

strictNullChecks 选项为 false 时,TypeScript 实际上会忽略 nullundefined。这可能会在运行时导致意外错误。

strictNullChecks 设置为 true 时,nullundefined 有它们自己的类型,如果你将它们分配给一个期望具体值(例如,字符串)的变量,则会得到一个类型错误。

let whoSangThis: string = getSong();const singles = [{ song: 'touch of grey', artist: 'grateful dead' },{ song: 'paint it black', artist: 'rolling stones' },
];const single = singles.find((s) => s.song === whoSangThis);console.log(single.artist);

singles.find 并不能保证它一定能找到这首歌 — 但是我们已经编写了下面的代码,好像它肯定能找到一样。

通过将 strictNullChecks 设置为 trueTypeScript 将抛出一个错误,因为在尝试使用它之前,我们没有保证 single 一定存在:

const getSong = () => {return 'song';
};let whoSangThis: string = getSong();const singles = [{ song: 'touch of grey', artist: 'grateful dead' },{ song: 'paint it black', artist: 'rolling stones' },
];const single = singles.find((s) => s.song === whoSangThis);console.log(single.artist); // ERROR: Object is possibly 'undefined'.

TypeScript 基本上是告诉我们在使用 single 之前要确保它存在。我们需要先检查它是否为 nullundefined

if (single) {console.log(single.artist); // rolling stones
}

TypeScript 中的类型收窄

TypeScript 中,变量可以从不太精确的类型转移到更精确的类型,这个过程称为类型收窄。

下面是一个简单的例子,展示了当我们使用带有 typeofif 语句时,TypeScript 如何将不太特定的 string | number 缩小到更特定的类型:

function addAnother(val: string | number) {if (typeof val === 'string') {// ts 将 val 视为一个字符串return val.concat(' ' + val);}// ts 知道 val 在这里是一个数字return val + val;
}console.log(addAnother('哈哈')); // 哈哈 哈哈
console.log(addAnother(17)); // 34

另一个例子:下面,我们定义了一个名为 allVehicles 的联合类型,它可以是 PlaneTrain 类型。

interface Vehicle {topSpeed: number;
}interface Train extends Vehicle {carriages: number;
}interface Plane extends Vehicle {wingSpan: number;
}type PlaneOrTrain = Plane | Train;function getSpeedRatio(v: PlaneOrTrain) {console.log(v.carriages); // ERROR: 'carriages' doesn't exist on type 'Plane'
}

由于 getSpeedRatio 函数处理了多种类型,我们需要一种方法来区分 vPlane 还是 Train 。我们可以通过给这两种类型一个共同的区别属性来做到这一点,它带有一个字符串值:

interface Train extends Vehicle {type: 'Train';carriages: number;
}interface Plane extends Vehicle {type: 'Plane';wingSpan: number;
}type PlaneOrTrain = Plane | Train;

现在,TypeScript 可以缩小 v 的类型:

function getSpeedRatio(v: PlaneOrTrain) {if (v.type === 'Train') {return v.topSpeed / v.carriages;}// 如果不是 Train,ts 知道它就是 Plane 了,聪明!return v.topSpeed / v.wingSpan;
}let bigTrain: Train = {type: 'Train',topSpeed: 100,carriages: 20,
};console.log(getSpeedRatio(bigTrain)); // 5

另外,我们还可以通过实现一个类型保护来解决这个问题,可以看看这篇文章:什么是鸭子🦆类型?

TypeScript & React

TypeScript 完全支持 React 和 JSX。这意味着我们可以将 TypeScript 与三个最常见的 React 框架一起使用:

  • create-react-app (https://create-react-app.dev/docs/adding-typescript/)

  • Gatsby (https://www.gatsbyjs.com/docs/how-to/custom-configuration/typescript/)

  • Next.js (https://nextjs.org/learn/excel/typescript)

如果你需要一个更自定义的 React-TypeScript 配置,你可以字节配置 Webpacktsconfig.json。但是大多数情况下,一个框架就可以完成这项工作。

例如,要用 TypeScript 设置 create-react-app,只需运行:

npx create-react-app my-app --template typescript# oryarn create react-app my-app --template typescript

src 文件夹中,我们现在可以创建带有 .ts (普通 TypeScript 文件)或 .tsx (带有 ReactTypeScript 文件)扩展名的文件,并使用 TypeScript 编写我们的组件。然后将其编译成 public 文件夹中的 JavaScript

React props & TypeScript

Person 是一个 React 组件,它接受一个 props 对象,其中 name 应该是一个字符串,age 是一个数字。

// src/components/Person.tsx
import React from 'react';const Person: React.FC<{name: string;age: number;
}> = ({ name, age }) => {return (<div><div>{name}</div><div>{age}</div></div>);
};export default Person;

一般我们更喜欢用 interface 定义 props

interface Props {name: string;age: number;
}const Person: React.FC<Props> = ({ name, age }) => {return (<div><div>{name}</div><div>{age}</div></div>);
};

然后我们尝试将组件导入到 App.tsx,如果我们没有提供必要的 propsTypeScript 会报错。

import React from 'react';
import Person from './components/Person';const App: React.FC = () => {return (<div><Person name='ConardLi' age={17} /></div>);
};export default App;

React hooks & TypeScript

useState()

我们可以用尖括号来声明状态变量的类型。如果我们省略了尖括号,TypeScript 会默认推断 cash 是一个数字。因此,如果想让它也为空,我们必须指定:

const Person: React.FC<Props> = ({ name, age }) => {const [cash, setCash] = useState<number | null>(1);setCash(null);return (<div><div>{name}</div><div>{age}</div></div>);
};

useRef()

useRef 返回一个可变对象,该对象在组件的生命周期内都是持久的。我们可以告诉 TypeScript   ref 对象应该指向什么:

const Person: React.FC = () => {// Initialise .current property to nullconst inputRef = useRef<HTMLInputElement>(null);return (<div><input type='text' ref={inputRef} /></div>);
};

参考

  • https://www.typescriptlang.org/docs/

  • https://react-typescript-cheatsheet.netlify.app/

  • https://www.freecodecamp.org/news/learn-typescript-beginners-guide

好了,这篇文章我们学习了一些 Typescript 的必备基础,有了这些知识你已经可以应付大部分 TS 的应用场景了,后续我会出一些 TS 的高级技巧相关的文章,敬请期待吧 ~

98fb42cea2ec850b06a8474bdc8a4d06.gif

················· 若川简介 ·················

你好,我是若川,毕业于江西高校。现在是一名前端开发“工程师”。写有《学习源码整体架构系列》20余篇,在知乎、掘金收获超百万阅读。
从2014年起,每年都会写一篇年度总结,已经坚持写了8年,点击查看年度总结。
同时,最近组织了源码共读活动,帮助3000+前端人学会看源码。公众号愿景:帮助5年内前端人走向前列。

4ddbb0dc8c8b6359b8b765d8c8589ab6.png

扫码加我微信 ruochuan02、拉你进源码共读

今日话题

目前建有江西|湖南|湖北 籍 前端群,想进群的可以加我微信 ruochuan12 进群分享、收藏、点赞、在看我的文章就是对我最大的支持~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/274761.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

标记偏见_如何(巧妙地)扭曲视觉效果以支持您的偏见叙事

标记偏见Data is important — it is the logical justification for world-changing decisions. Unfortunately, arrays of numbers don’t tell as interpretable a story as a picture does, providing an insatiable need for data visualizations.数据很重要-这是改变世界…

高瓴投资,顶配创业团队,dora 诚招前端 / Flutter

dora 是一个可以跨越设计稿&#xff0c;直接生成应用的新一代设计工具。让任何 Creator 都能轻松构建个性化的网站和应用&#xff0c;无需编写一行代码。通过自主研发的全新技术&#xff0c;我们为用户打造了完全自由度的设计与开发体验&#xff0c;足以满足任何复杂场景的个性…

猎鹰spacex_SpaceX:简单,美观的界面是未来

猎鹰spacex重点 (Top highlight)A photo has been floating around the internet showing how the interior of the new Dragon spacecraft differs from the Space Shuttle. The difference is staggering, but not entirely suprprising. After all the Shuttle started oper…

开发交接文档_为开发人员创造更好的设计交接体验

开发交接文档It’s 2020. We’re supposed to have flying cars and space travel. We should at least have our process for design handoff nailed down at this point.现在是2020年。我们应该有飞行汽车和太空旅行。 在这一点上&#xff0c;我们至少应该确定我们的设计移交…

​Cookie 从入门到进阶:一文彻底弄懂其原理以及应用

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列Cooki…

ui设计师常用的设计工具_2020年应该使用哪个UI设计工具?

ui设计师常用的设计工具重点 (Top highlight)It’s 2020, the market today is saturated with UI design tools. Ever since Sketch app came out with its sleek, simple, and efficient tool to craft user interface design, many companies have followed suit to take a …

你不知道的vscode之空间控制

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列王志远…

lynda ux_UX心态

lynda uxI have had the pleasure of training and mentoring several UX people at the beginning of their careers.在职业生涯的初期&#xff0c;我很高兴接受培训和指导。 Whatever your background or experience, I’ve found repeatedly that there are some key miles…

什么 Leader 值得追随?

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。目前建有江西|湖南|湖北 籍 前端群&#xff0c;想进群的可以加我微信 ruochuan12 进群。历…

pico8 掌机_使用Pico-8构建自己的复古游戏

pico8 掌机An example of the kinds of pixel animations people make in Pico-8.人们在Pico-8中制作的各种像素动画的示例。 Are you a fan of old school video games? What if I told you there’s an NES-style game devkit with the sound/sprite/code tools all built i…

实用 JavaScript 调试技巧

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

数据挖掘 点击更多 界面_6(更多)技巧,可快速改善用户界面

数据挖掘 点击更多 界面重点 (Top highlight)Creating beautiful, usable, and efficient UIs takes time, with many design revisions along the way.创建漂亮&#xff0c;可用和高效的UI需要花费时间&#xff0c;并且在此过程中进行了许多设计修订。 Making those constant…

Koa在实际的业务场景中,路由如何做分割?【文末留言送书】

大家好&#xff0c;我是若川。文末留言送书&#xff0c;具体规则文末说明。另外为了鼓励大家多写源码共读笔记&#xff0c;我会在写了5次及以上笔记的作者群里也抽奖送这本书。以后也会有更多福利倾斜。导读&#xff1a;Koa是一个Node框架&#xff0c;在Node开源社区中&#xf…

设计模式_设计

设计模式Thanks for my colleague WanChing‘s help to prepare this sharing article. E-Commerce app collects plentiful products from various brands. Each brand has its brand signature colors and public image. This article introduces how we made a single page …

使用 GTD 优化自己的工作和生活

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

模仿不再受宠若惊

If you haven’t heard of the Jio-Zoom plagiarism clash, you’re probably living under a rock (which may not be a bad idea given the state of the world right now). The turf war between Jio Meet and Zoom began when the Indian telecom giant ripped off the Chi…

Vue2剥丝抽茧-响应式系统 系列

大家好&#xff0c;我是若川。持续组织了8个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。历史面试系列。另外…

word文本样式代码样式_使用文本样式表达创建真相来源

word文本样式代码样式As of After Effects 17.0, you can use expressions to edit text styles in After Effects. Here’s why this would transform your workflow:从After Effects 17.0开始&#xff0c;您可以使用表达式在After Effects中编辑文本样式。 这就是这将改变您的…

前端框架源码解读之Vite

前端工具链十年盘点&#xff1a;https://mp.weixin.qq.com/s/FBxVpcdVobgJ9rGxRC2zfgWebpack、Rollup 、Esbuild、Vite ?webpack: 基于 JavaScript 开发的前端打包构建框架&#xff0c;通过依赖收集&#xff0c;模块解析&#xff0c;生成 chunk&#xff0c;最终输出生成的打包…

hp-ux_UX中的格式塔-或-为什么设计师如此讨厌间距

hp-uxI’ve been lucky so far in my design career to have worked with engineers that seem genuinely interested in learning about design. Perhaps, as mentioned in the title, it’s more about them trying to figure out why it matters so much to us that there i…