[ 万字总结 ] 夯实你的 TypeScript 基础!(二)(完结)
9.类型保护
通过判断识别所执行的代码块,自动识别变量属性和方法
一.typeof
类型保护
function double(val: number | string) {
if (typeof val === 'number') {
val
} else {
val
}
}
二.instanceof
类型保护
class Cat { }
class Dog { }
const getInstance = (clazz: { new(): Cat | Dog }) => {
return new clazz();
}
let r = getInstance(Cat);
if(r instanceof Cat){
r
}else{
r
}
三.in
类型保护
interface Fish {
swiming: string,
}
interface Bird {
fly: string,
leg: number
}
function getType(animal: Fish | Bird) {
if ('swiming' in animal) {
animal // Fish
} else {
animal // Bird
}
}
四.可辨识联合类型
interface WarningButton {
class: 'warning'
}
interface DangerButton {
class: 'danger'
}
function createButton(button: WarningButton | DangerButton) {
if (button.class == 'warning') {
button // WarningButton
} else {
button // DangerButton
}
}
五.null保护
const addPrefix = (num?: number) => {
num = num || 1.1;
function prefix(fix: string) {
return fix + num?.toFixed()
}
return prefix('zf');
}
console.log(addPrefix());
这里要注意的是ts无法检测内部函数变量类型
六.自定义类型保护
interface Fish {
swiming: string,
}
interface Bird {
fly: string,
leg: number
}
function isBird(animal: Fish | Bird):animal is Bird {
return 'swiming' in animal
}
function getAniaml (animal:Fish | Bird){
if(isBird(animal)){
animal
}else{
animal
}
}
七.完整性保护
interface ICircle {
kind: 'circle',
r: number
}
interface IRant {
kind: 'rant',
width: number,
height: number
}
interface ISquare {
kind: 'square',
width: number
}
type Area = ICircle | IRant | ISquare
const isAssertion = (obj: never) => { }
const getArea = (obj: Area) => {
switch (obj.kind) {
case 'circle':
return 3.14 * obj.r ** 2
default:
return isAssertion(obj); // 必须实现所有逻辑
}
}
10.类型推断
一.赋值推断
赋值时推断,类型从右像左流动,会根据赋值推断出变量类型
let str = 'wj';
let age = 11;
let boolean = true;
二.返回值推断
自动推断函数返回值类型
function sum(a: string, b: string) {
return a + b;
}
sum('a','b');
三.函数推断
函数从左到右进行推断
type Sum = (a: string, b: string) => string;
const sum: Sum = (a, b) => a + b;
四.属性推断
可以通过属性值,推断出属性的类型
let person = {
name:'zf',
age:11
}
let {name,age} = person;
五.类型反推
可以使用typeof
关键字反推变量类型
let person = {
name:'zf',
age:11
}
type Person = typeof person
六.索引访问操作符
interface IPerson {
name:string,
age:number,
job:{
address:string
}
}
type job = IPerson['job']
七.类型映射
interface IPerson {
name:string,
age:number
}
type MapPerson = {[key in keyof IPerson]:IPerson[key]}
11.交叉类型
交叉类型(Intersection Types)是将多个类型合并为一个类型
interface Person1 {
handsome: string,
}
interface Person2 {
high: string,
}
type P1P2 = Person1 & Person2;
let p: P1P2 = { handsome: '帅', high: '高' }
举例:我们提供两拨人,一拨人都很帅、另一拨人很高。我们希望找到他们的交叉部分 => 又高又帅的人
- 交叉类型
function mixin<T, K>(a: T, b: K): T & K {
return { ...a, ...b }
}
const x = mixin({ name: 'zf' }, { age: 11 })
interface IPerson1 {
name:string,
age:number
}
interface IPerson2 {
name:number
age:number
}
type person = IPerson1 & IPerson2
let name!:never
let person:person = {name,age:11}; // 两个属性之间 string & number的值为never
12.条件类型
一.条件类型基本使用
可以使用extends
关键字和三元表达式,实现条件判断
interface Fish {
name1: string
}
interface Water {
name2: string
}
interface Bird {
name3: string
}
interface Sky {
name4: string
}
type Condition<T> = T extends Fish ? Water : Sky;
let con1: Condition<Fish> = { name2: '水' }
二.条件类型分发
let con2: Condition<Fish|Bird> = { name2: '水' }
这里会用每一项依次进行分发,最终采用联合类型作为结果,等价于:
type c1 = Condition<Fish>;
type c2 = Condition<Bird>;
type c = c1 | c2
三.内置条件类型
- 1.
Exclude
排除类型
type Exclude<T, U> = T extends U ? never : T;
type MyExclude = Exclude<'1' | '2' | '3', '1' | '2'>
- 2.
Extract
抽取类型
type Extract<T, U> = T extends U ? T : never;
type MyExtract = Extract<'1' | '2' | '3', '1' | '2'>
- 3.
NoNullable
非空检测
type NonNullable<T> = T extends null | undefined ? never : T
type MyNone = NonNullable<'a' | null | undefined>
四.infer类型推断
- 1.
ReturnType
返回值类型
function getUser(a: number, b: number) {
return { name: 'zf', age: 10 }
}
type ReturnType<T> = T extends (...args: any) => infer R ? R : never
type MyReturn = ReturnType<typeof getUser>
- 2.
Parameters
参数类型
type Parameters<T> = T extends (...args: infer R) => any ? R : any;
type MyParams = Parameters<typeof getUser>;
- 3.
ConstructorParameters
构造函数参数类型
class Person {
constructor(name: string, age: number) { }
}
type ConstructorParameters<T> = T extends { new(...args: infer R): any } ? R : never
type MyConstructor = ConstructorParameters<typeof Person>
- 4.
InstanceType
实例类型
type InstanceType<T> = T extends { new(...args: any): infer R } ? R : any
type MyInstance = InstanceType<typeof Person>
五.infer实践
将数组类型转化为联合类型
type ElementOf<T> = T extends Array<infer E> ? E : never;
type TupleToUnion = ElementOf<[string, number, boolean]>;
将两个函数的参数转化为交叉类型
type T1 = { name: string };
type T2 = { age: number };
type ToIntersection<T> = T extends ([(x: infer U) => any, (x: infer U) => any]) ? U : never;
type t3 = ToIntersection<[(x:T1)=>any,(x:T2)=>any]>
表示要把
T1
、T2
赋予给x,那么x的值就是T1
、T2
的交集。(参数是逆变的可以传父类)
TS的类型:TS主要是为了代码的安全性来考虑。所以所有的兼容性问题都要从安全性来考虑!
13.内置类型
一.Partial转化可选属性
interface Company {
num: number
}
interface Person {
name: string,
age: string,
company: Company
}
// type Partial<T> = { [K in keyof T]?: T[K] }; 实现原理
type PartialPerson = Partial<Person>;
遍历所有的属性将属性设置为可选属性,但是无法实现深度转化!
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
type DeepPartialPerson = DeepPartial<Person>;
我们可以实现深度转化,如果值是对象继续深度转化。
二.Required转化必填属性
interface Company {
num: number
}
interface Person {
name: string,
age: string,
company: Company
}
type PartialPerson = Partial<Person>;
type Required<T> = {[K in keyof T]-?:T[K]}
type RequiredPerson = Required<PartialPerson>
将所有的属性转化成必填属性
三.Readonly转化仅读属性
type Readonly<T> = { readonly [K in keyof T]: T[K] }
type RequiredPerson = Readonly<Person>
将所有属性变为仅读状态
四.Pick挑选所需的属性
type Pick<T, U extends keyof T> = { [P in U]: T[P] }
type PickPerson = Pick<Person, 'name' | 'age'>
在已有类型中挑选所需属性
五.Record记录类型
type Record<K extends keyof any, T> = { [P in K] : T }
let person: Record<string, any> = { name: 'zf', age: 11 };
实现map方法,我们经常用record类型表示映射类型
function map<T extends keyof any, K, U>(obj: Record<T, K>, callback: (item: K, key: T) => U) {
let result = {} as Record<T, U>
for (let key in obj) {
result[key] = callback(obj[key], key)
}
return result
}
const r = map({ name: 'zf', age: 11 }, (item, key) => {
return item
});
六.Omit忽略属性
let person = {
name: 'wj',
age: 11,
address: '回龙观'
}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
type OmitAddress = Omit<typeof person, 'address'>
忽略person中的address属性 (先排除掉不需要的key,在通过key选出需要的属性)
14.装包和拆包
一.装包
type Proxy<T> = {
get():T,
set(value:T):void
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>
}
let props = {
name: 'wj',
age: 11
}
function proxify<T>(obj:T):Proxify<T>{
let result = {} as Proxify<T>;
for(let key in obj){
let value = obj[key];
result[key] = {
get(){
return value
},
set:(newValue)=>value = newValue
}
}
return result
}
let proxpProps = proxify(props);
二.拆包
function unProxify<T>(proxpProps:Proxify<T>):T{
let result = {} as T;
for(let key in proxpProps){
let value = proxpProps[key];
result[key] = value.get()
}
return result
}
let proxy = unProxify(proxpProps)
15.自定义类型
一.Diff实现
求两个对象不同的部分
let person1 = {
name: 'wj',
age: 11,
address: '回龙观'
}
let person2 = {
address: '回龙观',
}
type Diff<T extends object,K extends Object> = Omit<T,keyof K>
type DiffPerson = Diff<typeof person1,typeof person2>
二.InterSection交集
let person1 = {
name: 'wj',
age: 11,
address: '回龙观'
}
let person2 = {
address: '回龙观',
}
type InterSection<T extends object, K extends object> = Pick<T, Extract<keyof T, keyof K>>
type InterSectionPerson = InterSection<typeof person1, typeof person2>
三.Overwrite属性覆盖
type OldProps = { name: string, age: number, visible: boolean };
type NewProps = { age: string, other: string };
type Diff<T extends object,K extends Object> = Omit<T,keyof K>
type InterSection<T extends object, K extends object> = Pick<T, Extract<keyof T, keyof K>>
type Overwrite<T extends object, K extends object, I = Diff<T,K> & InterSection<K,T>> = Pick<I,keyof I>
type ReplaceProps = Overwrite<OldProps, NewProps>
如果存在已有属性则使用新属性类型进行覆盖操作
四.Merge对象合并
type Compute<A extends any> = { [K in keyof A]: A[K] };
type Merge<T, K> = Compute<Omit<T, keyof K> & K>;
type MergeObj = Merge<OldProps,NewProps>
将两个对象类型进行合并操作
16.unknown
一.unknown类型
unknown
类型,任何类型都可以赋值为unknown
类型。 它是 any 类型对应的安全类型
let unknown:unknown;
unknown = 'zf';
unknown = 11;
不能访问unknown类型上的属性,不能作为函数、类来使用
-
联合类型中的
unknown
type UnionUnknown = unknown | null | string | number
联合类型与
unknown
都是unknown
类型 -
交叉类型中的
unknown
type inter = unknown & null
交叉类型与
unknown
都是其他类型
二.unknown特性
-
never是unknown的子类型
type isNever = never extends unknown ? true : false;=
-
keyof unknown 是never
type key = keyof unknown;
-
unknown类型不能被遍历
type IMap<T> = { [P in keyof T]:number } type t = IMap<unknown>;
unknown类型不能和number类型进行
+
运算,可以用于等或不等操作
17.模块和命名空间
默认情况下 ,我们编写的代码处于全局命名空间中
一.模块
文件模块: 如果在你的 TypeScript 文件的根级别位置含有 import 或者 export,那么它会在这个文件中创建一个本地的作用域 。
// a.ts导出
export default 'zf'
// index.ts导入
import name from './a'
二.命名空间
命名空间可以用于组织代码,避免文件内命名冲突
-
命名空间的使用
export namespace zoo { export class Dog { eat() { console.log('zoo dog'); } } } export namespace home { export class Dog { eat() { console.log('home dog'); } } } let dog_of_zoo = new zoo.Dog(); dog_of_zoo.eat(); let dog_of_home = new home.Dog(); dog_of_home.eat();
-
命名空间嵌套使用
export namespace zoo { export class Dog { eat() { console.log('zoo dog'); } } export namespace bear{ export const name = '熊' } } console.log(zoo.bear.name);
命名空间中导出的变量可以通过命名空间使用。
18.类型声明
一.声明全局变量
- 普通类型声明
declare let age: number;
declare function sum(a: string, b: string): void;
declare class Animal { };
declare const enum Seaons{
Spring,
Summer,
Autumn,
Winter
}
declare interface Person {
name:string,
age:number
}
类型声明在编译的时候都会被删除,不会影响真正的代码。目的是不重构原有的js代码,而且可以得到很好的TS支持
练习: 声明jQuery类型
jquery通过外部CDN方式引入,想在代码中直接使用
declare const $:(selector:string)=>{
height(num?:number):void
width(num?:number):void
};
$('').height();
- 命名空间声明
declare namespace jQuery {
function ajax(url:string,otpions:object):void;
namespace fn {
function extend(obj:object):void
}
}
jQuery.ajax('/',{});
jQuery.fn.extend({});
namespace
表示一个全局变量包含很多子属性 , 命名空间内部不需要使用 declare 声明属性或方法
二. 类型声明文件
类型声明文件以
.d.ts
结尾。默认在项目编译时会查找所有以.d.ts
结尾的文件
// jquery.d.ts
declare const $:(selector:string)=>{
height(num?:number):void
width(num?:number):void
};
declare namespace jQuery {
function ajax(url:string,otpions:object):void;
namespace fn {
function extend(obj:object):void
}
}
三.编写第三方声明文件
配置tsconfig.json
- jquery声明文件
"moduleResolution": "node",
"baseUrl": "./",
"paths": {
"*": ["types/*"]
},
// types/jquery/index.d.ts
declare function jQuery(selector: string): HTMLElement;
declare namespace jQuery {
function ajax(url: string): void
}
export = jQuery;
- events模块声明文件
import { EventEmitter } from "zf-events";
var e = new EventEmitter();
e.on('message', function (text) {
console.log(text)
})
e.emit('message', 'hello');
export type Listener = (...args: any[]) => void;
export type Type = string | symbol
export class EventEmitter {
static defaultMaxListeners: number;
emit(type: Type, ...args: any[]): boolean;
addListener(type: Type, listener: Listener): this;
on(type: Type, listener: Listener): this;
once(type: Type, listener: Listener): this;
}
四.模块导入导出
import $ from 'jquery' // 只适用于 export default $
const $ = require('jquery'); // 没有声明文件可以直接使用 require语法
import * as $ from 'jquery' // 为了支持 Commonjs规范 和 AMD规范 导出时采用export = jquery
import $ = require('jquery') // export = jquery 在commonjs规范中使用
五.第三方声明文件
@types是一个约定的前缀,所有的第三方声明的类型库都会带有这样的前缀
npm install @types/jquery -S
当使用jquery时默认会查找
node_modules/@types/jquery/index.d.ts
文件
查找规范
- node_modules/jquery/package.json 中的types字段
- node_modules/jquery/index.d.ts
- node_modules/@types/jquery/index.d.ts
19.扩展全局变量类型
一.扩展局部变量
可以直接使用接口对已有类型进行扩展
interface String {
double():string
}
String.prototype.double = function () {
return this as string + this;
}
let str = 'wj';
interface Window {
mynane:string
}
console.log(window.mynane)
二.模块内全局扩展
declare global{
interface String {
double():string;
}
interface Window{
myname:string
}
}
声明全局表示对全局进行扩展
三.声明合并
同一名称的两个独立声明会被合并成一个单一声明,合并后的声明拥有原先两个声明的特性。
1.同名接口合并
interface Animal {
name:string
}
interface Animal {
age:number
}
let a:Animal = {name:'zf',age:10};
2.命名空间的合并
-
扩展类
class Form {} namespace Form { export const type = 'form' }
-
扩展方法
function getName(){} namespace getName { export const type = 'form' }
-
扩展枚举类型
enum Seasons { Spring = 'Spring', Summer = 'Summer' } namespace Seasons{ export let Autum = 'Autum'; export let Winter = 'Winter' }
3.交叉类型合并
import { createStore, Store } from 'redux';
type StoreWithExt = Store & {
ext:string
}
let store:StoreWithExt
四.生成声明文件
配置
tsconfig.json
为true 生成声明文件
"declaration": true
完结撒花
以上就是 TypeScript
基础的全部啦,掌握了这些对于面试已经是绰绰有余,不过对于语言来说还是需要多加练习哟。欢迎点赞收藏 !
转载自:https://juejin.cn/post/7102996554541170696