1.new关键字在类型中的使用
泛型
在泛型里使用类类型
在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型。比如,
function create<T>(c: {new(): T; }): T {//这边的new()不好理解
return new c();
}
一个更高级的例子,使用原型属性推断并约束构造函数与类实例的关系。
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
class Bee extends Animal {
keeper: BeeKeeper;
}
class Lion extends Animal {
keeper: ZooKeeper;
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
查了不少资料,比较好的解释是what is new() in Typescript?意思就是create
函数的参数是构造函数没有参数的T类的类型,同理,createInstance
函数的参数是构造函数没有参数的A类的类型。
带着疑问写了测试代码:
vscode依然报错,仔细想下,
createInstance
函数return new c();
这句话是类的实例化,所以传进来的参数c
是个类,而不是类的实例,故要使用(c: new () => A)
标明c是个类,而不是(c: Animal)
类的实例,从下面的调用也可以看出传递的是类而不是实例。我们知道js里面是没有类的,ES6里面的class也只是个语法糖,编译后依然为一个function。所以去修饰一个class也就是修饰一个function,但是修饰的是构造函数,所以这边加以区别,前面有个new。
接口
类静态部分与实例部分的区别
这边同样用到了关键字new()
第一个例子报错了,
官方解释:
当你操作类和接口的时候,你要知道类是具有两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会得到一个错误:
这里因为当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内。
因此,我们应该直接操作类的静态部分。 看下面的例子,我们定义了两个接口, ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 为了方便我们定义一个构造函数 createClock,它用传入的类型创建实例。
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick:()=>void;
}
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {//这个和泛型中使用类类型相同,
return new ctor(hour, minute);//需要类型为ClockInterface的两个参数的构造器类,只是二者写法有点区别
}
class DigitalClock implements ClockInterface {//这边实现的接口不能是直接的构造器
constructor(h: number, m: number) { }
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
因为createClock的第一个参数是ClockConstructor类型,在createClock(AnalogClock, 7, 32)里,会检查AnalogClock是否符合构造函数签名。
再结合react官方接口的书写,
interface Component<P = {}, S = {}> extends ComponentLifecycle<P, S> { }
class Component<P, S> {//这里全部是实例方法和属性
constructor(props?: P, context?: any);
// Disabling unified-signatures to have separate overloads. It's easier to understand this way.
// tslint:disable:unified-signatures
setState<K extends keyof S>(f: (prevState: S, props: P) => Pick<S, K>, callback?: () => any): void;
setState<K extends keyof S>(state: Pick<S, K>, callback?: () => any): void;
// tslint:enable:unified-signatures
forceUpdate(callBack?: () => any): void;
render(): JSX.Element | null | false;
// React.Props<T> is now deprecated, which means that the `children`
// property is not available on `P` by default, even though you can
// always pass children as variadic arguments to `createElement`.
// In the future, if we can define its call signature conditionally
// on the existence of `children` in `P`, then we should remove this.
props: Readonly<{ children?: ReactNode }> & Readonly<P>;
state: Readonly<S>;
context: any;
refs: {
[key: string]: ReactInstance
};
}
interface ComponentClass<P = {}> {
new (props?: P, context?: any): Component<P, ComponentState>;//此处对Component做了修饰,规定react的构造函数类型
propTypes?: ValidationMap<P>;//下面几个全部是静态方法和属性
contextTypes?: ValidationMap<any>;
childContextTypes?: ValidationMap<any>;
defaultProps?: Partial<P>;
displayName?: string;
}
为什么写了interface Component又写了class Component,interface Component没有写里面具体的实例方法和属性,而写了同名的class Component,是不是意味着class Component就是interface的具体实现,这里是index.d.ts文件,应该在.ts文件里面不能这么写
到此new()
关键字在类型中的使用基本搞清楚了。
类装饰器
function classDecorator<T extends {new(...args:any[]):{}}>(constructor:T) {
return class extends constructor {
newProperty = "new property";
hello = "override";
}
}
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter("world"));
2. 装饰器的使用
装饰器(Decorator)在React中的应用
JavaScript 中的装饰器是什么?
3. 函数类型声明的不同方式
1. 最常见的方式
函数声明(Function Declaration)类型的,就是普通的具名函数
function add(x: number, y: number): number {
return x + y
}
2. 函数表达式(Function Expression)类型声明
- 1.这种就是后面赋值号后面有个匿名或者具名函数,比较好理解的写法,都在函数体内写类型
handle = (
baseValue: number,
increment: number,
): number => {
return baseValue
}
- 2.给变量定义类型,同时也在函数内部定义类型
handle: (baseValue: number, increment: number) => number = (
baseValue: number,
increment: number,
): number => {
return baseValue
}
- 3.将变量的类型抽取到接口中
interface IHandle {
(baseValue: number, increment: number): number
}
handle: IHandle = (baseValue: number, increment: number): number => {
return baseValue
}
既然前面的变量声明了接口,那么后面的函数里面的类型就可以去掉了
interface IHandle {
(baseValue: number, increment: number): number
}
handle: IHandle = (baseValue,increment)=> {
return baseValue
}
但是发现个问题
interface IHandle {
(baseValue: number, increment: number): number
}
handle: IHandle = (baseValue)=> {
return baseValue
}
这么写居然vscode没有报错,increment明明不是可选参数,不是很理解,有待讨论
查阅了typescript的官方文档的函数章节
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
这么写的接口,和函数类型的接口声明惊人的相似,
interface IHandle {
(baseValue: number, increment: number): number
}
差别在哪呢.函数类型的接口声明是匿名的,而上面对象的类型声明的函数是具名的,做了一下测试
var src:UIElement = function() {}
//报错:
// Type '() => void' is not assignable to type 'UIElement'.
// Property 'addClickListener' is missing in type '() => void'.
// var src: UIElement
interface UIElement {
addClickListener(name: string): void
}
var src: UIElement = {//不报错
addClickListener() {},
}
var src: UIElement = { //报错
addClickListener(name: string, age: number) {},
}
// Type '{ addClickListener(name: string, age: number): void; }' is not assignable to type 'UIElement'.
// Types of property 'addClickListener' are incompatible.
// Type '(name: string, age: number) => void' is not assignable to type '(name: string) => void'.
看样就是实际的函数的参数可以比接口少定义,但是不能多定义
函数接口的是用来修饰变量的,当然包括函数的形参的修饰,以及返回值的修饰,但是不能修饰具名函数
interface IHandle {
props?: object
(baseValue: number, increment: number): number
}
function source:IHandle(baseValue) {//这么修饰具名函数会报错
return baseValue
}
function source(baseValue): IHandle {//这么写是可以的,表明返回了一个IHandle 的函数
return baseValue
}
3. 怎样修饰类(类类型)
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
}
else {
return Greeter.standardGreeting;
}
}
}
let greeter1: Greeter;
greeter1 = new Greeter();
console.log(greeter1.greet());
let greeterMaker: typeof Greeter = Greeter;
greeterMaker.standardGreeting = "Hey there!";
let greeter2: Greeter = new greeterMaker();
console.log(greeter2.greet());
- 我们一般都是修饰一个类的实例的,怎么简单的修饰类,typeof是个很好的办法,
这个例子里,greeter1与之前看到的一样。 我们实例化 Greeter类,并使用这个对象。 与我们之前看到的一样。
再之后,我们直接使用类。 我们创建了一个叫做 greeterMaker的变量。 这个变量保存了这个类或者说保存了类构造函数。 然后我们使用 typeof Greeter,意思是取Greeter类的类型,而不是实例的类型。 或者更确切的说,"告诉我 Greeter标识符的类型",也就是构造函数的类型。 这个类型包含了类的所有静态成员和构造函数。 之后,就和前面一样,我们在 greeterMaker上使用new,创建Greeter的实例。
也就是使用typeof ClassName
interface IPerson {
age: number;
}
class Person {
age: 99;
}
let p: typeof IPerson = Person;//这么写会报错的
- 使用构造函数接口修饰类
interface IPerson {
age: number;
}
interface IPersonConstructor {
new (): IPerson;
}
class Person {
age: 99;
}
let p: IPersonConstructor = Person;
之前对new (): IPerson;
这句话后面的返回值不是很理解,直到看到了将基类构造函数的返回值作为'this',也就是说new Person()的时候执行的是构造函数,那么构造函数就返回了Person的实例,自然new (): IPerson;
构造函数返回IPerson就很好理解了
4. keyof
比如有个interface a{
a1: 'a1';
a2: 'a2';
.....
.....
a100: 'a100';
}
然后又个类型要继承这个interface的某一个value的值
比如 type anum = 'a1' | 'a2' | 'a3' | ....| 'a100',
应该怎么写?
type anum = typeof a.a1 | typeof a.a2 | typeof a.a3 | ....| typeof a.a100;
有没有简单的写法 那个interface 有可能随时在变
a1到a100全要?
对全要
而且有可能 到a100以上 一直在添加
我想在添加的时候只添加interface type不用在修改了 有没有办法
key还是value
value
5. 函数名后面的感叹号(非空断言操作符)
onOk = () => {
this.props.onOk!(this.picker && this.picker.getValue());
this.fireVisibleChange(false);
}
react-component/m-picker
群里问了是非空操作符
再去搜索,在typescript 2.0的文档里找到了,叫 非空断言操作符
// 使用--strictNullChecks参数进行编译
function validateEntity(e?: Entity) {
// 如果e是null或者无效的实体,就会抛出异常
}
function processEntity(e?: Entity) {
validateEntity(e);
let s = e!.name; // 断言e是非空并访问name属性
}
需要在tsconfig.json 里面加上"strictNullChecks": true,这样vscode会自动检测null和undefined
6. ts差集运算
Add support for literal type subtraction
具体应用在高阶组件里面的props
TypeScript在React高阶组件中的使用技巧
7. 方法重写
方法重写子类的参数需要跟父类一致,否则会报错
8. 剩余参数
const renderWidget = ({ field, widget, ...restParams }) => {
const min = restParams.min;
};
这段代码的剩余参数restParams 怎么表示
感觉应该这么写
const renderWidget = (
{
field,
widget,
...restParams
}: { field: string; widget: string;restParams: [key: string]: any },
idx: number,
primaryField: string
) => {
const min = restParams.min;
};
但是还是报错,其实应该省去restParams
const renderWidget = ({
field,
widget,
...restParams
}: {
field: string;
widget: string;
[key: string]: any;
}) => {
const min = restParams.min;
};
这样就好了Destructuring a function parameter object and …rest
9. 元组推断
群里看到的疑问,为什么最后推断出是string|number
type TTuple = [string, number];
type Res = TTuple[number]; // string|number
一开始不理解,自己改下写法
type TTuple = [string, number];
type Res = TTuple[string];
[图片上传失败...(image-894ad4-1590418731585)]
再尝试写下去
type TTuple = [string, number];
type Res = TTuple[0];//string
type Res1 = TTuple[1];//number
type Res2 = TTuple[2];//报错
[图片上传失败...(image-e6ec46-1590418731585)]
TTuple[2]
报错
首先js中没有元组概念,数组中可以存放任何数据类型,ts中把存放不同数据类型的数组叫做元组,这样就很好理解TTuple[number]
中的number就是索引index,索引只能是数字,而且不能越界,所以两次报错就好理解了,TTuple[number]
为什么返回string|number
是因为没有指定具体的索引,只能推断出两种可能,string或number
联想到获取接口的属性的类型
interface IProps {
name: string;
age: number;
}
type IAge = IProps["name"];// string
const per: IAge = "geek";
10. type interface 泛型差异
下面实现类似于Record的代码
interface Collection<T extends string, U> {
[P in T]: U;
}
type Collection33<K extends string, T> = {
[P in K]: T;
};
type完全没问题,interface 的两种写法都会报错,好奇葩