相关文章推荐
暴走的茴香  ·  TypeScript中联合类型赋值null/ ...·  2 月前    · 
慷慨大方的皮带  ·  vue3+ts:shims-vue.d.ts·  2 月前    · 
叛逆的山楂  ·  调试 JavaScript 或 ...·  2 月前    · 
魁梧的硬币  ·  中国人民银行营口市分行关于东北虎豹国家公园普 ...·  7 月前    · 
健身的钥匙  ·  焦曼婷_百度百科·  1 年前    · 
喝醉的消防车  ·  高会兑换码怎么获得-百度经验·  1 年前    · 
想出国的碗  ·  九阳神王_百度百科·  1 年前    · 
温柔的打火机  ·  中国日报网:首届华调吟诵联谊研讨会在河大、郑 ...·  1 年前    · 
Code  ›  TypeScript入门完全指南(基础篇) | 微信开放社区
函数声明 枚举类型 js代码 typescript
https://developers.weixin.qq.com/community/develop/article/doc/000246978242b0fa54483e2dc51413
开朗的洋葱
2 年前

交流专区
服务市场
微信学堂
文档
小程序
  • 常用主页

    小程序

    小游戏

    企业微信

    微信支付

  • 服务市场
    微信学堂
    文档
登录
评论

置顶 TypeScript入门完全指南(基础篇) 精选 热门

孟健 2019-03-18
4586 浏览
4 评论

TypeScript的官方文档虽然较为全面,但通读下来却要耗时不少;另外,TypeScript中文资料本身也比较缺乏,本文可作为准备尝试TypeScript的同学入门使用。

[TOC]

为什么JS需要类型检查

