10分钟了解TypeScript

1.TypeScript是什么

简单来说,TypeScript是JavaScript的超集,是JavaScript语言的扩展,本质上是向JavaScript语言添加了可选的静态类型基于类的面向对象编程。


什么是超集,静态类型 :

超集

就相当于数学里面的包含关系:大圆套小圆,大圆是Typescript,小圆是JavaScript,只要是合法的js程序,就会是合法的Ts程序, 那多出的那部分就是ts对js的补充:类型检测和更丰富的语法。TypeScript最终会被编译为javaScript.

静态类型

这里有两个概念部分:强类型和弱类型(类型安全);静态类型和动态类型(类型检查)。

强类型定义的语言 语言层面就限制了函数的实参类型就必须与形参类型相同。不可以隐式转换,一旦定义了某种类型,如果不经过强制转换,就永远是该数据类型。Java、.net 、Python、C++。

若类型定义的语言可以隐式转换,JavaScript就是一门弱类型语言。 let a="3"; a=44; 这在js中是正常的,首先定义字符串,再赋值类型为number。 强类型语言中就会报错。

当然还没有统一的界定,大家遵守的约定就是强类型有更强的约束,弱类型几乎没有约束。强/弱类型的界定本身就留有争议。

简单说,在编译时就报错的,是静态类型。在运行时报错的,是动态类型。我们的TypeScript是静态类型语言。而JavaScript是动态类型语言。这也是之前JavaScript的优点灵活性。

1.1产生

TypeScript怎么产生的,是因为在一些大型的多人协作的项目中,JavaScript的灵活性就有它一体两面的特性,从优点就变成了缺点。因为是弱类型,且是动态类型,所以在多人编译时就无法发现编译时的bug,在代码运行时会发现很多低级编译bug。所以就有了在编译阶段的类型检测。这就是现在TypeScript受欢迎的原因。 它能节省时间,以及规避低级bug。

TypeScript在编译阶段,通过编译器的类型检查机制,帮助开发者在编译阶段,尽可能的识别问题。开发者给JavaScript代码的对象、变量、函数等,增加类型标注,说明代码的“目的”,这对大型团队,意义尤其重大。因为类型标注,本身就是文档和开发标准。

1.2特点

当我们了解了TypeScript的产生原因和与js的区别后,就会有这个印象:类型检测和新特性。 是ts与js的不同点,怀着问题寻找答案,记忆更清楚。

TS的特点:

支持最新的JavaScript的新特性(包括ES6以及最新的还未放入ECMAScript标准里的某些特性)

支持代码静态检查

支持C、C++、java、go 等后端语言中的特性(枚举、泛型、类型转换、命名空间、声明文件、类、接口等)

缺点:语言本身多了很多概念 枚举 泛型、接口等

项目小的 ,会增加成本 要编写类型声明 前期增加时间成本

1.3快速上手

安装

//使用yarn
//初始化一下
yarn init  --yes 
​
// 安装ts 本地项目安装  全局:yarn global add typescript
yarn add typescript --dev 
​
//查看是否安装成功
tsc -v
​
//生产tsconfig.json配置文件
tsc --init 
​
//运行
yarn tsc index.ts 
​
//-----------------------------------------------------------------
//使用npm
//初始化一下
npm init -y
​
//使用npm 安装使用typeScript  全局安装
npm i -g typescript@3.1.3 
​
//解决模块的声明文件
npm install @types/node --dev-save
​
//创建一个 tsconfig.json 
tsc --init
//运行
ts-node index.ts

2.TypeScript 类型系统

2.1 tsconfig.json 配置文件

{
  "compilerOptions": {
​
    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录 
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件作为单独的模块 (与 'ts.transpileModule' 类似).
​
    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'
​
    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)
​
    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。
​
    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性
​
    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}
​
//文章链接地址:https://jkchao.github.io/typescript-book-chinese/project/compilationContext.html#tsconfig-json

