在TypeScript使用泛型建立工厂函数时,须要引用构造函数的类类型。好比,javascript
function create<T>(c: {new(): T; }): T {//这边的new()很差理解 return new c(); }
一个更高级的例子,使用原型属性推断并约束构造函数与类实例的关系。html
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类的类型。
带着疑问写了测试代码:java
vscode依然报错,仔细想下,createInstance
函数return new c();
这句话是类的实例化,因此传进来的参数c
是个类,而不是类的实例,故要使用(c: new () => A)
标明c是个类,而不是(c: Animal)
类的实例,从下面的调用也能够看出传递的是类而不是实例。
咱们知道js里面是没有类的,ES6里面的class也只是个语法糖,编译后依然为一个function。因此去修饰一个class也就是修饰一个function,可是修饰的是构造函数,因此这边加以区别,前面有个new。react
这边一样用到了关键字new()
第一个例子报错了,
官方解释:
当你操做类和接口的时候,你要知道类是具备两个类型的:静态部分的类型和实例的类型。 你会注意到,当你用构造器签名去定义一个接口并试图定义一个类去实现这个接口时会获得一个错误:
这里由于当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,因此不在检查的范围内。
所以,咱们应该直接操做类的静态部分。 看下面的例子,咱们定义了两个接口, ClockConstructor为构造函数所用和ClockInterface为实例方法所用。 为了方便咱们定义一个构造函数 createClock,它用传入的类型建立实例。git
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是否符合构造函数签名。github
再结合react官方接口的书写,typescript
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文件里面不能这么写,同事被相同的问题纠结住,json
interface Component<P = {}, S = {}, SS = any> extends ComponentLifecycle<P, S, SS> { } class Component<P, S> { static contextType?: Context<any>; // TODO (TypeScript 3.0): unknown context: any; constructor(props: Readonly<P>); /** * @deprecated * @see https://reactjs.org/docs/legacy-context.html */ constructor(props: P, context?: any); // We MUST keep setState() as a unified signature because it allows proper checking of the method return type. // See: https://github.com/DefinitelyTyped/DefinitelyTyped/issues/18365#issuecomment-351013257 // Also, the ` | S` allows intellisense to not be dumbisense setState<K extends keyof S>( state: ((prevState: Readonly<S>, props: Readonly<P>) => (Pick<S, K> | S | null)) | (Pick<S, K> | S | null), callback?: () => void ): void; forceUpdate(callback?: () => void): void; render(): ReactNode; readonly props: Readonly<P> & Readonly<{ children?: ReactNode }>; state: Readonly<S>; /** * @deprecated * https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs */ refs: { [key: string]: ReactInstance }; }
再次看React index.d.ts 会发现interface Component没有声明任何内容,生命周期方法也是继承过来的,包括setState,forceUpdate,render的声明都是在class Component中的,经过组件中的this.setState也是定位到class Component中,咱们知道interface Component是用来修饰组件的实例的,可是无法描述组件的静态属性,好比contextType,这时候也许只能借助class来描述static成员,经过上述分析可知interface是能够用来修饰类的,再结合interface ComponentClassapi
interface ComponentClass<P = {}, S = ComponentState> extends StaticLifecycle<P, S> { new (props: P, context?: any): Component<P, S>; propTypes?: WeakValidationMap<P>; contextType?: Context<any>; contextTypes?: ValidationMap<any>; childContextTypes?: ValidationMap<any>; defaultProps?: Partial<P>; displayName?: string; }
ComponentClass是用来描述class Component的,这个时候限制Component的static成员,接着上面的例子作个测试:数组
// 用来描述类类型 interface ClockConstructor { new (hour: number, minute: number); contextType: any; // 新增个成员变量 } interface ClockInterface { tick: () => void; } class DigitalClock implements ClockInterface { //这边实现的接口不能是直接的构造器 constructor(h: number, m: number) {} tick() { console.log("beep beep"); } } function createClock( ctor: ClockConstructor, hour: number, minute: number ): ClockInterface { //这个和泛型中使用类类型相同, return new ctor(hour, minute); //须要类型为ClockInterface的两个参数的构造器类,只是两者写法有点区别 }
vscode给报错了,说是DigitalClock缺乏contextType,其实就是缺乏静态属性 contextType,
class DigitalClock implements ClockInterface { static contextType; //这边实现的接口不能是直接的构造器 constructor(h: number, m: number) {} tick() { console.log("beep beep"); } } let digital = createClock(DigitalClock, 12, 17);
给DigitalClock 加上static contextType就不会报错了,ClockInterface是用来描述类的实例的,ClockConstructor是用来描述类的,用ClockConstructor描述类有哪些静态属性没问题,可是怎么去描述实例的静态属性呢?仿造React的方式在.ts文件中定义同名interface跟class
interface ClockInterface {} class ClockInterface { tick(): void; }
说函数没有具体实现,将代码贴到d.ts文件,就不会报错,
interface ClockInterface { name: string; } class ClockInterface { tick(): void; static jump(): void; }
使用该接口,
class DigitalClock implements ClockInterface { static jump: () => {}; name: string = ""; tick() { console.log("beep beep"); } } DigitalClock.jump();
在vscode中输入DigitalClock. 或者static j都有相应的提示,因此能够在d.ts中用同名interface和class描述类的静态属性
到此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"));
装饰器(Decorator)在React中的应用
JavaScript 中的装饰器是什么?
函数声明(Function Declaration)类型的,就是普通的具名函数
function add(x: number, y: number): number { return x + y }
handle = ( baseValue: number, increment: number, ): number => { return baseValue }
handle: (baseValue: number, increment: number) => number = ( baseValue: number, increment: number, ): number => { return baseValue }
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 }
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());
*这个例子里,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就很好理解了
好比有个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
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
Add support for literal type subtraction
具体应用在高阶组件里面的props
TypeScript在React高阶组件中的使用技巧
方法重写子类的参数须要跟父类一致,不然会报错
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
群里看到的疑问,为何最后推断出是string|number
type TTuple = [string, number]; type Res = TTuple[number]; // string|number
一开始不理解,本身改下写法
type TTuple = [string, number]; type Res = TTuple[string];
再尝试写下去
type TTuple = [string, number]; type Res = TTuple[0];//string type Res1 = TTuple[1];//number type Res2 = TTuple[2];//报错
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";