typestcript 常用函数


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;


declare

  1. 在 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.tsmodule-class.d.ts and module-function.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";

模块解析策略

共有两种可用的模块解析策略:NodeClassic。 你可以使用 --moduleResolution标记来指定使用哪种模块解析策略。若未指定,那么在使用了 --module AMD | System | ES2015时的默认值为Classic,其它情况时则为Node

有一个对moduleB的非相对导入import { b } from “moduleB”,它是在/root/src/folder/A.ts文件里,会以如下的方式来定位"moduleB"

  1. /root/src/folder/moduleB.ts
  2. /root/src/folder/moduleB.d.ts
  3. /root/src/moduleB.ts
  4. /root/src/moduleB.d.ts
  5. /root/moduleB.ts
  6. /root/moduleB.d.ts
  7. /moduleB.ts
  8. /moduleB.d.ts

Node.js如何解析模块

为了理解TypeScript编译依照的解析步骤,先弄明白Node.js模块是非常重要的。 通常,在Node.js里导入是通过 require函数调用进行的。 Node.js会根据 require的是相对路径还是非相对路径做出不同的行为。

相对路径很简单。 例如,假设有一个文件路径为 /root/src/moduleA.js,包含了一个导入var x = require(“./moduleB”); Node.js以下面的顺序解析这个导入:

  1. 检查/root/src/moduleB.js文件是否存在。
  2. 检查/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
  3. 检查/root/src/moduleB目录是否包含一个index.js文件。 这个文件会被隐式地当作那个文件夹下的”main”模块。

还是用上面例子,但假设/root/src/moduleA.js里使用的是非相对路径导入var x = require("moduleB");。 Node则会以下面的顺序去解析 moduleB,直到有一个匹配上。

  1. /root/src/node_modules/moduleB.js
  2. /root/src/node_modules/moduleB/package.json (如果指定了"main"属性)
  3. /root/src/node_modules/moduleB/index.js
  4. /root/node_modules/moduleB.js
  5. /root/node_modules/moduleB/package.json (如果指定了"main"属性)
  6. /root/node_modules/moduleB/index.js
  7. /node_modules/moduleB.js
  8. /node_modules/moduleB/package.json (如果指定了"main"属性)
  9. /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声明会导出或导入目标的所有含义。