有常用得到

{
 
    /* 
     * target
     * module
     * lib
     * sourceMap
     * outDir
     * rootDir 
     * strict
     * strictNullChecks
     */
}

2.1 typescript 的类型

八种原始类型

在typescript时同样可以

let str:string = "string";
let num:number = 10; //NaN Infinity 
let bool: boolean= true ;
 //严格模式下 sring number boolean 都可以为空 非严格模式下不可以会报错     
    // tsconfig.json "strict":true  "strictNullChecks": true, 启用严格的 null 检查
let u: undefined = undefined;
let n: null = null;
let obj: object = {x: 1};
let big: bigint = 100n;
// number 和big 都表示数字 但是两个不兼容
let sym: symbol = Symbol("me"); 
​
​
​
​
//这样如果看不清楚可以这样写 
let num1: number;
let str1: string;
let bool1: boolean;
​
//上面是声明 声明时确定类型注解  下面是赋值
num1 = 123;
num1 = 123.456;
num1 = '123'; // Error
​
str1 = '123';
str1 = 123; // Error
​
bool1 = true;
bool1 = false;
bool1 = 'false'; // Error

数组

定义的两种方式:

let list: number[] = [1, 2, 3]; //1.元素类型后面接[]
let list: Array<number> = [1, 2, 3]; //2.使用数组泛型 Array<元素类型>
​
function sum (...args:number[]){//给参数定义数组类型
    return args.reduce((prev,current)=>prev +current,0)
}

定义联合类型数组:

let arr:(number | string)[]; // 表示定义了一个名称叫做arr的数组,  // 这个数组中将来既可以存储数值类型的数据, 也可以存储字符串类型的数据 arr3 = [1, 'b', 2, 'c'];

元组 Tuple

明确元素数量和每一个元素的类型

let tuple:[number,string]=[18,"jack"]
​
// 访问元组中的元素,使用下标方式 数组解构
let age=tuple[0]
let [age,name]=tulpe

当访问一个越界的元素,会使用联合类型替代:

tuple[3] = 'world'; // OK, 字符串可以赋值给(string | number)类型
​
console.log(tuple[5].toString()); // OK, 'string' 和 'number' 都有 toString
​
tuple[6] = true; // Error, 布尔不是(string | number)类型

枚举 enum

有确定状态的多选择。给一组数值起名字,存在固定的值。 你可以由枚举的值得到它的名字.

不指定值时,从0累加;指定第一个 后面的在第一个基础上累加

enum PostStatus{
    Draft = 1,
    Unpublished = 3,
    Published = 5,
}
​
let PostStatusName: string = PostStatus[3]; 
console.log(PostStatusName) //Unpublished 它的值是3

Any

针对编程时类型不明确的变量使用的一种数据类型,ts的类型检查器对any类型的关闭检查直接通过编译阶段。

在any上访问任何属性都是允许的,也允许调用任何方法

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // okay, definitely a boolean
​
notSure.ifItExists(); // okay, ifItExists might exist at runtime
notSure.toFixed(); // okay, toFixed exists (but the compiler doesn't check)
​
let list: any[] = [1, true, "free"];
​
list[1] = 100;

Void

void表示没有任何类型,和其他类型是平等关系,不能直接赋值,当一个函数没有返回值时,你通常会见到其返回值类型是 void

function warnUser(): void {
    console.log("This is my warning message");
}

声明一个void类型的变量没有什么大用,因为你只能为它赋予undefinednull

let unusable: void = undefined;

Null 和 Undefined

undefinednull两者各自有自己的类型分别叫做undefinednull。 默认情况下 nullundefined 是所有类型的子类型。 就是说你可以把 nullundefined 赋值给其他类型。如果你在tsconfig.json指定了"strictNullChecks":truenullundefined 只能赋值给 void 和它们各自的类型。

