export type Dispatch<T> = T extends (...args: infer A) => any ? (...args: A) => void : never;
type T0 = Dispatch<() => string>; // () => void, 里面 A 就是[]
type T1 = Dispatch<(s: string) => void> // (s: string) => void, A 就是 [string]
type T2 = Dispatch<(<T>(arg: T) => T)>; // (arg: unknow) => void, A 就是[unknow]
type T3 = Dispatch<object>; // never
// 这么传跟已知类型传其实没太大区别
type Parameters1<T, R extends Array<any>> = T extends (...args: R) => any ? R : any;
type T4 = Parameters1<(s: string) => void, number[]>;
// 函数类型
interface SearchFunc {
(source: string, subString: string): boolean;
}
// 对象/方法
type Connected = {
delay(input: number): string;
setMessage(action: Date): string;
};
// ******* 类型分配 ******
class EffectModule {
a = 1;
b() {
}
}
interface Action<T> {
payload?: T;
type: string;
}
// 获取方法的名称
type MethodName<T> = {[F in keyof T]: T[F] extends Function ? F : never}[keyof T]
type EE = MethodName<EffectModule>
type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>>
type asyncMethodConnect<T, U> = (input: T) => Action<U>
type syncMethod<T, U> = (action: Action<T>) => Action<U>
type syncMethodConnect<T, U> = (action: T) => Action<U>
type EffectMethodAssign<T> = T extends asyncMethod<infer U, infer V> ? asyncMethodConnect<U, V> : T extends syncMethod<infer U, infer V> ? syncMethodConnect<U, V> : never
type Connect = (module: EffectModule) => {
[F in MethodName<typeof module>]:EffectMethodAssign<typeof module[F]>
}
//********* */ keyof 用法 **********
interface Itest{
webName:string;
age: number;
address:string;
dd: never;
}
type ant = keyof Itest; // ant = "webName" | "age" | "address"| 'dd'
type d = Itest[keyof Itest] // string | number , 这里 never 就被剔除
// ********** extends 的 条件类型 *************
// extends 运用在 type 和 class 中时完全是两种作用的效果
// 简单的值匹配
type Equal<X, Y> = X extends Y ? true : false;
type Str = Equal<'a', 'a'>; // true
type Boo = Equal<true, false>; // false
// 简单的类型匹配
type isNum<T> = T extends number ? number : string
//判断联合类型
type A = 'x';
type B = 'x' | 'y';
type Y = A extends B ? true : false; // true
// 当联合类型无法做出判断时
type AB<T> = T extends 'x' ? 'a' : 'b';
type C = AB<'x'>;
// 得到 type C = 'a'
type All = AB<'x' | 'y'>; // 非确定条件,可能是 'x' 或 'y'
// 得到 type All = 'a' | 'b';
//泛型约束
interface Lengthwise {
length: number;
a: string;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // Now we know it has a .length property, so no more error
return arg;
}
//
type p = 'length' extends keyof Lengthwise ? true : false; // true;
// *** 高级类型 ***
// Partial<T>, Readonly<T>,
// Record<K extends keyof any, T>,
// Pick<T, K extends keyof T> ,
// Exclude<T, U> 相反的Extract<T, U>
// 排除接口中指定的属性 Omit<T, K extends keyof any>
type record = Record<'x' | 'y', number>; //{x: number, y; number}
// NonNullable< T >
type Tp1 = NonNullable<string | null | undefined>; // string
// Parameters 获取函数的全部参数类型,以 元组类型 返回:
// Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
type F1 = (a: string, b: number) => void;
type F1ParamTypes = Parameters<F1>; // [a:string, b:number]
// ReturnType 接收函数声明,返回函数的返回值类型,如果多个类型则以 联合类型 方式返回
//type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
type F2 = () => Date;
type F1ReturnType = ReturnType<F1>; // Date
// InstanceType 只是这里获取的是 构造函数 的全部参数
// type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
Category: typescript
declare
- 在 d.ts 声明文件中,任何的 declare 默认就是 global 的了,所以你在 d.ts 文件中是不能出现
declare global
的。只有在模块文件中的定义,如果想要全局就使用declare global
xxx.d.ts
declare module "test" {
export var value: number;
export function hello(str: string): String;
}
declare var D2: string;
declare namespace mySpace {
interface ITest {
id: number;
}
}
import test from "test";
test.hello('123');
test.value;
window.D2 = "hello";
const obj: mySpace.ITest = {
id: 1
};
2. 如果在模块中 只需要
Typescript declare , 命名空间和模块
声明文件
TypeScript 作为 JavaScript 的超集,在开发过程中不可避免要引用其他第三方的 JavaScript 的库。虽然通过直接引用可以调用库的类和方法,但是却无法使用TypeScript 诸如类型检查等特性功能。通过引用这个声明文件,就可以借用 TypeScript 的各种特性来使用库文件了
使用 非typescript 库, 需要 引入自定义声明文件暴露接口才可以使用, 在ts 文件里头部 /// <reference path=”…” />, 声明文件间也可以用/// <reference path=”…” /> 互相引用;
.ts 文件 经过编译 会生成.d.ts,可以通过编译选项开关
全局库
模版文件global.d.ts
定义了myLib库作为例子。 一定要阅读 “防止命名冲突”补充说明。
模块化库
针对模块有三种可用的模块, module.d.ts
, module-class.d.ts
and module-function.d.ts
.
- 使用
module-function.d.ts
,如果模块能够作为函数调用。 - 使用
module-class.d.ts
,如果模块能够使用new
来构造: - 如果模块不能被调用或构造,使用
module.d.ts
文件。
使用module-function.d.ts 模块,因为 模块可以作为函数调用
var x = require("foo"); //
// Note: calling 'x' as a function
var y = x(42);
declare 关键字
作用:告诉编译器,某个项(对象,变量,包,类等等)已经存在了,不用检查
三斜线指令
三斜线指令是包含单个XML标签的单行注释。 注释的内容会做为编译器指令使用。
/// <reference path=”…” />指令是三斜线指令中最常见的一种。 它用于引入声明文件。三斜线引用告诉编译器在编译过程中要引入的额外的文件。
///<reference types=”…” />与 /// <reference path=”…” />指令相似,这个指令是用来声明 依赖的。一个 /// <reference types=”…” />指令则声明了对某个包的依赖。可以简单地把三斜线类型引用指令当做 import声明的包。
例如,把 /// <reference types=”node” />引入到声明文件,表明这个文件使用了@types/node/index.d.ts里面声明的名字; 并且,这个包需要在编译阶段与声明文件一起被包含进来。仅当在你需要写一个d.ts文件时才使用这个指令。
使用依赖
依赖全局库
如果你的库依赖于某个全局库,使用 /// <reference types="..." />
指令:
/// <reference types="someLib" />
function getThing(): someLib.thing;
依赖模块
如果你的库依赖于模块,使用import
语句:
import * as moment from "moment";
function getThing(): moment;
依赖UMD库 分两种情况
- 从全局库 / 如果你的全局库依赖于某个UMD模块,使用/// <reference types>指令
- 从一个模块或UMD库 / 如果你的模块或UMD库依赖于一个UMD库,使用
import
语句
命名空间和模块的陷阱
对模块使用
要想描述非TypeScript编写的类库的类型,我们需要声明类库所暴露出的API。
外部模块
在Node.js里大部分工作是通过加载一个或多个模块实现的。 我们可以使用顶级的 export
声明来为每个模块都定义一个.d.ts
文件,但最好还是写在一个大的.d.ts
文件里。 我们使用与构造一个外部命名空间相似的方法,但是这里使用 module
关键字并且把名字用引号括起来,方便之后import
// node.d.ts 文件
declare module "url" {
export function normalize(p: string): string;
export function join(...paths: any[]): string;
export let sep: string;
}
现在我们可以/// <reference> node.d.ts
并且使用import url = require("url");
或import * as URL from "url"
加载模块。
/// <reference path="node.d.ts"/>
import * as URL from "url";
let myUrl = URL.parse("http://www.typescriptlang.org");
外部模块简写 / 假如你不想在使用一个新模块之前花时间去编写声明,你可以采用声明的简写形式以便能够快速使用它。
//声明文件
declare module "hot-new-module";
//简写模块里所有导出的类型将是any。
//其他文件 模块引入
import x, {y} from "hot-new-module";
x(y);
一个常见的错误是使用/// <reference>引用模块文件,应该使用import。要理解这之间的区别,我们首先应该弄清编译器是如何根据 import路径(例如,import x from “…”)里面的…,等等)来定位模块的类型信息的。
编译器首先尝试去查找相应路径下的 .ts,.tsx再或者.d.ts。 如果这些文件都找不到,编译器会查找 外部模块声明。 回想一下,它们是在 .d.ts文件里声明的。
myModules.d.ts
// In a .d.ts file or .ts file that is not a module:
declare module "SomeModule" {
export function fn(): string;
}
myOtherModule.ts
/// <reference path="myModules.d.ts" />
// 这里的引用标签指定了外来模块的位置
import * as m from "SomeModule";
模块解析策略
共有两种可用的模块解析策略:Node和Classic。 你可以使用 --moduleResolution
标记来指定使用哪种模块解析策略。若未指定,那么在使用了 --module AMD | System | ES2015
时的默认值为Classic,其它情况时则为Node。
有一个对moduleB
的非相对导入import { b } from “moduleB”,它是在/root/src/folder/A.ts
文件里,会以如下的方式来定位"moduleB"
:
- /root/src/folder/moduleB.ts
- /root/src/folder/moduleB.d.ts
- /root/src/moduleB.ts
- /root/src/moduleB.d.ts
- /root/moduleB.ts
- /root/moduleB.d.ts
- /moduleB.ts
- /moduleB.d.ts
Node.js如何解析模块
为了理解TypeScript编译依照的解析步骤,先弄明白Node.js模块是非常重要的。 通常,在Node.js里导入是通过 require函数调用进行的。 Node.js会根据 require的是相对路径还是非相对路径做出不同的行为。
相对路径很简单。 例如,假设有一个文件路径为 /root/src/moduleA.js,包含了一个导入var x = require(“./moduleB”); Node.js以下面的顺序解析这个导入:
- 检查/root/src/moduleB.js文件是否存在。
- 检查
/root/src/moduleB
目录是否包含一个package.json
文件,且package.json
文件指定了一个"main"
模块。 在我们的例子里,如果Node.js发现文件/root/src/moduleB/package.json
包含了{ "main": "lib/mainModule.js" }
,那么Node.js会引用/root/src/moduleB/lib/mainModule.js
。 - 检查
/root/src/moduleB
目录是否包含一个index.js
文件。 这个文件会被隐式地当作那个文件夹下的”main”模块。
还是用上面例子,但假设/root/src/moduleA.js
里使用的是非相对路径导入var x = require("moduleB");
。 Node则会以下面的顺序去解析 moduleB
,直到有一个匹配上。
/root/src/node_modules/moduleB.js
/root/src/node_modules/moduleB/package.json
(如果指定了"main"
属性)/root/src/node_modules/moduleB/index.js
/root/node_modules/moduleB.js
/root/node_modules/moduleB/package.json
(如果指定了"main"
属性)/root/node_modules/moduleB/index.js
/node_modules/moduleB.js
/node_modules/moduleB/package.json
(如果指定了"main"
属性)/node_modules/moduleB/index.js
TypeScript如何解析模块
TypeScript是模仿Node.js运行时的解析策略来在编译阶段定位模块定义文件。 因此,TypeScript在Node解析逻辑基础上增加了TypeScript源文件的扩展名( .ts
,.tsx
和.d.ts
)。 同时,TypeScript在 package.json
里使用字段"types"
来表示类似"main"
的意义 – 编译器会使用它来找到要使用的”main”定义文件。
react typescript
Interfaces
A better way to define our ExtendedButton element would be to extend a native HTML button element type like so:
import React, {ButtonHTMLAttributes} from 'react';
interface IButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
/** The text inside the button */
text: string,
/** The type of button, pulled from the Enum ButtonTypes */
type: ButtonTypes,
/** The function to execute once the button is clicked */
action: () => void
}
const ExtendedButton : React.FC<IButtonProps> = ({text, type, action}) => {
}
Enums
//...
/** A set of groupped constants */
enum SelectableButtonTypes {
Important = "important",
Optional = "optional",
Irrelevant = "irrelevant"
}
interface IButtonProps {
text: string,
/** The type of button, pulled from the Enum SelectableButtonTypes */
type: SelectableButtonTypes,
action: (selected: boolean) => void
}
const ExtendedSelectableButton = ({text, type, action}: IButtonProps) => {
let [selected, setSelected] = useState(false)
return (<button className={"extendedSelectableButton " + type + (selected? " selected" : "")} onClick={ _ => {
setSelected(!selected)
action(selected)
}}>{text}</button>)
}
/** Exporting the component AND the Enum */
export { ExtendedSelectableButton, SelectableButtonTypes}
Importing and using Enums:
import React from 'react';
import './App.css';
import {ExtendedSelectableButton, SelectableButtonTypes} from './components/ExtendedSelectableButton/ExtendedSelectableButton'
const App = () => {
return (
<div className="App">
<header className="App-header">
<ExtendedSelectableButton type={SelectableButtonTypes.Important} text="Select me!!" action={ (selected) => {
console.log(selected)
}} />
</header>
</div>
);
}
export default App;
Optional types for your props
//...
interface IProps {
prop1: string,
prop2: number,
myFunction: () => void,
prop3?: boolean //optional prop
}
//...
function MyComponent({...props}: IProps) {
//...
}
/** You can then use them like this */
<mycomponent prop1="text here" prop2=404 myFunction={() = {
//...
}} />
Hooks
Thanks to TypeScript’s type validation, you can enforce the type (or interface) of the initial value of the state, like this:
const [user, setUser] = React.useState<IUser>(user);
Nullable values to hooks
const [user, setUser] = React.useState<IUser | null>(null);
// later...
setUser(newUser);
Generic Components 通用组件
interface Props<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>(props: Props<T>) {
const { items, renderItem } = props;
const [state, setState] = React.useState<T[]>([]);
return (
<div>
{items.map(renderItem)}
</div>
);
}
//type inference
ReactDOM.render(
<List
items={["a", "b"]} // type of 'string' inferred here
renderItem={item => (
<li key={item}>
{item.trim()} //allowed, because we're working with 'strings' all around
</li>
)}
/>,
document.body
);
//directly specifying the data types
ReactDOM.render(
<List<number>
items={[1,2,3,4]}
renderItem={item => <li key={item}>{item.toPrecision(3)}</li>}
/>,
document.body
);
Extending HTML Elements
export interface IBorderedBoxProps extends React.HTMLAttributes<HTMLDivElement> {
title: string;
}
class BorderedBox extends React.Component<IBorderedBoxProps, void> {
public render() {
const {children, title, ...divAttributes} = this.props;
return (
//it is a DIV afterall, and we're trying to let the user use this component knowing that.
<div {...divAttributes} style={{border: "1px solid red"}}>
<h1>{title}</h1>
{children}
</div>
);
}
}
const myBorderedBox = <BorderedBox title="Hello" onClick={() => alert("Hello")}/>;
Event Types
function eventHandler(event: React.MouseEvent<HTMLAnchorElement>) {
console.log("TEST!")
}
const ExtendedSelectableButton = ({text, type, action}: IButtonProps) => {
let [selected, setSelected] = useState(false)
return (<button className={"extendedSelectableButton " + type + (selected? " selected" : "")} onClick={eventHandler}>{text}</button>)
//And you’ll see an error message
//so You can, however, use unions to allow a single handler to be re-used by multiple components:
/** This will allow you to use this event handler both, on anchors and button elements */
function eventHandler(event: React.MouseEvent<HTMLAnchorElement | HTMLButtonElement>) {
console.log("TEST!")
}
Typescript声明
类型
明确一下, 类型通过以下方式引入:
- 类型别名声明(type sn = number | string;)
- 接口声明(interface I { x: number[]; })
- 类声明(class C { })
- 枚举声明(enum E { A, B, C })
- 指向某个类型的import声明
以上每种声明形式都会创建一个新的类型名称。
值
与类型相比,你可能已经理解了什么是值。 值是运行时名字,可以在表达式里引用。 比如 let x = 5;创建一个名为x的值。
同样,以下方式能够创建值:
- let,const,和var声明
- 包含值的namespace或module声明
- enum声明
- class声明
- 指向值的import声明
- function声明
命名空间
类型可以存在于命名空间里。 比如,有这样的声明 let x: A.B.C, 我们就认为 C类型来自A.B命名空间。
这个区别虽细微但很重要 — 这里,A.B不是必需的类型或值。
简单的组合:一个名字,多种意义
一个给定的名字A,我们可以找出三种不同的意义:一个类型,一个值或一个命名空间。 要如何去解析这个名字要看它所在的上下文是怎样的。 比如,在声明 let m: A.A = A;, A首先被当做命名空间,然后做为类型名,最后是值。 这些意义最终可能会指向完全不同的声明!
内置组合
眼尖的读者可能会注意到,比如,class同时出现在类型和值列表里。 class C { }声明创建了两个东西: 类型C指向类的实例结构, 值C指向类构造函数。 枚举声明拥有相似的行为。
用户组合
假设我们写了模块文件foo.d.ts:
export var SomeVar: { a: SomeType };
export interface SomeType {
count: number;
}
这样使用它:
import * as foo from './foo';
let x: foo.SomeType = foo.SomeVar.a;
console.log(x.count);
这可以很好地工作,但是我们知道SomeType和SomeVar很相关 因此我们想让他们有相同的名字。 我们可以使用组合通过相同的名字 Bar表示这两种不同的对象(值和对象):
export var Bar: { a: Bar };
export interface Bar {
count: number;
}
这提供了解构使用的机会:
import { Bar } from './foo';
let x: Bar = Bar.a;
console.log(x.count);
再次地,这里我们使用Bar做为类型和值。
高级组合
有一些声明能够通过多个声明组合。 比如, class C { }和interface C { }可以同时存在并且都可以做为C类型的属性。
利用interface添加
我们可以使用一个interface往别一个interface声明里添加额外成员:
interface Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number; // 继续增加
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
这同样作用于类:
class Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number; // 往class 增加 y
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
注意我们不能使用接口往 类型别名里添加成员(type s = string;)
使用namespace添加
namespace声明可以用来添加新类型,值和命名空间,只要不出现冲突。
比如,我们可能添加静态成员到一个类:
class C {
}
// ... elsewhere ...
namespace C {
export let x: number;
}
let y = C.x; // OK
注意在这个例子里,我们添加一个值到C的静态部分(它的构造函数)。 这里因为我们添加了一个 值,且其它值的容器是另一个值 (类型包含于命名空间,命名空间包含于另外的命名空间)
我们还可以给类添加一个命名空间类型:
class C {
}
// ... elsewhere ...
namespace C {
export interface D { }
}
let y: C.D; // OK
在这个例子里,直到我们写了namespace声明才有了命名空间C。 做为命名空间的 C不会与类创建的值C或类型C相互冲突。
namespace X {
export interface Y { }
export class Z { }
}
// ... elsewhere ...
namespace X {
export var Y: number;
export namespace Z {
export class C { }
}
}
type X = string;
在这个例子里,第一个代码块创建了以下名字与含义:
- 一个值X(因为namespace声明包含一个值,Z)
- 一个命名空间X(因为namespace声明包含一个值,Z)
- 在命名空间X里的类型Y
- 在命名空间X里的类型Z(类的实例结构)
- 值X的一个属性值Z(类的构造函数)
第二个代码块创建了以下名字与含义:
- 值Y(number类型),它是值X的一个属性
- 一个命名空间Z
- 值Z,它是值X的一个属性
- 在X.Z命名空间下的类型C
- 值X.Z的一个属性值C
- 类型X
使用export =或import
一个重要的原则是export和import声明会导出或导入目标的所有含义。