原文链接:TypeScript入门

之前浏览vue源码的时候发现有TypeScript,一脸懵逼,因而须要入个门。

最近在新环境的日常工作中也须要用到TypeScript,学习过程中遇到一些纳闷,做了记录。

集体感觉还是比拟适宜TypeScript入门的同学浏览的,因为我遇到的这些纳闷,可能你也会遇到。

  • ts类型中的?,<>意思是什么?
  • 什么是duck typing?
  • constructor之前的变量定义是什么?
  • declare是什么?
  • ts中unknown, void, null和undefined,never区别是什么?
  • ts中的泛型束缚是什么?
  • 数组类型的两种定义形式
  • ts中的类型断言
  • 泛型函数与泛型接口
  • 如何了解as const?
  • declare global是什么意思?
  • 如何在TypeScript环境减少一个全局变量?
  • interface能够继承吗?
  • typescript中的&是什么意思?
  • interface与type的区别是什么?
  • enum作为一种类型是什么意思?
  • 我的项目中xxx.d.ts的declare module '*.scss'是什么意思?declare module还能够做什么?
  • typescript如何束缚Promise的类型?
  • typescript中的keyof如何应用?
  • typescript中的typeof如何应用?
  • typescript中的non-null operator是什么?

ts类型中的?意思是什么?

// https://github.com/vuejs/vue/blob/dev/src/core/observer/watcher.jsbefore: ?Function;options?: ?Object,

这是ts的interface中的一个概念。ts的interface就是"duck typing"或者"structural subtyping",类型查看次要关注the shape that values have。因而咱们先来相熟一下interface,再引出?的解释。

TypeScript一般形式定义函数:
function print(obj: {label: string}) {    console.log(obj.label);}let foo = {size: 10, label: "这是foo, 10斤"};print(foo);
TypeScript interface形式定义函数:
interface labelInterface {    label: string;}function print(obj: labelInterface) {    console.log(obj.label);}let foo = {size: 10, label: "这是foo, 10斤"};print(foo);

进入正题,TypeScript中的是什么意思?Optional Properties。