// null和undefined赋值给string
let str:string = "111";
str = null
str= undefined
​
// null和undefined赋值给number
let num:number = 111;
num = null
num= undefined

Never

never类型表示的是那些永不存在的值的类型

never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never

一些返回never类型的函数:

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message);
}
​
// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}
​
// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) {
    }
}

object和Object和{}

object表示非原始类型,也就是除numberstringbooleansymbolnullundefined之外的类型。

大Object 代表所有拥有 toString、hasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object。

{}可以和Object相互转换

例如:

const foo:object =function(){}//1.指除了原始类型以外的其他类型
​
const  obj:{foo:number,bar:string}={foo:112,bar:'jack'} //字面量创建对象
​

object

declare function create(o: object | null): void;
​
create({ prop: 0 }); // OK
create(null); // OK
​
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

Object

let upperCaseObject: Object;
upperCaseObject = 1; // ok
upperCaseObject = 'a'; // ok
upperCaseObject = true; // ok
upperCaseObject = null; // ts(2322)
upperCaseObject = undefined; // ts(2322)
upperCaseObject = {}; // ok
​

{}

let ObjectLiteral: {};
ObjectLiteral = 1; // ok
ObjectLiteral = 'a'; // ok
ObjectLiteral = true; // ok
ObjectLiteral = null; // ts(2322)
ObjectLiteral = undefined; // ts(2322)
ObjectLiteral = {}; // ok
type isLiteralCaseObjectExtendsUpperCaseObject = {} extends Object ? true : false; // true
type isUpperCaseObjectExtendsLiteralCaseObject = Object extends {} ? true : false; // true
upperCaseObject = ObjectLiteral;
ObjectLiteral = upperCaseObject;

类型断言

类型断言可以用来手动指定一个值的类型,即允许变量从一种类型更改为另一种类型

类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。

<类型>值 or 值 as 类型
const foo = {};
foo.bar = 123; // Error: 'bar' 属性不存在于 ‘{}’
foo.bas = 'hello'; // Error: 'bas' 属性不存在于 '{}'
​
//这是接口
interface Foo {
  bar: number;
  bas: string;
}
​
const foo = {} as Foo;
foo.bar = 123;
foo.bas = 'hello';

<类型>值

let foo: any; let bar = <string>foo; // 现在 bar 的类型是 'string'

当你在 JSX 中使用 <foo> 的断言语法时,这会与 JSX 的语法存在歧义: 所以使用 值 as 类型

let someValue: any = "this is a string";
​
let strLength: number = (someValue as string).length;

接口 interface

约定对象的具体成员和类型。TypeScript 中的接口是一个非常灵活的概念,除了可用于[对类的一部分行为进行抽象]以外,也常用于对「对象的形状(Shape)」进行描述。 为代码的结构做类型约束 不会在编译后的代码中有体现.

ps:类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以

interface Post {
    title:string;
    content:number;
}
​
function pringPost (post:Post){
    console.log(post.title)
    console.log(post.content)
}
​
printPost({
    title:"my print",
    content: 19
})

可选属性、只读属性:

interface Post {
    title:string;
    content:number;
    subTitle?:string;//可选
    readonly sum:string;//只读
​
}
​
function pringPost (post:Post){
    console.log(post.title)
    console.log(post.content)
}
​
printPost({
    title:"my print",
    content: 19,
    sum:"omg"
})

只读属性用于限制只能在对象刚刚创建的时候修改其值。此外 TypeScript 还提供了 ReadonlyArray<T> 类型,它与 Array<T> 相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。

let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
​
​
//可以用类型断言重写
a = ro as number[];
​

动态属性

希望一个接口中除了包含必选和可选属性之外,还允许有其他的任意属性 使用 [索引签名] 的格式

interface Cache{
    [prop:string]: string
}
const cache:Cache = {}
cache.foo='value1'
cache.bar="value2"
​

ps:需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

