TypeScript的概念
TypeScript是什么?
TypeScript是JavaScript类型的超集,它可以编译成纯JavaScript。
TypeScript可以在任何浏览器、任何计算机和任何操作系统上运行,并且是开源的。
Typescript的优缺点
- 优点
- 在代码书写和编译阶段进行错误提示
- 借助编辑器可以扩展更多功能,包括代码提示补全、接口提示等
- 可以直接编译成js文件
- 缺点
- 增加了学习成本,其中涉及到很多非前端开发工程师所理解的知识点
- 增加了开发的时间成本,因为相比js要定义很多类型。
将TypeScript安装到全局
yarn add typescript -g
yarn add ts-node -g // 用于执行ts文件(会进行语法检查,速度慢)
yarn add esno -g // 用于执行ts文件(不进行语法检查,速度快)
在线编写TypeScript
playcode 比较推荐,编译ts速度极快
Playground 官方提供的在线ts工具,更加利于分享
一、TypeScript中的基本数据类型
1.1 string
let str1: string = 'hello'; // 成功
let str2: string = new String(); // 失败
// str2报错原因是因为new String返回的是一个对象,而非一个字符串;
let str3: String = new String(); // 成功
1.2 number
const num1: number = 1; // 支持整数
const num2: number = 0xffff; // 支持16禁止
const num3: number = 0b010101; // 支持二进制
const num4: number = 0o001; // 支持八进制
const num5: number = 1.001; // 支持小数
const num6: number = NaN; // 支持NaN
const num7: number = Infinity; // 支持Infinity
1.3 boolean
let boo1: boolean = false;
let boo2: boolean = true;
let boo3: boolean = Boolean();
1.4 undefined 和 null
let und: undefined = undefined;
let nul: null = null;
因为null和undefined是所以类型的子集,所以其他类型的变量进行初始化时,也可以使用null或undefined进行赋值
let str3: string = null;
let num8: number = null;
1.5 void (空值)
一般表示一个没有返回值的函数,或表示一个变量值为undefined
function foo(name: string): void {
console.log(name);
}
foo('张三');
let void1: void = undefined;
1.6 any(任意值)
- any类型的变量可以被随便赋值和改变
let a1: any = 1; // 初始值为number类型
a1 = '1'; // 更改为字符串
- 初始值未设置类型时,默认类型为any
let a2;
a2 = 1;
a2 = '1';
a2 = false;
a2 = {};
1.7 unknown(未知类型)
let un1: unknown = 'ABC';
un1.toLowerCase(); // (报错) 尽管结果为字符串类型 ,但是ts仍然无法推导unknown类型的变量
// 可以配合类型检查 帮助ts分析变量的类型
if (typeof un1 === 'string') un1.toLowerCase(); // 成功的
// 也可以使用as进行强制类型转换
(un1 as string).toLowerCase()
1.8 bigint类型 (大数型 es2020新增)
let big1: bigint = BigInt(1000);
let big2: bigint = big1 + 1000n; // bigint参与计算
let big3: bigint = big1 + big2; // bigint参与计算
console.log(big3);
1.9 symbol
let sym1: symbol = Symbol();
二、TypeScript中的对象类型
2.1 object
object可以表示一个变量的类型为对象,数组和函数等非原始类型数据
let o1:object = {};
o1 = []; // 正确的
o1 = () => {}; // 正确的
o1 = ''; // 错误的
o1 = 1; // 错误的
2.2 Object
Object用于表示任何原始类型和非原始类型数据,严格模式下不能赋值为null和undefined
let o2: Object = {};
o2 = []; // 正确的
o2 = () => {}; // 正确的
o2 = ''; // 正确的
o2 = 1; // 正确的
o2 = undefined; // 严格模式下错误
o2 = null; // 严格模式下错误
2.3 {}
Object 和 {} 使用一致,都表示原始类和非原始值数据严格模式下不能赋值为null和undefined
let o3: {} = {};
o3 = []; // 正确的
o3 = () => {}; // 正确的
o3 = ''; // 正确的
o3 = 1; // 正确的
o3 = undefined; // 严格模式下错误
o3 = null; // 严格模式下错误
三、类型操作符
3.1 类型声明
类型声明也叫类型别名,在 TypeScript
一般使用 type
对类型进行命名
// 使用type声明一个类型为string的别名
type Address = string;
const address:Address = '中国上海' ;
3.2 联合类型
使用 type
进行声明类型, 并且使用 |
进行联合类型
// 在项目中 我们一般使用 '男' 或 1 来表示男性
type Sex = string | number;
let a: Sex = 1;
a = '男'; // 使用联合类型声明后,a变量的值可以更改为number 或 string 类型,但是不能为其他类型
a = false; // 错误的赋值
3.3 交叉类型
交叉类型表示,多个类型共同组合叠加成一个新的类型,该类型包含所有交叉类型的特性
type TypeName = {
name: string;
};
type TypeAge = {
age: number;
};
// 进行类型联合
type User = TypeName & TypeAge;
let user1: User = {
name: '小明',
age: 12,
};
// 也可以直接在声明变量时进行联合
let user2: TypeName & TypeAge = {
name: '小明',
age: 12,
};
3.4 类型推导
当你没有给变量指定一个类型时, TypeScript
会帮你根据变量的结果推测出一个类型;
let a = '1'; // 此时ts会自动推导出a的类型为字符串
a = 1; // (报错) 不能将类型“number”分配给类型“string”。
3.5 类型断言
类型断言是为了帮助 TypeScript
给出更加具体的类型,可以使用 as
关键字按照自己断言的类型帮助ts通过编译
// 将变量类型指定为unknown(不确定)类型
let b: unknown = 'ABC';
b.toLowerCase(); // 报错 “b”的类型为“未知”
(b as string).toLowerCase(); // 将b变量的类型 断言为string类型即可通过编译
3.6 字面量类型
在 TypeScript
中,变量的结果也可以作为变量的类型来使用。常用的字面量类型为number、string、boolean
let c1: 10 = 10; // 数字字面量类型
let c2: false = false; // boolean字面量类型
let c3: 'hello' = 'hello'; // 字符串字面量类型
四、数组和元祖(Array、Tuple)
4.1 数组推导
let arr1 = [1, 2, 3]; // 推导为number组成的数组 number[]
arr1.push('4'); // 报错
arr1.push(4); // 成功
let arr2 = [1, '2']; // 推导为number[] 或 string[]
arr2.push(3, '4'); // 成功
4.2 元祖类型
元祖表示一个数量固定并且类型固定的数组,它是一种特殊的数组
let arr3: [number, string, boolean]; // 表示元祖长度为3,类型分别为number string和boolean
arr3 = [1, '2', true]; // 成功
arr3 = [-1, '', false]; // 成功
console.log(arr3[3]); // 报错 (不能读取超过长度限制的元素)
arr3.push(1, 2, 3, 4, 5); // 可以通过push或unshift添加元素
arr3.unshift(0, 1, 2); // 可以通过push或unshift添加元素
五、枚举类型
在工作中,经常会遇到某一个变量仅仅存在几个固定的取值,如星期一到星期日,只有7种取值,那么就可以使用枚举,将变量和值列举成对应关系。
5.1 整数枚举
// 整数枚举会从0开始依次递增
enum EnumCars1 {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
console.log(EnumCars1.Monday); // 结果为0
console.log(EnumCars1.Tuesday); // 结果为1
console.log(EnumCars1.Wednesday); // 结果为2
console.log(EnumCars1[0]); // 结果为 Monday
console.log(EnumCars1[1]); // 结果为 Tuesday
console.log(EnumCars1[2]); // 结果为 Wednesday
5.2 整数枚举控制起始数
// 初始的枚举值手动设为100,后面的依次进行递增
enum EnumCars2 {
Monday = 100,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday
}
console.log(EnumCars2.Monday); // 100
console.log(EnumCars2.Tuesday); // 101
console.log(EnumCars2.Wednesday); // 102
console.log(EnumCars2[100]); // Monday
// 从某个位置改变数值,后面的变量沿着这个值继续递增
enum EnumCars3 {
Monday = 100,
Tuesday,
Wednesday,
Thursday = 1000,
Friday,
Saturday,
Sunday,
}
console.log(EnumCars3.Monday); // 100
console.log(EnumCars3.Tuesday); // 101
console.log(EnumCars3.Wednesday); // 102
console.log(EnumCars3.Thursday); // 1000
console.log(EnumCars3.Friday); // 1001
console.log(EnumCars3.Saturday); // 1002
console.log(EnumCars3.Sunday); // 1003
console.log(EnumCars3[1003]); // Sunday
5.3 常量整数枚举
和普通枚举的区别是,普通枚举可以使用整数访问枚举值,但是常数枚举只能使用string访问,不能使用整数进行访问
const enum EnumCars4 {
Monday,
Tuesday,
Wednesday,
}
console.log(EnumCars4.Monday); // 0
console.log(EnumCars4.Monday); // 1
console.log(EnumCars4.Monday); // 2
console.log(EnumCars4[0]); // 报错 (只有使用字符串文本才能访问常数枚举成员。)
5.4 常量字符串枚举
const enum EnumCars5 {
Monday = '星期一',
Tuesday = '星期二',
Wednesday = '星期三',
}
console.log(EnumCars5.Monday); // 星期一
console.log(EnumCars5.Tuesday); // 星期二
console.log(EnumCars5.Wednesday); // 星期三
六、TypeScript函数
6.1 函数的定义和参数
// 普通命名函数 参数name为string类型
function foo1(name: string) {
return '姓名为:' + name;
}
// 匿名函数 参数age为number类型
const foo2 = (age: number) => '年龄为:' + age;
6.2 函数的返回值类型
不写函数的返回值类型时,默认为void,表示没有返回值或返回值为undefined,也可以指定函数的返回值类型
// 指定回类型是string类型
function foo3(name: string): string {
return '姓名为:' + name;
}
function foo4(name: string): string {
return 123; // 返回number类型则会报错
}
6.3 函数的默认参数和可选参数
默认参数在定义参数时使用 =
进行赋值,可选参数使用 ?
进行标注
function foo4(name: string = '小明', age?: number): string {
return `姓名是:${name},年龄是: ${age || '未知'}`;
}
console.log(foo4()); // 姓名是:小明,年龄是: 未知
console.log(foo4('小红', 20)); // 姓名是:小红,年龄是: 20
6.4 不固定参数
当不确定函数的参数个数时,可以使用剩余 ...
参数进行处理
// 所有参数组成的数组,不考虑参数的类型
function foo5(...args: any[]): void {
console.log(args);
}
// 除第一位参数,剩余参数组成的数组,参数类型必须全部为number
function foo6(a: number, ...args: number[]): void {
console.log(args);
}
foo5(1, 2, 3, 4, 5); // 打印结果为: [ 1, 2, 3, 4, 5 ]
foo6(1, 2, 3, 4, 5); // 打印结果为: [ 2, 3, 4, 5 ]
6.5 函数内部的this
type TypeThis = {
say(): void;
};
// 将函数的this指定为 TypeThis 类型,内部就可以通过this调用TypeThis上面的say方法
function foo7(this: TypeThis) {
this.say();
}
const bar: TypeThis = {
say() {
return 'say';
},
};
// 调用foo函数,并将this指向 bar 对象
const result = foo7.call(bar);
console.log(result); // 'say'
6.6 函数重载
函数重载是多个函数声明
,一个函数实现
,最终实现的函数需要满足上面所有声明的约束
function foo8(input: string): string;
function foo8(input: number): string;
function foo8(input: number | string): string {
if (typeof input === 'string') return `输入的值是字符串类型`;
else return `输入的值是数字类型`;
}
七、TypeScript类
7.1 类的成员和方法
在TypeScript
中,通过class创建一个类,可以在类中定义成员属性和方法
class Person {
name: string;
constructor(name:string) {
this.name = name;
}
getName(): string {
return this.name;
}
}
const p1 = new Person('randian');
console.log(p1.getName);
7.2 类的修饰符
在TypeScript
中,类中的属性和方法可以使用类的修饰符进行修饰,常用的修饰符有public、private、protected。
- public表示公开属性,可以在任何地方进行访问,也是类的默认修饰符
- private表示私有属性,只能在类的内部使用,不能在子类和实例身上
- protected表示属性和方法是被保护的,可以在类的内部和子类的内部进行访问
class Person1 {
public name: string;
private age: number;
protected sex: string;
constructor(name:string) {
this.name = name;
}
public getName(): string {
return this.name;
}
private getAge() {
return this.age;
}
protected getSex() {
return this.sex;
}
}
const p2 = new Person1('randian');
console.log(p2.getName()); // 成功 randian
console.log(p2.getAge()); // 报错 属性“getAge”为私有属性,只能在类“Person1”中访问
console.log(p2.getSex()); // 报错 属性“getSex”受保护,只能在类“Person1”及其子类中访问。
7.3 extends 继承
在TypeScript
类中,继承使用extends关键字,在子类的构造器内,通过super调用父类的构造器方法,将需要继承的属性传入super
// 父类
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 子类通过extends继承父类
class Dog extends Animal {
action: string;
constructor(name: string, action: string) {
super(name);
this.action = action;
}
}
const dog = new Dog('小狗', '啃骨头');
7.4 只读属性 readonly
在TypeScript
中, 使用readonly对属性限制只读
class Animal1 {
readonly name: string; // 声明name是只读属性
constructor(name: string) {
this.name = name;
}
getName() {
return this.name; // 可以进行访问
}
setName(_name: string) {
this.name = _name; // 报错: 无法为“name”赋值,因为它是只读属性
}
}
7.5 存取器 get、set
在TypeScript
中, 可以使用 get
、set
函数对属性进行读取和写入,其原理是Es5中的Object.definedPrototype
class Animal2 {
name: string;
constructor(name: string) {
this.name = name;
}
// 通过get获取属性
get value() {
console.log('get方法被执行了');
return this.name;
}
// 通过set设置属性
set value(value: string) {
console.log('set方法被执行了');
this.name = value;
}
}
const dog1 = new Animal2('旺财');
console.log(dog1.value); // 旺财
dog1.value = '小花';
console.log(dog1.value); // 小花
7.6 抽象类 abstract
在TypeScript
中,存在抽象类的概念,抽象类有以下几个特征
- 抽象类不能直接实例化,抽象类一般用作普通类的基类,被普通类继承使用;
- 抽象类内部可以有自己的方法和抽象方法(通过abstract修饰的方法),抽象方法只能定义,不能实现,必须在子类中实现;
abstract class Animal3 {
name: string;
constructor(name: string) {
this.name = name;
}
// 抽象类的普通方法
say() {
console.log('say方法执行le');
}
// 抽象方法只能在抽象类中定义,不能直接实现
abstract foo(): void;
}
const dog2 = new Animal3('旺财'); // 报错: 无法创建抽象类的实例
// 声明一个类继承抽象类
class Dog3 extends Animal3 {
constructor(name: string) {
super(name);
}
// 必须实现抽象类中的抽象方法
foo(): void {
console.log('foo方法执行了');
}
}
const dog3 = new Dog3('旺财');
dog3.foo(); // foo方法执行了
dog3.say(); // say方法执行了
八、接口
8.1 接口的定义
在TypeScript
中,通过 interface
关键字来定义接口
interface Users {
name: string;
address: string;
age?: number; // 可选参数
readonly sex: string; // 只读属性
}
// 使用在对象身上
const user: Users = {
name: 'randian',
address: '上海',
sex: '男',
};
user.sex = '女'; // 报错: 无法为“sex”赋值,因为它是只读属性。
// 在类上使用
// 使用implements使用接口
class UserInfo implements Users {
name: string;
address: string;
readonly sex: string;
constructor(name: string, address: string, sex: string) {
this.name = name;
this.address = address;
this.sex = sex;
}
}
8.2 接口的任意类型
当我们不确定一个接口都有哪些参数时,可以使用[key: string]表示这个接口接受任意参数;其中key可以是任意字符。
interface Users {
[key: string]: any;
}
8.3 接口的继承
在TypeScript
中,接口和接口之间可以使用extends进行继承
interface Users {
name: string;
address: string;
age?: number; // 可选参数
readonly sex: string; // 只读属性
}
// 该接口继承于Users接口.并在自身新增了两个属性
interface Details extends Users {
message: string;
ater(): void;
}
const des: Details = {
name: 'randian',
address: '上海',
sex: '男',
message: '成功的消息',
alert: () => {
console.log(des.message);
},
};
8.4 多接口实现
interface A {
name: string;
}
interface B {
age: number;
}
interface C {
sex: string;
}
class Person2 implements A, B, C {
name: string;
age: number;
sex: string;
}
8.5 函数类型接口
interface GetterFn {
(message: string, ...args: any[]): boolean;
}
const fn: GetterFn = (message: string, ...args: any[]) => {
return args.length > 2;
};
console.log(fn('hello', 1, 2)); // false
console.log(fn('hello', 1, 2, 3)); // true
九、泛型
泛型是指在定义类,函数,接口时,不予先限制类型,在使用时再指定类型
9.1 泛型的实例
// 使用T和V作为类型的占位符,在使用时传入对应的类型(T和V可以是任意字符)
class Person<T, V> {
private name: T;
private age: V;
constructor(_name: T, _age: V) {
this.name = _name;
this.age = _age;
}
getName(): T {
return this.name;
}
getAge(): V {
return this.age;
}
}
// 使用时替换T,V对应的类型
const p1 = new Person<string, number>('randian', 18);
9.2 泛型约束
// 在使用泛型变量时,因为预先不知道变量的类型,导致一些api在使用时会发生错误,这个时候就需要使用extends进行约束
function getLength<T extends any[]>(arr: T): number {
return arr.length;
}
console.log( getLength<number[]>([1,2,3,4]) );
9.3 泛型参数的默认类型
class Users<T = string> {
private id: T;
constructor(_id: T) {
this.id = _id;
}
getId(): T {
return this.id;
}
}
const user1 = new Users('1'); // 默认类型为string
const user2 = new Users<number>(1); // 传入number的id
十、高级特性
10.1 in 操作符
遍历并集的每一个元素,将字面量类型的值转为属性
type Key = 'k1' | 'k2' | 'k3';
type MyMap = {
[K in Key]: number;
};
const map: MyMap = {
k1: 1,
k2: 2,
k3: 3,
};
10.2 keyof 操作符
将object中的key转为字面量类型
type Users = {
name: string;
age: number;
sex: string;
};
type userKeys = keyof Users;
let k1: userKeys;
k1 = 'name';
k1 = 'age';
k1 = 'sex';
10.3 keyof 和 in 的结合
type Users = {
name: string;
age: number;
sex: string;
};
// 创建一个新的类型,将Users里面的参数变成可选的
type OptionalUsers = {
[K in keyof Users]?: Users[K];
};
// 参数是必填的
const o1: Users = {
name: 'randian',
sex: '男',
age: 18,
};
// 参数是可选的
const o2: OptionalUsers = {
name: 'randian',
};
10.4 条件类型 - 三元运算符
class UserMan {
sex: string = '男';
}
class UserWoMan {
sex: string = '女';
}
// extends在编译时会进行条件判断
type ResultUser<T extends boolean> = T extends true ? UserMan : UserWoMan;
function getUserInfo<T extends boolean>(isMan: T): ResultUser<T> {
return isMan ? new UserMan() : new UserWoMan();
}
// 根据boolean返回具体的实现
const user1 = getUserInfo(false);
const user2 = getUserInfo(true);
10.5 条件类型 - infer
- infer只能跟在extends后使用
type ElemType<T> = T extends (infer U)[] ? U : never;
type A = ElemType<number[]>; // number
type B = ElemType<string[]>; // string
type C = ElemType<string>; // never
// 使用infer返回数组的第一位元素
type ArrayOne<T> = T extends [infer U, ...any[]] ? U : never;
type D = ArrayOne<[1, 2, 3]>; // 1
type E = ArrayOne<[false, '1', true]>; // false
// 获取对象中的类型
type GetValueType<T> = T extends { name: infer V; age: infer U } ? [V, U] : [];
type F = GetValueType<Users>;
// 获取函数的返回值类型
type Fns = (name: string, age: number) => string;
type GetFnReturn<T> = T extends (name: any, age: any) => infer D ? D : never;
type G = GetFnReturn<Fns>;
10.6 映射类型 - Pick
将集合中的一部分映射到新的集合中
type User2 = {
name: string;
age: number;
sex: string;
};
type PickUser = Pick<User2, 'name' | 'age'>;
const u1: PickUser = {
name: 'randianx',
age: 18,
};
10.7 映射类型 - Partial
将集合中的属性变为可选属性
type User2 = {
name: string;
age: number;
sex: string;
};
type PartialUset = Partial<User2>;
const u2: PartialUset = {
name: 'randian',
};
10.8 映射类型 - Record
规定属性和值的类型返回一个集合
type User3 = Record<string, number>;
const u3: User3 = {
小明: 1,
小张: 2,
};
10.9 映射类型 - Readonly
将集合内的属性变为只读的
type User2 = {
name: string;
age: number;
sex: string;
};
type ReadonlyUser = Readonly<User2>;
const u4: ReadonlyUser = {
name: 'randian',
age: 18,
sex: '男',
};
u4.sex = '女'; // 报错: 无法为“sex”赋值,因为它是只读属性。
10.10 映射类型 - Omit
从集合中剔除某一个或多个属性
type User2 = {
name: string;
age: number;
sex: string;
};
type OmitUser = Omit<User2, 'age' | 'sex'>;
const u5: OmitUser = {
name: 'randian',
};
10.11 映射类型 - Required
将集合中的属性转为必填项
type User3 = {
name?: string;
age?: number;
sex: string;
};
type RequiredUser = Required<User3>;
const u6: RequiredUser = {
name: 'randian',
age: 18,
sex: '男',
};
10.12 映射类型 - Extract
取两个集合之间的交集作为一个新的集合
// 声明一个圆的类型
type Circular = {
radius: number;
center: [number, number];
perimeter: number;
area: number;
};
// 声明一个正方形的类型
type Square = {
width: number;
height: number;
perimeter: number;
area: number;
};
// 圆形和正方形都有周长和面积
type CS = Extract<keyof Circular, keyof Square>; // "perimeter" | "area"
type A = number | string | boolean | Symbol;
type B = number | string | Symbol;
type AB = Extract<A, B>; // number | string | Symbol
十一、高级类型-类型体操
type User = {
id: number | string;
name?: string;
age: number;
sex: string;
address?: string;
children?: User[];
};
11.1 实现Required - 将集合中的属性变为必填项
type MyRequired<T> = {
[K in keyof T]-?: T[K];
};
type RequiredUser = MyRequired<User>;
const u1: RequiredUser = {
id: 1,
name: 'randian',
age: 18,
sex: '男',
address: '上海',
children: [],
};
11.2 实现Record - 根据属性和值的类型返回一个集合
type MyRecord<K extends keyof any, T> = {
[P in K]: T;
};
const u2: MyRecord<number, boolean> = {
1: false,
'2': true,
};
11.3 实现Partial - 将集合中的属性都变成可选属性
type MyPartial<T> = {
[K in keyof T]?: T[K] | undefined;
};
const u3: MyPartial<User> = {
name: 'randian',
};
11.4 实现Pick - 将集合中的部分属性返回到一个新的集合中
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
const u4: MyPick<User, 'sex' | 'age' | 'id'> = {
age: 18,
sex: '男',
id: 1,
};
11.5 实现Omit - 排除掉集合中的部分属性返回到一个新的集合
type MyOmit<T, K extends keyof T> = {
[P in Exclude<keyof T, K>]: T[P];
};
const u5: MyOmit<User, 'sex' | 'age' | 'id'> = {
name: 'randian',
address: '上海',
children: [],
};
11.6 实现Readonly - 将集合中的属性变为可读的
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
const u6: MyReadonly<User> = {
id: 1,
age: 18,
sex: '男',
};
// u6.age = 19; // 报错: 无法为“age”赋值,因为它是只读属性。
export {};
11.7 实现Extract -取两个集合的交集作为一个新的集合
type MyExtract<T, K> = T extends K ? T : never;
type Circular = {
radius: number;
center: [number, number];
perimeter: number;
area: number;
};
type Square = {
width: number;
height: number;
perimeter: number;
area: number;
};
// 圆形和正方形都有周长和面积
type CS = MyExtract<keyof Circular, keyof Square>; // "perimeter" | "area"
type A = number | string | boolean | Symbol;
type B = number | string | Symbol;
type AB = MyExtract<A, B>; // number | string | Symbol
11.8 实现NonNullable - 从集合中剔除null和undefined的值
type MyNonNullable<T> = T extends null | undefined ? never : T;
type Ext = string | null | undefined | boolean;
type NotNullTpye = MyNonNullable<Ext>;