TS Basic Drive
更多请参考:https://chinese.freecodecamp.org/news/learn-typescript-beginners-guide/
什么是TS
TypeScript是JavaScript的超集,意味着它能做JavaScript所做的一切,但有一些附加功能。
why?
- JS是一门动态类型的预言,这意味着变量可以改变类型
- TS为JS添加静态类型,这意味着变量类型在程序的任何时候都不能被改变
注: TS不被浏览器理解,所以TS => (TSC编译器)编译 => JS
优点
- 帮助发现错误
- 更可读,帮助了解其他开发人员的意图
- 受欢迎
- 更好理解JS
缺点
- 更长的编写时间(小项目不值得使用)
- 必须编译(在大项目中更花时间)
花更多时间,编写更精确的代码然后编译,来减少代码错误
使用
// 安装
npm i -g typescript
// 检查
tsc -v
// index.ts
let sport = 'football';
let id = 5;
// 编译
tsc index
// 编译重命名
tsc index.ts --outfile file-name.js
// 监听改动自动编译
tsc index.ts -w
// 配置tsconfig.json
tsc --init
TS会编译代码,无论是否有错误,他假定开发者知道的更多。
类型
原始类型
- string
- number
- bigint
- boolean
- undefined
- null
- symbol
Primitives is immutable(原始值不可变)
let name = 'Danny';
name.toLowerCase();
console.log(name); // 字符串方法不改变字符串值
let arr = [1, 3, 5, 7];
arr.pop();
console.log(arr); // 数组方法改变数组值
name = 'Anna' // 赋新值,而非改变原值
包装对象
let firstname = new String('Danny');
console.log(firstname); // String {'Danny'}
除null和undefined,原始值均有对应的对象--包裹着原始值:String、Number、BigInt、Boolean和Symbol。
包装对象提供了允许原始值被操纵的方法。
:type
类型注解
let id: number = 5;
let firstname: string = 'danny';
let hasDog: boolean = true;
let unit: number; // Declare variable without assigning a value
unit = 5;
一般不必使用类型注解,TS可自动推断
let id = 5; // TS knows it's a number
let firstname = 'danny'; // TS knows it's a string
let hasDog = true; // TS knows it's a boolean
hasDog = 'yes'; // ERROR
联合类型
let age: string | number;
age = 26;
age = '26';
引用类型
- 数组
- 对象
- 函数
原始类型与引用类型
- 每个原始值都存储在内存中的一个唯一位置。
let x = 2;
let y = 1;
x = y;
y = 100;
console.log(x); // 1 (even though y changed to 100, x is still 1)
- 引用类型指向的是存储对象的一个内存位置
let point1 = { x: 1, y: 1 };
let point2 = point1;
point1.y = 100;
console.log(point2.y); // 100 (point1和point2多指向的是存储点对象的同一个内存地址)
TS数组
let ids: number[] = [1, 2, 3, 4, 5]; // can only contain numbers
let names: string[] = ['Danny', 'Anna', 'Bazza']; // can only contain strings
let options: boolean[] = [true, false, false]; can only contain true or false
let books: object[] = [
{ name: 'Fooled by randomness', author: 'Nassim Taleb' },
{ name: 'Sapiens', author: 'Yuval Noah Harari' },
]; // can only contain objects
let arr: any[] = ['hello', 1, true]; // any basically reverts TypeScript back into JavaScript
ids.push(6);
ids.push('7'); // ERROR: Argument of type 'string' is not assignable to parameter of type 'number'.
let person: (string | number | boolean)[] = ['Danny', 1, true];
person[0] = 100;
person[1] = {name: 'Danny'} // Error - person array can't contain objects
元组
元组是一个具有固定大小和已知数据类型的数组
let person: [string, number, boolean] = ['Danny', 1, true];
person[0] = 100; // Error - Value at index 0 can only be a string
TS对象
// Declare a variable called person with a specific object type annotation
let person: {
name: string;
location: string;
isProgrammer: boolean;
};
// Assign person to an object with all the necessary properties and value types
person = {
name: 'Danny',
location: 'UK',
isProgrammer: true,
};
person.isProgrammer = 'Yes'; // ERROR: should be a boolean
person = {
name: 'John',
location: 'US',
};
// ERROR: missing the isProgrammer property
interface(接口)
通常使用接口检查对象属性,特别是多个对象是否具有相同的特定属性和价值类型
interface Person {
name: string;
location: string;
isProgrammer: boolean;
}
let person1: Person = {
name: 'Danny',
location: 'UK',
isProgrammer: true,
};
let person2: Person = {
name: 'Sarah',
location: 'Germany',
isProgrammer: false,
};
函数属性
interface Speech {
sayHi(name: string): string; // JS func
sayBye: (name: string) => string; // ES6箭头函数
}
let sayStuff: Speech = {
sayHi: function (name: string) {
return `Hi ${name}`;
},
sayBye: (name: string) => `Bye ${name}`,
};
console.log(sayStuff.sayHi('Heisenberg')); // Hi Heisenberg
console.log(sayStuff.sayBye('Heisenberg')); // Bye Heisenberg
ts并不关心箭头函数还是普通函数。
TS函数
// Define a function called circle that takes a diam variable of type number, and returns a string
function circle(diam: number): string {
return 'The circumference is ' + Math.PI * diam;
}
console.log(circle(10)); // The circumference is 31.41592653589793
const circle = (diam: number): string => {
return 'The circumference is ' + Math.PI * diam;
};
console.log(circle(10)); // The circumference is 31.41592653589793
?:
可选参数
// c是联合类型,也时刻u西安参数
const add = (a: number, b: number, c?: number | string) => {
console.log(c);
return a + b;
};
console.log(add(5, 4, 'I could pass a number, string, or nothing here!'));
// I could pass a number, string, or nothing here!
// 9
:void
只声明,不返回
// Declare the varible sayHello, and give it a function signature that takes a string and returns nothing.
let sayHello: (name: string) => void;
// Define the function, satisfying its signature
sayHello = (name) => {
console.log('Hello ' + name);
};
sayHello('Danny'); // Hello Danny
any
动态类型
将TypeScript还原成JavaScript:
let age: any = '100';
age = 100;
age = {
years: 100,
months: 2,
};
类型别名
type StringOrNumber = string | number;
type PersonObject = {
name: string;
id: StringOrNumber;
};
const person1: PersonObject = {
name: 'John',
id: 1,
};
const person2: PersonObject = {
name: 'Delia',
id: 2,
};
const sayHello = (person: PersonObject) => {
return 'Hi ' + person.name;
};
const sayGoodbye = (person: PersonObject) => {
return 'Seeya ' + person.name;
};
TSDOM
TS不能访问DOM,因此不能确定DOM元素是否真的存在。
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
!
非空断言操作符
// 在这里,我们告诉TypeScript,我们确定这个锚点标签存在
const link = document.querySelector('a')!;
console.log(link.href); // www.freeCodeCamp.org
TS可以自动推理出HTMLAnchorElement
类型
类型转换
const form = document.getElementById('signup-form');
console.log(form.method);
// ERROR: Object is possibly 'null'.
// ERROR: Property 'method' does not exist on type 'HTMLElement'.
const form = document.getElementById('signup-form') as HTMLFormElement;
console.log(form.method); // post
event
TS内置的事件对象
const form = document.getElementById('signup-form') as HTMLFormElement;
form.addEventListener('submit', (e: Event) => {
e.preventDefault(); // prevents the page from refreshing
console.log(e.tarrget); // ERROR: Property 'tarrget' does not exist on type 'Event'. Did you mean 'target'?
});
TS类
创建Person
类
class Person {
name: string;
isCool: boolean;
pets: number;
constructor(n: string, c: boolean, p: number) {
this.name = n;
this.isCool = c;
this.pets = p;
}
sayHello() {
return `Hi, my name is ${this.name} and I have ${this.pets} pets`;
}
}
const person1 = new Person('Danny', false, 1);
const person2 = new Person('Sarah', 'yes', 6); // ERROR: Argument of type 'string' is not assignable to parameter of type 'boolean'.
console.log(person1.sayHello()); // Hi, my name is Danny and I have 1 pets
People
数组只包括Person
类构建的对象
let People: Person[] = [person1, person2];
访问修饰语
class Person {
readonly name: string; // This property is immutable - it can only be read
private isCool: boolean; // Can only access or modify from methods within this class
protected email: string; // Can access or modify from this class and subclasses
public pets: number; // Can access or modify from anywhere - including outside the class
constructor(n: string, c: boolean, e: string, p: number) {
this.name = n;
this.isCool = c;
this.email = e;
this.pets = p;
}
sayMyName() {
console.log(`Your not Heisenberg, you're ${this.name}`);
}
}
const person1 = new Person('Danny', false, 'dan@e.com', 1);
console.log(person1.name); // Fine
person1.name = 'James'; // Error: read only
console.log(person1.isCool); // Error: private property - only accessible within Person class
console.log(person1.email); // Error: protected property - only accessible within Person class and its subclasses
console.log(person1.pets); // Public property - so no problem
更简洁的写法:
class Person {
constructor(
readonly name: string,
private isCool: boolean,
protected email: string,
public pets: number
) {}
sayMyName() {
console.log(`Your not Heisenberg, you're ${this.name}`);
}
}
const person1 = new Person('Danny', false, 'dan@e.com', 1);
console.log(person1.name); // Danny
类的拓展
class Programmer extends Person {
programmingLanguages: string[];
constructor(
name: string,
isCool: boolean,
email: string,
pets: number,
pL: string[]
) {
// The super call must supply all parameters for base (Person) class, as the constructor is not inherited.
super(name, isCool, email, pets);
this.programmingLanguages = pL;
}
}
TS接口
接口定义了一个对象
interface Person {
name: string;
age: number;
}
function sayHi(person: Person) {
console.log(`Hi ${person.name}`);
}
sayHi({
name: 'John',
age: 48,
}); // Hi John
也可以使用一个类型别名来定义一个对象类型
type Person = {
name: string;
age: number;
};
function sayHi(person: Person) {
console.log(`Hi ${person.name}`);
}
sayHi({
name: 'John',
age: 48,
}); // Hi John
接口和类型别名
类型别名不能被重新打开以添加新的属性,而接口总是可以扩展的
- 接口拓展
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
const bear: Bear = {
name: "Winnie",
honey: true,
}
- 拓展类型别名
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
const bear: Bear = {
name: "Winnie",
honey: true,
}
接口和类
TypeScript文档推荐使用接口来定义对象,直到你需要使用一个类型的功能。
interface Person {
name: string
age: number
speak(sentence: string): void
}
const person1: Person = {
name: "John",
age: 48,
speak: sentence => console.log(sentence),
}
为什么不使用类?
- 接口只被TS使用。这意味着它不会被编译,也不会给JS增加臃肿。类是JS的特性,会被编译。
- 类本质上是一个对象工厂(也就是说,一个对象应该是什么样子的蓝图(blueprint),然后实现),而接口是一个仅用于类型检查的结构。
TS字面类型
// Union type with a literal type in each position
let favouriteColor: 'red' | 'blue' | 'green' | 'yellow';
favouriteColor = 'blue';
favouriteColor = 'crimson'; // ERROR: Type '"crimson"' is not assignable to type '"red" | "blue" | "green" | "yellow"'.
<T>
泛型
类型捕捉不到的问题
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; }'.
传入对象到addID
时,没有指定该对象有什么属性,TS不着调该对象有什么属性,因此报错。
// <T> is just the convention - e.g. we could use <X> or <A>
const addID = <T>(obj: T) => {
let id = Math.floor(Math.random() * 1000);
return { ...obj, id };
};
现在,当我们把一个对象传给addID时,我们已经告诉TypeScript捕捉类型--所以T变成我们传入的任何类型。addID现在会知道我们传入的对象有哪些属性。
传递任意类型的问题
let person1 = addID({ name: 'John', age: 40 });
let person2 = addID('Sally'); // Pass in a string - no problem
console.log(person1.id); // 271
console.log(person1.name); // John
console.log(person2.id);
console.log(person2.name); // ERROR: Property 'name' does not exist on type '"Sally" & { id: number; }'.
我们需要一个约束:我们需要告诉TypeScript只接受对象,通过使我们的通用类型T成为object的扩展:
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('Sally'); // ERROR: Argument of type 'string' is not assignable to parameter of type 'object'.
并没有完全解决问题,因为JS中,数组也是对象:
let person2 = addID(['Sally', 26]); // Pass in an array - no problem
console.log(person2.id); // 824
console.log(person2.name); // Error: Property 'name' does not exist on type '(string | number)[] & { id: number; }'.
解决:
const addID = <T extends { name: string }>(obj: T) => {
let id = Math.floor(Math.random() * 1000);
return { ...obj, id };
};
let person2 = addID(['Sally', 26]); // ERROR: argument should have a name property with string value
在参数和返回类型未知时,泛型允许你在组件中实现类型安全。
泛型用于描述两个值之间的对应关系。
EXAMPLE
实现一个接受多种类型的的函数
any
function logLength(a: any) {
console.log(a.length); // No error
return a;
}
let hello = 'Hello world';
logLength(hello); // 11
let 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 property
return a;
}
有反馈了:
interface hasLength {
length: number;
}
function logLength<T extends hasLength>(a: T) {
console.log(a.length);
return a;
}
let hello = 'Hello world';
logLength(hello); // 11
let howMany = 8;
logLength(howMany); // Error: numbers don't have length properties
带接口的泛型
// 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: 'John',
age: 48,
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: 'Delia',
age: 46,
documents: 'passport, P45',
};
TS枚举(Enums)
定义或声明相关值的集合,可以是数字或字符串,作为一组命名的常量
enum ResourceType {
BOOK,
AUTHOR,
FILM,
DIRECTOR,
PERSON,
}
console.log(ResourceType.BOOK); // 0
console.log(ResourceType.AUTHOR); // 1
// To start from 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
TS严格模式
开启
// tsconfig.json
"strict": true
没有隐含的any
function logName(a) {
// No error??
console.log(a.name);
}
logName(97);
// ERROR: Parameter 'a' implicitly has an 'any' type.
function logName(a) {
console.log(a.name);
}
严格的空值检查
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'.
// 修正
if (single) {
console.log(single.artist); // rolling stones
}
type narrowing(类型缩小)
// All trains must now have a type property equal to 'Train'
interface Train extends Vehicle {
type: 'Train';
carriages: number;
}
// All trains must now have a type property equal to 'Plane'
interface Plane extends Vehicle {
type: 'Plane';
wingSpan: number;
}
type PlaneOrTrain = Plane | Train;
function getSpeedRatio(v: PlaneOrTrain) {
if (v.type === 'Train') {
// TypeScript now knows that `v` is definitely a `Train`. It has narrowed down the type from the less specific `Plane | Train` type, into the more specific `Train` type
return v.topSpeed / v.carriages;
}
// If it's not a Train, TypeScript narrows down that `v` must be a Plane - smart!
return v.topSpeed / v.wingSpan;
}
let bigTrain: Train = {
type: 'Train',
topSpeed: 100,
carriages: 20,
};
console.log(getSpeedRatio(bigTrain)); // 5
TS&React
CRA
npx create-react-app my-app --template typescript
组件
// 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 {
name: string;
age: number;
}
const Person: React.FC<Props> = ({ name, age }) => {
return (
<div>
<div>{name}</div>
<div>{age}</div>
</div>
);
};
React hooks
useState()
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()
const Person: React.FC = () => {
// Initialise .current property to null
const inputRef = useRef<HTMLInputElement>(null);
return (
<div>
<input type='text' ref={inputRef} />
</div>
);
};
本文来自博客园,作者:沧浪浊兮,转载请注明原文链接:https://www.cnblogs.com/shixiu/p/16863137.html