interface Person {
    name: string;
    age?: number;
    [propName: string]: string;
}
​
let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
​
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
//   Index signatures are incompatible.
//     Type 'string | number' is not assignable to type 'string'.
//       Type 'number' is not assignable to type 'string'.

任意属性的值允许是 string,但是可选属性 age 的值却是 numbernumber 不是 string 的子属性,所以报错了。

一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型

interface Person {
    name: string;
    age?: number; // 这里真实的类型应该为:number | undefined
    [propName: string]: string | number | undefined; //确定属性和可选属性的类型都必须是它的类型的子集
}
​
let tom: Person = {
    name: 'Tom',
    age: 25,
    gender: 'male'
};
​

接口描述函数类型

为了使用接口表示函数类型,我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。

interface SearchFunc {
  (source: string, subString: string): boolean;
}

函数类型的类型检查来说,函数的参数名不需要与接口里定义的名字相匹配 例子:

let mySearch: SearchFunc;
mySearch = function(src: string, sub: string): boolean {
  let result = src.search(sub);
  return result > -1;
}

可索引的类型

描述通过索引得到的类型。可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型 。

interface StringArray {
  [index: number]: string;
}
​
let myArray: StringArray;
myArray = ["Bob", "Fred"];
​
let myStr: string = myArray[0];

TypeScript支持两种索引签名:字符串和数字。数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致。

索引签名的名称(如:{ [index: string]: { message: string } } 里的 index )除了可读性外,并没有任何意义。

描述一类具体事物的抽象特征

TypeScript增强了class 的语法

类属性在定义之前,必须要在类当中先声明,声明或者初始化必须有一个。

class Person{
    name:string
    age:number
    constructor(name:string,age:number){
            this.name = name;
            this.age = age ;
    }
    sayHi(msg:string):void {
        console.log(I am${this.name},${mssg})
    }
}

类的访问修饰符 private 只能在内部访问

class Person{
    public name:string //公有 加不加都无所谓
    private age:number //内部访问
    protected gender:boolean //外部访问不到,只允许在子类访问
    constructor(name:string,age:number){
            this.name = name;
            this.age = age ;
    }
    sayHi(msg:string):void {
        console.log(I am${this.name},${mssg})
            console.log(this.age )//正常
    }
}
​
​
class Student extends Person {
    constructor(name:string,age:number){
        super(name,age) //继承父类
        console.log(this.gender)//不报错
    }
}
​
const tom = new Person(‘tom’,18)
console.log(this.age)//报错 外部不能访问
console.log(this.gender)//报错 外部不能访问
​
​
​
​
​
//------------------------------------------------------
​
class Student extends Person {
  private   constructor(name:string,age:number){
        super(name,age) //继承父类
        console.log(this.gender)//不报错
    }
     static creat(name:string,age:number){
        return new Student(name,age)
    }
}
//吧构造函数变成内部 外部就不能使用  可以添加静态方法 返回构造函数的调用 
const jack = new Student()//报错
const jack = Student.creat("jack",18)//可以

类的只读属性

class Person{
    public name:string //公有 加不加都无所谓
    private readonly age:number //内部访问 只读属性跟在访问修饰符的后面
    protected gender:boolean //外部访问不到,只允许在子类访问
    constructor(name:string,age:number){
            this.name = name;
            this.age = age ;
    }
    sayHi(msg:string):void {
        console.log(I am${this.name},${mssg})
            console.log(this.age )//正常
    }
}
​

类和接口

用接口对类抽象化,一个接口只抽象一个类型。简单具体

interface Eat{
    eat(food:string):void
}
​
interface Run{
    run(distance:number):void
}
​
class Person implements Eat,Run{
    eat(food:string):void{
        console.log("优雅的进餐"+food)
    }
    run(distance:number):void{
        console.log("直立行走"+distance)
    }
}
​
​
class Animal implements Eat,Run{
    eat(food:string):void{
        console.log("呼噜噜的吃"+food)
    }
    run(distance:number):void{
        console.log("爬行"+distance)
    }
}