Optional Properties
  • 并不是interface中的所有属性都是required的,一些存在特定条件下,一些基本不存在。
  • Optional Properties实用于"option bags"的设计模式,这种设计模式意思是:咱们传递一个对象到函数,这个函数只有几个属性,没有其余更多的属性。
  • Optional Property的益处在于,清晰的看清楚有哪些属性,避免传入不属于该interface的属性。

    interface SquareConfig {  color?: string;  width?: number;}function createSquare(config: SquareConfig): {color: string; area: number} {  let newSquare = {color: "white", area: 100};  if (config.clor) {      // Error: Property 'clor' does not exist on type 'SquareConfig'      newSquare.color = config.color;  }  if (config.width) {      newSquare.area = config.width * config.width;  }  return newSquare;}let mySquare = createSquare({color: "black"});
    Interfaces with optional properties are written similar to other interfaces, with each optional property denoted by a ? at the end of the property name in the declaration.

什么是?和Optional Properties呢?interface的某些非required属性名的开端,增加?这是一个optional property,其实就是字面意思,条件属性。

Optional Property只是属性名,也就是options?: ?Object,中options后的问号,拿属性值类型前的问号是什么意思,也就是?Object,是什么意思?
此处的问号代表属性值类型是否能够是null类型,然而只有strictNullChecks为on时,值类型能力为null。

  /**   * @type {?number}   * strictNullChecks: true -- number | null   * strictNullChecks: off -- number   * */  var nullable;

咱们的例子中,options?:?Object的意思是options的值类型能够是Object,null(仅在strictNullChecks为true时容许)。

ts类型中的<>什么意思?

deps: Array<Dep>anewDeps: Array<Dep>

ts中的数组类型与java中的定义相似:

let list: number[] = [1, 2, 3];let list: Array<number> = [1, 2, 3];

什么是duck typing?

duck test。如果"走路像鸭子,叫声像鸭子,那么这就是鸭子"。
在computer programming,用于'判断对象是否能够依照预期的目标应用'。
通常的typing中,适用性取决于对象的type。duck typing不一样,对象的适用性取决于指定method或property的存在与否,而不是取决于对象本身的类型。

前端工程师根本都是duck typing,因为JavaScript没有type。 --这话是我说的
Python3 example
class Duck:    def fly(self):        print("Duck flying")class Airplane:    def fly(self):        print("Airplane flying")class Whale:    def swim(self):        print("Whale swimming")def lift_off(entity):    entity.fly()duck = Duck()airplane = Airplane()whale = Whale()lift_off(duck) # prints `Duck flying`lift_off(airplane) # prints `Airplane flying`lift_off(whale) # Throws the error `'Whale' object has no attribute 'fly'`
Javascript example
class Duck {    fly() {        console.log("Duck flying")    }}class Airplane {    fly() {        console.log("Airplane flying")    }}class Whale {    swim() {        console.log("Whale swimming")    }}function liftOff(entity) {    entity.fly()}const duck = new Duck();const airplane = new Airplane();const whale = new Whale();liftOff(duck); // Duck flyingliftOff(airplane); // Airplane flyingliftOff(whale); // Uncaught TypeError: entity.fly is not a function

constructor之前的变量定义是什么?

例如vnode的定义:

export default class VNode {  tag: string | void;  data: VNodeData | void;  children: ?Array<VNode>;  text: string | void;  elm: Node | void;  ns: string | void;  context: Component | void; // rendered in this component's scope  key: string | number | void;  componentOptions: VNodeComponentOptions | void;  componentInstance: Component | void; // component instance  parent: VNode | void; // component placeholder node  // strictly internal  raw: boolean; // contains raw HTML? (server only)  isStatic: boolean; // hoisted static node  isRootInsert: boolean; // necessary for enter transition check  isComment: boolean; // empty comment placeholder?  isCloned: boolean; // is a cloned node?  isOnce: boolean; // is a v-once node?  asyncFactory: Function | void; // async component factory function  asyncMeta: Object | void;  isAsyncPlaceholder: boolean;  ssrContext: Object | void;  fnContext: Component | void; // real context vm for functional nodes  fnOptions: ?ComponentOptions; // for SSR caching  fnScopeId: ?string; // functional scope id support  constructor ()...}

http://www.typescriptlang.org...
typeScript中的class要比es6的多一项:property。这和java或者c#中的统一。

propertyconstructormethod

实际上es6提供了一种公有变量,仅仅能在class外部拜访。

class Rectangle {  #height = 0;  #width;  constructor(height, width) {        this.#height = height;    this.#width = width;  }}

冒号前面的:VNode什么意思?

export function cloneVNode (vnode: VNode): VNode {...}

TypeScript中的函数返回值类型。

declare是什么?

申明这是一个definition。

  • declare是ts中用于写定义文件的关键字。
  • declare能够定义全局变量,全局函数,全局命名空间,class等等。
  • declare能够依照上面这样去应用:

    declare var foo:number;declare function greet(greeting: string): void;declare namespace myLib {  function makeGreeting(s: string): string;  let numberOfGreeting: number;}declare function getWidget(n: number): Widget;declare function getWidget(s: string): Widget[];declare class Greeter {  constructor(greeting: string);  greeting: string;  showGreeting(): void;}

ts中any,unknown, void, null和undefined,never区别是什么?

null,undefined就是js中的意思。

any: 任意类型,审慎应用,防止使typescript变成anyscript
unknown: 与any相似,然而比any更加平安
void: 通常用于返回值的函数
never:never occur 从来不会产生的类型,例如永远不会有后果的,抛出异样或者死循环。

ts中的泛型束缚是什么?

基于string(boolean, Function)类型

function loggingIdentity<T extends string>(arg: T): T {    console.log(arg.length);    return arg;}loggingIdentity("hello"); // 5loggingIdentity(2); // Argument of type 'number' is not assignable to parameter of type 'string'.

基于自定义的interface

interface Lengthwise {    length: number;}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;}loggingIdentity(3);  // Error, number doesn't have a .length propertyloggingIdentity({length: 10, value: 3}); // 10

ts2.8公布阐明

// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.htmltype TypeName<T> = T extends string  ? "string"  : T extends number  ? "number"  : T extends boolean  ? "boolean"  : T extends undefined  ? "undefined"  : T extends Function  ? "function"  : "object";type T0 = TypeName<string>; // "string"type T1 = TypeName<"a">; // "string"type T2 = TypeName<true>; // "boolean"type T3 = TypeName<() => void>; // "function"type T4 = TypeName<string[]>; // "object"

同时反对type和interface两种类型的泛型束缚

interface reduxModel<T> {    reducers: T extends string ? {[x in T]: () => void}: T,}type TType = "foo" | "bar" | 'baz'interface TInterface {    "foo": () => void,    "bar": () => void,    'baz': () => void}const ireducers = {    "foo": () => void}const model : reduxModel<TType> = {    reducers: ireducers    // 失常运行}const model : reduxModel<TInterface> = {    reducers: ireducers    // Type '{ foo: () => undefined; }' is missing the following properties from type 'TInterface': "bar", 'baz'}

数组类型的两种定义形式

Array<类型>

Array前面加一个<>,<>内申明元素类型。

type Foo= Array<string>;
interface Bar {     baz: Array<{          name: string,          age: number,     }>}

类型[]

元素类型前面加一个[]。

type Foo = string[]
interface Bar {    baz : {          name: string,          age: number,     }[]}

ts中的类型断言

TypeScript容许咱们笼罩推断和剖析出的视图类型为咱们想要的任意形式,这种机制叫做类型断言(Type Assertion),类型断言会通知编译器你比它更加晓得具体是哪种类型,编译器不必再二次推断了。
类型断言往往是产生在编译器编译期间,用于提醒编译器如何剖析咱们的代码。

  • 语法
  • 迁徙js代码
  • 类型断言的问题
  • 指定event类型
  • 慎用as any和as unknown
  • type与类型断言

语法

interface Foo {    name: string,}type Any = any;let a:Foo = {} as Foo;let a:Foo = {} as Any;

any是任意类型的子类型,所以任意类型都能够被as any,还是倡议审慎应用,防止变为anyscript。

迁徙js代码

var foo = {};foo.bar = 123; // Error: property 'bar' does not exist on `{}`foo.bas = 'hello'; // Error: property 'bas' does not exist on `{}`
interface Foo {    bar: number;    bas: string;}var foo = {} as Foo;foo.bar = 123;foo.bas = 'hello';  // 正文掉这一行也不会报错

类型断言的问题

foo.bas = 'hello'; // 正文掉这一行也不会报错
如果是上面的形式就会报错了,会提醒短少bas的定义

interface Foo {    bar: number;    bas: string;}var foo : Foo= {    bar: 123};

所以说,类型断言是不够谨严的,倡议应用var foo : Foo这种形式。

指定event类型

function handler (event: Event) {    let mouseEvent = event as MouseEvent;}
function handler(event: Event) {    let element = event as HTMLElement; // HTMLElement不是一个齐全的event子类型,因而不能充沛重叠,须要加一个unknown或者any}

二次断言编译提醒勾销:

function handler(event: Event) {    let element = event as unknown as HTMLElement; // Okay!}

慎用as any和as unknown

通常状况是类型断言S和T的话,S为T的子类型,或者T为S的子类型,这种是绝对平安的。
如果是用as any或者as unknown,是十分不平安的。慎用!慎用!

// 审慎应用as anyas known

type与类型断言

type keys = 'foo' | 'bar' | 'baz'obj[key as keys]是什么意思?
与variable:type相似,这是另外一种类型束缚。

如果不明确的花,看完上面这个demo就明确了。

type keys = 'foo' | 'bar' | 'baz'const obj = {    foo: 'a',    bar: 'b',    baz: 'c'}const test = (key:any) => {    return obj[key] ; // 提醒谬误 type 'any' can't be used to index type '{ foo: string; bar: string; baz: string; }'.}

如何解决这个报错呢?
第一种形式:类型束缚

const test = (key:keys) => {    return obj[key] ;}

第二种形式:类型断言(这种形式罕用于第三方库的callback,返回值类型没有束缚的状况)

const test = (key:any) => {    return obj[key as keys] ;}

须要留神:obj[key as keys]中keys的类型能够少于obj的类型,反过来obj的属性不能少于keys的类型。

泛型函数与泛型接口

泛型函数

想想一个场景,咱们心愿函数的输出与输入类型统一。
你可能会这样做,但这并不能保障输出与输入类型统一。

function log(value: any):any {    return value;}

通过泛型函数能够精准实现:函数名后加一个<T>这里的T能够了解为泛型的名字。指定输出类型为T,返回值为T。

function log<T>(value: T):T {    return value;}

这是一个泛型函数实例,如何定义一种泛型函数类型呢?

type Log = <T>(value: T) => T

应用泛型函数类型束缚函数:

let log : Log = function <T>(value: T):T {    return value;}

泛型接口

接口所有属性灵便,输入输出统一即可。

interface Log {     <T>(value: T): T}let myLog: Log = logmyLog("s")// "s"myLog(1)// 1

接口所有属性必须为同一类型。

interface Log<T> {     (value: T): T}let myLog: Log<string> = logmyLog("s")// "s"myLog(1)// Error

ts中的<>

在ts中,遇到<>的话,尖括号两头大多状况下都是类型。

  • Array<string>
  • <string>[]
  • function <T>(value: T): T { ... }
  • type MyType = <T>(value : T) => T
  • interface MyInterface<T> { (value: T): T }

如何了解as const?

  • 为了解决let赋值问题的,将一个mutable的变量改为readonly。
  • 防止将类型推断为联结类型。

为了解决let赋值问题的,将一个mutable的变量改为readonly。

let x = "hello";x = "world"; // 报错
第一种形式 const
const x = "hello"
第二种形式 "hello"类型
let x: "hello" = "hello";x = "world"; // 
第三种形式 discriminated unions
type Shape =    | { kind: "circle", radius: number }    | { kind: "square", sideLength: number }function getShapes(): readonly Shape[] {    // to avoid widening in the first place.    let result: readonly Shape[] = [        { kind: "circle", radius: 100, },        { kind: "square", sideLength: 50, },    ];    return result;}
第四种形式 as const

.tsx类型文件

// Type '10'let x = 10 as const;// Type 'readonly [10, 20]'let y = [10, 20] as const;// Type '{ readonly text: "hello" }'let z = { text: "hello" } as const;

非.tsx类型文件

// Type '10'let x = <const>10;// Type 'readonly [10, 20]'let y = <const>[10, 20];// Type '{ readonly text: "hello" }'let z = <const>{ text: "hello" };

优化discriminated unions

function getShapes() {    let result = [        { kind: "circle", radius: 100, },        { kind: "square", sideLength: 50, },    ] as const;        return result;}for (const shape of getShapes()) {    // Narrows perfectly!    if (shape.kind === "circle") {        console.log("Circle radius", shape.radius);    }    else {        console.log("Square side length", shape.sideLength);    }}

防止将类型推断为联结类型。

防止将类型推断为 (boolean | typeof load)[],而是推断为[boolean, typeof load]。

export function useLoading() {  const [isLoading, setState] = React.useState(false);  const load = (aPromise: Promise<any>) => {    setState(true);    return aPromise.finally(() => setState(false));  };  return [isLoading, load] as const; // infers [boolean, typeof load] instead of (boolean | typeof load)[]}

declare global是什么意思?

是为了在全局命名空间做申明,比方为对象减少一个未定义的属性。

为Window减少csrf的定义

declare global {  interface Window {    csrf: string;  }}

为String减少fancyFormat的定义

declare global {  /*~ Here, declare things that go in the global namespace, or augment   *~ existing declarations in the global namespace   */  interface String {    fancyFormat(opts: StringFormatOptions): string;  }}

留神global作用域只能用于导出模块或者内部的模块申明

Augmentations for the global scope can only be directly nested in external modules or ambient module declarations.

如何在TypeScript环境减少一个全局变量?

比方咱们想要实现上面的成果,然而会报错Property '__INITIAL_DATA__' does not exist

<script>  window.__INITIAL_DATA__ = {    "userID": "536891193569405430"  };</script>const initialData = window.__INITIAL_DATA__; // 报错 

应用类型断言

const initialData = (window as any).__INITIAL_DATA__;
type InitialData = {  userID: string;};const initialData = (window as any).__INITIAL_DATA__ as InitialData;const userID = initialData.userID; // Type string

申明全局变量

declare var __INITIAL_DATA__: InitialData;
const initialData = __INITIAL_DATA__;const initialData = window.__INITIAL_DATA__;

在es模块中,有import,export的,须要这样做:

export function someExportedFunction() {  // ...}declare global {   var __INITIAL_DATA__: InitialData;}const initialData = window.__INITIAL_DATA__;

如果在很多文件都用到的话,能够用一个globals.d.ts文件。

利用interface合并

interface Window {  __INITIAL_DATA__: InitialData;}const initialData = window.__INITIAL_DATA__;

在js模块中须要像上面这样:

export function someExportedFunction() {  // ...}declare global {  interface Window {    __INITIAL_DATA__: InitialData;  }}const initialData = window.__INITIAL_DATA__;

interface能够继承吗?

能够的。

interface Base {    foo: string;}interface Props extends Base {    bar: string    baz?: string}const test = (props: Props) => {    console.log(props);}test({ foo: 'hello' }) // Property 'bar' is missing in type '{ foo: string; }' but required in type 'Props'test({ foo: 'hello', bar: 'world' })

当Props继承了Base之后,实际上它最终变成了上面这样:

interface Props extends Base {    foo: string;    bar: string    baz?: string}

Props能够笼罩Base吗?能够,然而只能是required笼罩optional,optional不能笼罩required。

// ✅interface Base {    foo?: string;}interface Props extends Base {    foo: string;    bar: string    baz?: string}
// ❌interface Base {    foo: string;}interface Props extends Base {    foo?: string;    bar: string    baz?: string}

typescript中的&是什么意思?

在react的dts文件中有这样一个定义。

type PropsWithChildren<P> = P & { children?: ReactNode };

typescript中的&指的是穿插类型。

interface ErrorHandling {  success: boolean;  error?: { message: string };}interface ArtworksData {  artworks: { title: string }[];}interface ArtistsData {  artists: { name: string }[];}// These interfaces are composed to have// consistent error handling, and their own data.type ArtworksResponse = ArtworksData & ErrorHandling;type ArtistsResponse = ArtistsData & ErrorHandling;const handleArtistsResponse = (response: ArtistsResponse) => {  if (response.error) {    console.error(response.error.message);    return;  }  console.log(response.artists);};

晓得&是ts中的穿插类型当前,咱们就明确PropsWithChildren的意思了,而且也明确为什么react的函数式组件会比一般函数组件多了children属性。

它的意思是PropsWithChildren类型是P和对象{children?: ReactNode}的穿插类型,也就是通过&连贯两个对象之后,最终生成的对象是领有children这个可选属性的。

interface与type的区别是什么?

An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot.
An interface can have multiple merged declarations, but a type alias for an object type literal cannot.
  1. interface能够继承(比方用extends),type不能够
  2. interface能够实现有多个合并申明,type不能够

enum作为一种类型是什么意思?

在浏览pixi.js的源码中,发现有将enum作为了一种类型。

enum也能够作为一种类型去束缚。

// pixi/constantsexport enum BLEND_MODES {    NORMAL = 0,    ADD = 1,    MULTIPLY = 2,    SCREEN = 3,    OVERLAY = 4,}export enum ANOTHER_ENUM {    FOO = 5,    BAR = 6}import { BLEND_MODES } from '@pixi/constants';export class Sprite extends Container{    public blendMode: BLEND_MODES;    constructor(){        this.blendMode = BLEND_MODES.NORMAL; // 最佳        //  this.blendMode = 0 这样是能够的,次之        //  this.blendMode = ANOTHER_ENUM.FOO 这样ts会报错    }}

我的项目中xxx.d.ts的declare module '*.scss'是什么意思?declare module还能够做什么?

我的项目中xxx.d.ts的declare module '*.scss'是什么意思?

// externals.d.tsdeclare module '*.scss'
默认状况下import style from 'style.scss'在ts的ide校验器里会报错,那就用d.ts假设定义所有scss结尾的文件是module。--社长

假如将declare module '*.scss'正文掉,ide会报错,然而能够通过lint。

declare module还能够做什么?

当咱们引入了一个微软官网@types/*中不存在的自定义包时,ide会报错。

例如上面这样:

如何解决这个报红的谬误呢?declare module

// typing.d.tsdeclare module 'visual-array'

这样报红就隐没了。

typescript如何束缚Promise的类型?

Promise泛型函数

interface Promise<T> {    then<TResult1 = T, TResult2 = never>(onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null): Promise<TResult1 | TResult2>;    catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<T | TResult>;}
interface foo {   bar: ()=>Promise<string>,   baz: ()=>Promise<number[]>,   car: (id)=>Promise<boolean[]>}

typescript中的keyof如何应用?

最简

type Point = { x: number; y: number };type P = keyof Point; // 'x' | 'y'let foo: P = 'x';let bar: P = 'y';let baz: P = 'z'; // ❌

罕用

interface Person {  name: string;  age: number;  location: string;}type K1 = keyof Person; // "name" | "age" | "location"type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ...type K3 = keyof { [x: string]: Person }; // stringtype P1 = Person["name"]; // stringtype P2 = Person["name" | "age"]; // string | numbertype P3 = string["charAt"]; // (pos: number) => stringtype P4 = string[]["push"]; // (...items: string[]) => numbertype P5 = string[][0]; // string

keyof使得函数类型平安(type-safe)

function getProperty<T, K extends keyof T>(obj: T, key: K) {  return obj[key]; // Inferred type is T[K]}function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) {  obj[key] = value;}let x = { foo: 10, bar: "hello!" };let foo = getProperty(x, "foo"); // numberlet bar = getProperty(x, "bar"); // stringlet oops = getProperty(x, "wargarbl"); // Error! "wargarbl" is not "foo" | "bar"setProperty(x, "foo", "string"); // Error!, string expected number

Partial,Required,Readonly,Pick 泛型工具类型的实现原理

type Partial<T> = {    [P in keyof T]? : T[P];}
type Required<T> = {    [P in keyof T]?- : T[P]; }
type Readonly<T> = {    readonly [P in keyof T] : T[P];}
type Pick<T, K extends keyof T> = {    [P in K]: T[P]}

typescript中的typeof如何应用?

js中的typeof次要用于表达式上下文,而ts中的typeof次要用于类型上下文。

let s = "hello";let n: typeof s;//  ^ = let n: string
type Predicate = (x: unknown) => boolean;type K = ReturnType<Predicate>;//   ^ = type K = boolean
function f() {  return { x: 10, y: 3 };}type P = ReturnType<typeof f>;//   ^ = type P = {//       x: number;//       y: number;//   }

typescript中的non-null assert operator是什么?

非null断言操作符:当为null时,产生断言,抛出异样。
可选链:当为null/undefined时,返回undefined。

非空断言操作符和可选链操作符测试

// Non-Null Assertion Operatorconst obj = null;interface Entity {  name?: string;}// 非空断言操作符function nonNull(e?: Entity) {  const s = e!.name; // 产生断言,抛出TypeError}try {  nonNull(obj);} catch (e) {  console.error("nonNull catch", e); // TypeError: Cannot read property 'name' of null}// 可选链function optionalChaining(e?: Entity) {  const s = e?.name;  console.log(s); // undefined}optionalChaining(obj);

用于函数返回值空检测

function returnNullFunc() {  return null;}try {  returnNullFunc()!.age;} catch (e) {  console.error("returnNullFunc", e); // TypeError: Cannot read property 'age' of null}function returnNonNullFunc() {  return {    age: "18"  };}returnNonNullFunc()!.age;

在线demo:https://codesandbox.io/s/non-...

期待和大家交换,共同进步:

  • 微信公众号: 大大大前端 / excellent_developers
  • Github博客: 趁你还年老233的集体博客
  • SegmentFault专栏:趁你还年老,做个优良的前端工程师
  • 掘金账号:趁你还年老233
致力成为优良前端工程师!