TypeScript的设计目标在 这里 可以查看到,简单概括为两点:

  • 为JavaScript提供一个可选择的类型检查系统;
  • 为JavaScript提供一个包含将来新特性的版本。
  • TypeScript的核心价值体现在第一点,第二点可以认为是TypeScript的向后兼容性保证,也是TypeScript必须要做到的。

    那么为什么JS需要做静态类型检查呢?在几年前这个问题也许还会存在比较大的争议,在前端日趋复杂的今天,经过像Google、Microsoft、FaceBook这样的大公司实践表明,类型检查对于代码可维护性和可读性是有非常大的帮助的,尤其针对于需要长期维护的规模性系统。

    TypeScript优势

    在我看来,TypeScript能够带来最直观上的好处有三点:

  • 帮助更好地重构代码;
  • 类型声明本身是最好查阅的文档。
  • 编辑器的智能提示更加友好。
  • 一个好的代码习惯是时常对自己写过的代码进行小的重构,让代码往更可维护的方向去发展。然而对于已经上线的业务代码,往往测试覆盖率不会很高,当我们想要重构时,经常会担心自己的改动会产生各种不可预知的bug。哪怕是一个小的重命名,也有可能照顾不到所有的调用处造成问题。

    如果是一个TypeScript项目,这种担心就会大大降低,我们可以依赖于TypeScript的静态检查特性帮助找出一个小的改动(如重命名)带来的其他模块的问题,甚至对于模块文件来说,我们可以直接借助编辑器的能力进行 “一键重命名” 操作。

    另外一个问题,如果你接手过一个老项目,肯定会头痛于各种文档的缺失和几乎没有注释的代码,一个好的TypeScript项目,是可以做到代码即文档的,通过声明文件我们可以很好地看出各个字段的含义以及哪些是前端必须字段:

    // 砍价用户信息
    export interface BargainJoinData {
      curr_price: number; // 当前价
      curr_ts: number; // 当前时间
      init_ts: number; // 创建时间
      is_bottom_price: number; // 砍到底价
    

    TypeScript对开发者是友好的

    TypeScript在设计之初,就确定了他们的目标并不是要做多么严格完备的类型强校验系统,而是能够更好地兼容JS,更贴合JS开发者的开发习惯。可以说这是MS的商业战略,也是TS能够成功的关键性因素之一。它对JS的兼容性主要表现为以下三个方面:

    隐式的类型推断

    var foo = 123;
    foo = "456"; // Error: cannot assign `string` to `number`
    

    当我们对一个变量或函数等进行赋值时,TypeScript能够自动推断类型赋予变量,TypeScript背后有非常强大的自推断算法帮助识别类型,这个特性无疑可以帮助我们简化一些声明,不必像其他语言那样处处是声明,也可以让我们看代码时更加轻松。

    结构化的类型

    TypeScript旨在让JS开发者更简单地上手,因此将类型设计为“结构化”(Structural)的而非“名义式”(Nominal)的。

    什么意思呢?意味着TypeScript的类型并不根据定义的名字绑定,只要是形似的类型,不管名称相不相同,都可以作为兼容类型(这很像所谓的duck typing),也就是说,下面的代码在TypeScript中是完全合法的:

    class Foo { method(input: string) { /* ... */ } }
    class Bar { method(input: string) { /* ... */ } }
    let test: Foo = new Bar(); // no Error!
    

    这样实际上可以做到类型的最大化复用,只要形似,对于开发者也是最好理解的。(当然对于这个示例最好的做法是抽出一个公共的interface)

    知名的JS库支持

    TypeScript有强大的DefinitelyTyped社区支持,目前类型声明文件基本上已经覆盖了90%以上的常用JS库,在编写代码时我们的提示是非常友好的,也能做到安全的类型检查。(在使用第三方库时,可以现在这个项目中检索一下有没有该库的TS声明,直接引入即可)

    回顾两个基础知识

    在进入正式的TS类型介绍之前,让我们先回顾一下JS的两个基础:

    相等性判断

    我们都知道,在JS里,两个等号的判断会进行隐式的类型转换,如:

    console.log(5 == "5"); // true 
    console.log(0 == ""); // true
    

    在TS中,因为有了类型声明,因此这两个结果在TS的类型系统中恒为false,因此会有报错:

    This condition will always return 'false' since the types '5' and '"5"' have no overlap.
    

    所以在代码层面,一方面我们要避免这样两个不同类型的比较,另一方面使用全等来代替两个等号,保证在编译期和运行期具有相同的语义。

    对于TypeScript而言,只有null和undefined的隐式转换是合理的:

    console.log(undefined == undefined); // true
    console.log(null == undefined); // true
    console.log(0 == undefined); // false
    console.log('' == undefined); // false
    console.log(false == undefined); // false
    

    类(Class)

    对于ES6的Class,我们本身已经很熟悉了,值得一提的是,目前对于类的静态属性、成员属性等有一个提案——proposal-class-fields已经进入了Stage3,这个提案包含了很多东西,主要是类的静态属性、成员属性、公有属性和私有属性。其中,私有属性的提案在社区内引起了非常大的争议,由于它的丑陋和怪异遭受各路人马的抨击,现TC39委员会已决定重新思考该提案。

    现在让我们来看看TypeScript对属性访问控制的情况:

    public protected private
  • TypeScript的类型系统设计是可选的,意味着JavaScript就是TypeScript。
  • TypeScript的报错并不会阻止JS代码的生成,你可以渐进式地将JS逐步迁移为TS。
  • :<TypeAnnotation>
    

    TypeScript的基本类型语法是在变量之后使用冒号进行类型标识,这种语法也揭示了TypeScript的类型声明实际上是可选的。

    原始值类型

    var num: number;
    var str: string;
    var bool: boolean;
    

    TypeScript支持三种原始值类型的声明,分别是number、string和boolean。

    对于这三种原始值,TS同样支持以它们的字面量为类型:

    var num: 123;
    var str: '123';
    var bool: true;
    

    这类字面量类型配合上联合类型还是十分有用的,我们后面再讲。

    对于数组的声明也非常简单,只需要加上一个中括号声明类型即可:

    var boolArray: boolean[];
    

    以上就简单地定义了一个布尔类型的数组,大多数情况下,我们数组的元素类型是固定的,如果我们数组内存在不同类型的元素怎么办?

    如果元素的个数是已知有限的,可以使用TS的元组类型:

    var nameNumber: [string, number];
    

    该声明也非常的形象直观,如果元素个数不固定且类型未知,这种情况较为罕见,可直接声明成any类型:

    var arr: any[]
    

    接口类型是TypeScript中最常见的组合类型,它能够将不同类型的字段组合在一起形成一个新的类型,这对于JS中的对象声明是十分友好的:

    interface Name {
        first: string;
        second: string;
    var personName:Name = {
        first: '张三'
    } // Property 'second' is missing in type '{ first: string; }' but required in type 'Name'
    

    上述例子可见,TypeScript对每一个字段都做了检查,若未定义接口声明的字段(非可选),则检查会抛出错误。

    对于对象来说,我们也可以使用内联接口来快速声明类型:

    var personName:{ first: string, second: string } = {
        first: '张三'
    } // Property 'second' is missing in type '{ first: string; }' but required in type 'Name'
    

    内联接口可以帮助我们快速声明类型,但建议谨慎使用,对于可复用以及一般性的接口声明建议使用interface声明。

    对于对象而言,我们可以使用中括号的方式去存取值,对TS而言,同样支持相应的索引类型:

    interface Foo {
      [key:string]: number
    

    对于索引的key类型,TypeScript只支持number和string两种类型,且Number是string的一种特殊情况。

    对于索引类型,我们在一般化的使用场景上更方便:

    interface NestedCSS {
      color?: string;
      nest?: {
        [selector: string]: NestedCSS;
    const example: NestedCSS = {
      color: 'red',
      nest: {
        '.subclass': {
          color: 'blue'
    

    对于接口而言,另一个重要作用就是类可以实现接口:

    interface Point {
        x: number; y: number;
        z: number; // New member
    class MyPoint implements Point { // ERROR : missing member `z`
        x: number; y: number;
    

    对类而言,实现接口,意味着需要实现接口的所有属性和方法,这和其他语言是类似的。

    函数是TypeScript中最常见的组成单元:

    interface Foo {
        foo: string;
    // Return type annotated as `: Foo`
    function foo(sample: Foo): Foo {
        return sample;
    

    对于函数而言,本身有参数类型和返回值类型,都可进行声明。

    对于参数,我们可以声明可选参数,即在声明之后加一个问号:

    function foo(bar: number, bas?: string): void {
        // ..
    

    void和never类型

    另外,上述例子也表明,当函数没有返回值时,可以用void来表示。

    当一个函数永远不会返回时,我们可以声明返回值类型为never:

    function bar(): never {
        throw new Error('never reach');
    

    callable和newable

    我们还可以使用接口来定义函数,在这种函数实现接口的情形下,我们称这种定义为callable:

    interface Complex {
      (bar?: number, ...others: boolean[]): number;
    var foo: Complex;
    

    这种定义方式在可复用的函数声明中非常有用。

    callable还有一种特殊的情况,该声明中指定了new的方法名,称之为newable:

    interface CallMeWithNewToGetString {
      new(): string
    var foo: CallMeWithNewToGetString;
    new foo();
    

    这个在构造函数的声明时非常有用。

    最后,一个函数可以支持多种传参形式,这时候仅仅使用可选参数的约束可能是不够的,如:

    unction padding(a: number, b?: number, c?: number, d?: number) {
        if (b === undefined && c === undefined && d === undefined) {
            b = c = d = a;
        else if (c === undefined && d === undefined) {
            c = a;
            d = b;
        return {
            top: a,
            right: b,
            bottom: c,
            left: d
    

    这个函数可以支持四个参数、两个参数和一个参数,如果我们粗略的将后三个参数都设置为可选参数,那么当传入三个参数时,TS也会认为它是合法的,此时就失去了类型安全,更好的方式是声明函数重载:

    function padding(all: number);
    function padding(topAndBottom: number, leftAndRight: number);
    function padding(top: number, right: number, bottom: number, left: number);
    function padding(a: number, b?: number, c?: number, d?: number) {
       //...
    

    函数重载写法也非常简单,就是重复声明不同参数的函数类型,最后一个声明包含了兼容所有重载声明的实现。这样,TS类型系统就能准确的判断出该函数的多态性质了。

    使用callable的方式也可以声明重载:

    interface Padding {
      (all: number): any
      (topAndBottom: number, leftAndRight: number): any
      (top: number, right: number, bottom: number, left: number): any
    

    any在TypeScript中是一个比较特殊的类型,声明为any类型的变量就像动态语言一样不受约束,好像关闭了TS的类型检查一般。对于any类型的变量,可以将其赋予任何类型的值:

    var power: any;
    power = '123';
    power = 123;
    

    any对于JS代码的迁移是十分友好的,在已经成型的TypeScript项目中,我们要慎用any类型,当你设置为any时,意味着告诉编辑器不要对它进行任何检查。

    null和undefined

    null和undefined作为TypeScript的特殊类型,它同样有字面量的含义,之前我们已经了解到。

    值得注意的是,null和undefined可以赋值给任意类型的变量:

    var num: number;
    var str: string;
    // 赋值给任意类型的变量都是合法的
    num = null;
    str = undefined;
    

    void和never

    在函数类型中,我们已经介绍了两种类型,专门修饰函数返回值。

    readonly

    readonly是只读属性的修饰符,当我们的属性是只读时,可以用该修饰符加以约束,在类中,用readonly修饰的属性仅可以在构造函数中初始化:

    class Foo {
        readonly bar = 1; // OK
        readonly baz: string;
        constructor() {
            this.baz = "hello"; // OK
    

    一个实用场景是在react中,props和state都是只读的:

    interface Props {
        readonly foo: number;
    interface State {
        readonly bar: number;
    export class Something extends React.Component<Props,State> {
      someMethod() {
        this.props.foo = 123; // ERROR: (props are immutable)
        this.state.baz = 456; // ERROR: (one should use this.setState)  
    

    当然,React本身在类的声明时会对传入的props和state做一层ReadOnly的包裹,因此无论我们是否在外面显式声明,赋值给props和state的行为都是会报错的。

    注意,readonly听起来和const有点像,需要时刻保持一个概念:

  • readonly是修饰属性的
  • const是声明变量的
  • 在更加一般化的场景,我们的类型可能并不固定已知,它和any有点像,只不过我们希望在any的基础上能够有更近一步的约束,比如:

    function reverse<T>(items: T[]): T[] {
        var toreturn = [];
        for (let i = items.length - 1; i >= 0; i--) {
            toreturn.push(items[i]);
        return toreturn;
    

    reverse函数是一个很好的示例,对于一个通用的函数reverse来说,数组元素的类型是未知的,可以是任意类型,但reverse函数的返回值也是个数组,它和传入的数组类型是相同的,对于这个约束,我们可以使用泛型,其语法是尖括号,内置泛型变量,多个泛型变量用逗号隔开,泛型变量名称没有限制,一般而言我们以大写字母开头,多个泛型变量使用其语义命名,加上T为前缀。

    在调用时,可以显示的指定泛型类型:

    var reversed = reverse<number>([1, 2, 3]);
    

    也可以利用TypeScript的类型推断,进行隐式调用:

    var reversed = reverse([1, 2, 3]);
    

    由于我们的参数类型是T[],而传入的数组类型是一个number[],此时T的类型被TypeScript自动推断为number。

    对于泛型而言,我们同样可以作用于接口和类:

    interface Array<T> {
     reverse(): T[];
     // ...
    

    在JS中,一个变量的类型可能拥有多个,比如:

    function formatCommandline(command: string[]|string) {
        var line = '';
        if (typeof command === 'string') {
            line = command.trim();
        } else {
            line = command.join(' ').trim();
    

    此时我们可以使用一个|分割符来分割多种类型,对于这种复合类型,我们称之为联合类型。

    如果说联合类型的语义等同于或者,那么交叉类型的语义等同于集合中的并集,下面的extend函数是最好的说明:

    function extend<T, U>(first: T, second: U): T & U {
        let result = <T & U> {};
        for (let id in first) {
            result[id] = first[id];
        for (let id in second) {
            if (!result.hasOwnProperty(id)) {
                result[id] = second[id];
        return result;
    

    该函数最终以T&U作为返回值值,该类型既包含了T的字段,也包含了U的字段,可以看做是两个类型的并集。

    TypeScript为类型的复用提供了更便捷的方式——类型别名。当你想复用类型时,可能在该场景下要为已经声明的类型换一个名字,此时可以使用type关键字来进行类型别名的定义:

    interface state {
    export type userState = state;
    

    我们同样可以使用type来声明一个类型:

    type Text = string | { text: string };
    type Coordinates = [number, number];
    type Callback = (data: string) => void;
    

    对于type和interface的取舍:

  • 如果要用交叉类型或联合类型,使用type。
  • 如果要用extend或implement,使用interface。
  • 其余情况可看个人喜好,个人建议type更多应当用于需要起别名时,其他情况尽量使用interface。
  • 对于组织一系列相关值的集合,最好的方式应当是枚举,比如一系列状态集合,一系列归类集合等等。

    在TypeScript中,枚举的方式非常简单:

    enum Color {
        Green,
    var col = Color.Red;
    

    默认的枚举值是从0开始,如上述代码,Red=0,Green=1依次类推。

    当然我们还可以指定初始值:

    enum Color {
        Red = 3,
        Green,
    

    此时Red=3, Green=4依次类推。

    大家知道在JavaScript中是不存在枚举类型的,那么TypeScript的枚举最终转换为JavaScript是什么样呢?

    var Color;
    (function (Color) {
        Color[Color["Red"] = 0] = "Red";
        Color[Color["Green"] = 1] = "Green";
        Color[Color["Blue"] = 2] = "Blue";
    })(Color || (Color = {}));
    

    从编译后的代码可以看到,转换为一个key-value的对象后,我们的访问也非常方便:

    var red = Color.Red; // 0
    var redKey = Color[0]; // 'Red'
    var redKey = Color[Color.Red]; // 'Red'
    

    既可以通过key来访问到值,也可以通过值来访问到key。

    Flag标识位

    对于枚举,有一种很实用的设计模式是使用位运算来标识(Flag)状态:

    enum EnvFlags {
      None = 0,
      QQ = 1 << 0,
      Weixin = 1 << 1
    function initShare(flags: EnvFlags) {
      if (flags & EnvFlags.QQ) {
        initQQShare();
      if (flags & EnvFlags.Weixin) {
        initWeixinShare();
    

    在我们使用标识位时,可以遵循以下规则:

  • 使用 |= 增加标志位
  • 使用 &= 和 ~清除标志位
  • 使用 | 联合标识位
  • var
    
    
    
    
        
     flag = EnvFlags.None;
    flag |= EnvFlags.QQ;    // 加入QQ标识位
    Flag &= ~EnvFlags.QQ;   // 清除QQ标识位
    Flag |=  EnvFlags.QQ | EnvFlags.Weixin; // 加入QQ和微信标识位
    

    在枚举定义加上const声明,即可定义一个常量枚举:

    enum Color {
        Red = 3,
        Green,
    

    对于常量枚举,TypeScript在编译后不会产生任何运行时代码,因此在一般情况下,应当优先使用常量枚举,减少不必要代码的产生。

    字符串枚举

    TypeScript还支持非数字类型的枚举——字符串枚举

    export enum EvidenceTypeEnum {
      UNKNOWN = '',
      PASSPORT_VISA = 'passport_visa',
      PASSPORT = 'passport',
      SIGHTED_STUDENT_CARD = 'sighted_tertiary_edu_id',
      SIGHTED_KEYPASS_CARD = 'sighted_keypass_card',
      SIGHTED_PROOF_OF_AGE_CARD = 'sighted_proof_of_age_card',
    

    这类枚举和我们之前使用JavaScript定义常量集合的方式很像,好处在于调试或日志输出时,字符串比数字要包含更多的语义。

    在没有模块化的时代,我们为了防止全局的命名冲突,经常会以命名空间的形式组织代码:

    (function(something) {
        something.foo = 123;
    })(something || (something = {}))
    

    TypeScript内置了namespace变量帮助定义命名空间:

    namespace Utility {
        export function log(msg) {
            console.log(msg);
        export function error(msg) {
            console.error(msg);
    

    对于我们自己的工程项目而言,一般建议使用ES6模块的方式去组织代码,而命名空间的模式可适用于对一些全局库的声明,如jQuery:

    namespace $ {
      export function ajax(//...) {}
    

    当然,命名空间还可以便捷地帮助我们声明静态方法,如和enum的结合使用:

    enum Weekday {
        Monday,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday
    namespace Weekday {
        export function isBusinessDay(day: Weekday) {
            switch (day) {
                case Weekday.Saturday:
                case Weekday.Sunday:
                    return false;
                default:
                    return true;
    const mon = Weekday.Monday;
    const sun = Weekday.Sunday;
    console.log(Weekday.isBusinessDay(mon)); // true
    console.log(Weekday.isBusinessDay(sun)); // false
    

    关于命名规范

    变量名、函数和文件名

  • 推荐使用驼峰命名。
  • // Bad
    var FooVar;
    function BarFunc() { }
    // Good
    var fooVar;
    function barFunc() { }
    

    类、命名空间

  • 推荐使用帕斯卡命名。
  • 成员变量和方法推荐使用驼峰命名。
  • // Bad
    class foo { }
    // Good
    class Foo { }
    // Bad
    class Foo {
        Bar: number;
        Baz() { }
    // Good
    class Foo {
        bar: number;
        baz() { }
    

    Interface、type

  • 推荐使用帕斯卡命名。
  • 成员字段推荐使用驼峰命名。
  • // Bad
    interface foo { }
    // Good
    interface Foo { }
    // Bad
    interface Foo {
        Bar: number;
    // Good
    interface Foo {
        bar: number;
    

    关于模块规范

    export default的争论

    关于是否应该使用export default在这里有详尽的讨论,在AirBnb规范中也有prefer-default-export这条规则,但我认为在TypeScript中应当尽量不使用export default:

    关于链接中提到的重命名问题, 甚至自动import,其实export default也是可以做到的,借助编辑器和TypeScript的静态能力。所以这一点还不是关键因素。

    不过使用一般化的export更让我们容易获得智能提示:

    import /* here */ from 'something';
    

    在这种情况下,一般编辑器是不会给出智能提示的。

    import { /* here */ } from 'something';
    

    我们可以通过智能提示做到快速引入。

    除了这一点外,还有以下几点好处:

  • 对CommonJS是友好的,如果使用export default,在commonJS下需要这样引入:
  • const {default} = require('module/foo');
    

    多了个default无疑感觉非常奇怪。

  • 对动态import是友好的,如果使用export default,还需要显示的通过default字段来访问:
  • const HighChart = await import('https://code.highcharts.com/js/es-modules/masters/highcharts.src.js');
    Highcharts.default.chart('container', { ... }); // 注意 `.default`
    
  • 对于re-exporting是友好的,如果使用export default,那么进行re-export会比较麻烦:
  • import Foo from "./foo"; export { Foo }
    

    相比之下,如果没有export default,我们可以直接使用:

    export * from "./foo"
    

    实践中的一些坑

    实践篇即将到来,敬请期待~

    最后一次编辑于 2019-03-18
    点赞 17
    收藏
    分享

    扫描小程序码分享

    复制链接

    删除文章后,文章内容和评论将一并被删除,且不可恢复。

    删除 取消
    评论
    关闭

    请选择投诉理由

    • 广告内容
    • 违法违规
    • 恶意灌水内容
    • 其他

    4 个评论

    • 零度的田
      零度的田
      2019-03-18
      请 登录 后发表内容
      关闭

      新增或编辑超链接

      确认 取消
      关闭

      插入视频

      确认 取消
      发表

      /强,有些点确实可以借鉴学习。不过,感觉 更多像是笔记。 期待实践篇的内容。


      另外,能否 多讲解一下 index.d.ts 这个描述文件,很多TS 文章都是讲解 语法 相关。实际上,index.d.ts 在使用中也有很多可以学习和借鉴的地方。

      你好,麻烦通过点击下方“反馈信息”按钮,提供出现问题的。

      待楼主反馈
      2019-03-18
      赞同 2
      回复
      关闭

      请选择投诉理由

      • 广告内容
      • 违法违规
      • 恶意灌水内容
      • 其他
    • 铭锋科技
      铭锋科技
      2019-03-19
      请 登录 后发表内容
      关闭

      新增或编辑超链接

      确认 取消
      关闭

      插入视频

      确认 取消
      发表

      看得蒙圈,照做不了

      你好,麻烦通过点击下方“反馈信息”按钮,提供出现问题的。

      待楼主反馈
      2019-03-19
      赞同
      回复
      关闭

      请选择投诉理由

      • 广告内容
      • 违法违规
      • 恶意灌水内容
      • 其他
    • H.Y
      H.Y
      2019-03-19
      请 登录 后发表内容
      关闭

      新增或编辑超链接

      确认 取消
      关闭

      插入视频

      确认 取消
      发表

      mark 入门看

      你好,麻烦通过点击下方“反馈信息”按钮,提供出现问题的。

      待楼主反馈
      2019-03-19
      赞同
      回复
      关闭

      请选择投诉理由

      • 广告内容
      • 违法违规
      • 恶意灌水内容
      • 其他
    • 兰昊
      兰昊
      2019-03-19
     
    推荐文章
    暴走的茴香  ·  TypeScript中联合类型赋值null/undefined_type 'boolean' is not assignable to type 'location
    2 月前
    慷慨大方的皮带  ·  vue3+ts:shims-vue.d.ts
    2 月前
    叛逆的山楂  ·  调试 JavaScript 或 TypeScript 应用 - Visual Studio (Windows) | Microsoft Learn
    2 月前
    魁梧的硬币  ·  中国人民银行营口市分行关于东北虎豹国家公园普通纪念币分配数量的公告_营口市人民政府
    7 月前
    健身的钥匙  ·  焦曼婷_百度百科
    1 年前
    喝醉的消防车  ·  高会兑换码怎么获得-百度经验
    1 年前
    想出国的碗  ·  九阳神王_百度百科
    1 年前
    温柔的打火机  ·  中国日报网:首届华调吟诵联谊研讨会在河大、郑州轻院举行
    1 年前
    今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
    删除内容请联系邮箱 2879853325@qq.com
    Code - 代码工具平台
    © 2024 ~ 沪ICP备11025650号