类 抽象类

abstract 对大范围的 做抽象 只能被继承 不能new实例化对象

abstract clas Animal {
    eat(food:string):void{
        console.log("呼噜噜的吃"+food)
    }
abstract run(distance:number):void //抽象方法
}
​
class Dog extends Animal {
    run(distance:number):void{
        console.log("四脚爬行"+distance)    
  }
}
const d=new Dog()
d.eat("西蓝花")
d.run(1110)

泛型 Generics

指定义接口 或者函数时,没有指定具体类型,等到调用的时候再具体指定

function createdArray<T>(length:number,value:T):T[]{
    const arr = Array<T>(length).file(value)
    return arr
}
const res = creatArray<string>(3,"foo")
​

函数

函数声明:

function sum(x: number, y: number): number {
    return x + y;
}
//(x: number, y?: number) 可选参数
//(x: number, y: number=1) 默认值参数 都要放最后

函数表达式:

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};

接口定义函数类型

interface SearchFunc{
  (source: string, subString: string): boolean;
}
​

采用函数表达式接口定义函数的方式时,对等号左侧进行类型限制,可以保证以后对函数名赋值时保证参数个数、参数类型、返回值类型不变

可选参数

function buildName(firstName: string, lastName?: string) {
    if (lastName) {
        return firstName + ' ' + lastName;
    } else {
        return firstName;
    }
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
​

参数默认值

function buildName(firstName: string, lastName: string = 'Cat') {
    return firstName + ' ' + lastName;
}
let tomcat = buildName('Tom', 'Cat');
let tom = buildName('Tom');
​

剩余参数

function push(array: any[], ...items: any[]) {
    items.forEach(function(item) {
        array.push(item);
    });
}
let a = [];
push(a, 1, 2, 3);
​

函数重载

函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。 要解决前面遇到的问题,方法就是为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据这个列表去处理函数的调用。

由于 JavaScript 是一个动态语言,我们通常会使用不同类型的参数来调用同一个函数,该函数会根据不同的参数而返回不同的类型的调用结果,我们为 add 函数提供了多个函数类型定义,从而实现函数的重载。

type Types = number | string
function add(a:number,b:number):number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a:Types, b:Types) {
  if (typeof a === 'string' || typeof b === 'string') {
    return a.toString() + b.toString();
  }
  return a + b;
}
const result = add('Semlinker', ' Kakuqo');
result.split(' ');
​
​

类型推断

多情况下,TypeScript 会根据上下文环境自动推断出变量的类型,无须我们再写明类型注解。

{
  let str: string = 'this is string';
  let num: number = 1;
  let bool: boolean = true;
}
{
  const str: string = 'this is string';
  const num: number = 1;
  const bool: boolean = true;
}
​
//-----------------------------------------------
​
{
  let str = 'this is string'; // 等价
  let num = 1; // 等价
  let bool = true; // 等价
}
{
  const str = 'this is string'; // 不等价
  const num = 1; // 不等价
  const bool = true; // 不等价
}
//在 TypeScript 中,具有初始化值的变量、有默认值的函数参数、函数返回的类型都可以根据上下文推断出来。比如我们能根据 return 语句推断函数返回的类型,如下代码所示:
{
  /** 根据参数的类型,推断出返回值的类型也是 number */
  function add1(a: number, b: number) {
    return a + b;
  }
  const x1= add1(1, 1); // 推断出 x1 的类型也是 number
  
  /** 推断参数 b 的类型是数字或者 undefined,返回值的类型也是数字 */
  function add2(a: number, b = 1) {
    return a + b;
  }
  const x2 = add2(1);
  const x3 = add2(1, '1'); // ts(2345) Argument of type "1" is not assignable to parameter of type 'number | undefined
}
//如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

一些关于TypeScript的简单的知识介绍到这里 更多文章可关注公众号:程序员番外

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值