typescript基础篇

1 篇文章 0 订阅

多态、继承

父类定义一个方法不去实现,让继承它的子类去实现,每一个子类有不同的表现。

class  Animal {
    name:string
    constructor(name:string){
        this.name = name
    }
    getVariety(){} //父类定义了getVariety方法不去实现
}
class Cat extends Animal{
    constructor(name:string){
        super(name)
    }
    getVariety():string{
        return `${this.name}是只猫!` //子类Cat对getVariety的实现
    }
}
class Dog extends Animal{
    constructor(name:string){
        super(name)
    }
    getVariety():string{
        return `${this.name}是只狗!` //子类Dog对getVariety的实现
    }
}
//这种子类继承父类后重写了getVariety方法,每个子类有自己不同的实现,叫多态

抽象类和抽象方法

TS中的抽象类是提供其他类继承的基类,不能直接实例化。用abstract关键字定义抽象类和抽象方法,抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
抽象类和抽象方法用来定义标准,来约束子类必须实现指定方法

abstract class Animal {
    name:string
    constructor(name:string){
        this.name = name
    }
    abstract getVariety():any
    run():void{} //没有abstract标识的方法在子类中可以不实现
}
// const animal = new Animal()  //报错!!抽象类无法实例化
class Dog extends Animal{
    constructor(name:string){
        super(name)
    }
    getVariety(){  //实现抽象方法中的getVariety方法,如果没有则报错!如果要标明返回值类型,必须和抽象方法中一致
        return `${this.name}是只狗!`
    }
}
const dog = new Dog('旺财')
console.log(dog.getVariety()) //旺财是只狗!


 abstract class Animal1 {
   name:string;
   constructor(name:string) {
     this.name=name;
   }
    run():void{};
    seep():void{
      alert(`${this.name}睡觉睡的很香`)
    }
   abstract eat():any;
 }
 class Cat extends Animal1 {
   constructor(name:string) {
     super(name)
   }
   eat():void{
     alert(`${this.name} 吃小鱼哦哦哦`)
   }
   run():void{
     console.log(`${this.name} 跑的飞快哦`)
   }
 }
 let c=new Cat("大花猫")
 c.eat()
 c.run()
 console.log('>>>>>>>>>>>>',c.name);
 c.seep()

type

类型别名

使用 type 创建为类型创建别名,常用于联合类型

type Name = string;  //为string创建别名Name
type NameResolver = () => string;   //TS中=>左边是参数,右边是返回值,所以类型是一个 返回字符串的函数,且该函数没有参数
type NameOrResolver = Name | NameResolver;  //  为联合类型起别名
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

interface一般表示object,class,function类型。type可以复杂类型(例如联合类型等)

type Key = string | number;
type JSXElementConstructor<P> =((props: P) => ReactElement | null) | (new (props: P) => Component<P, any>);

定义方法类型

//使用类似定义箭头函数的语法来表示函数类型的参数和返回值类型,此时=> 类型仅仅用来定义一个函数类型而不用实现这个函数。
//需要注意的是,这里的=>与 ES6 中箭头函数的=>有所不同。TypeScript 函数类型中的=>用来表示函数的定义,
//其左侧是函数的参数类型,右侧是函数的返回值类型;而 ES6 中的=>是函数的实现。
type say = (age: number) => string;
const says: say = function (age: number): string {
  return String(age);
};
console.log(says(222));

//interface say1 {
//  (age: number): string;
//}
//const says1: say1 = function (age: number): string {
//  return String(age);
//};
//console.log(says1(222333));

字符串字面量类型

用来约束取值只能是某几个字符串中的一个。
注意:类型别名与字符串字面量类型都是使用 type 进行定义

 //使用 type 定义一个字符串字面量类型 EventNames,它只能取三种字符串中的一种
type EventNames = 'click' | 'scroll' | 'mousemove';   
function handleEvent(ele: Element, event: EventNames) {
    // do something
}
handleEvent(document.getElementById('hello'), 'scroll');  // 没问题
handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'

实现

类实现interface或type

type Point = {
  x: number;
  aaa: (str: string) => string
}
class P2 implements Point {
  x: number
  constructor(x: number) {
    this.x = x
  }
  aaa(str: string): string {
    return str
  }
}

继承

interface 和 type 两者都可以继承,语法不同

interface IPoint {
	x: number;
}
type Point = {
	x: number;
}

// 接口继承接口
interface Point2 extends IPoint {
	y: number;
}
// 接口继承type
interface Point2 extends Point {
	y: number;
}
//type继承接口
type Point2 = IPoint & {y: number;}
//type继承type
type Point2 = Point & {y: number}

//联合类型的type不能被接口继承 或实现
// An interface can only extend an object type or intersection of object types with statically known members
//接口只能扩展对象类型或对象类型与静态已知成员的交集
type Points = {x: number;} | {y: number;}  

//可以接口继承和实现
type Points = {x: number;} & {y: number;}  

extends在type中的使用

用来判断一个类型是不是可以分配给另一个类型,这在写高级类型的时候非常有用
需要理解的是,这里A extends B,是指类型A可以分配给类型B,而不是说类型A是类型B的子集

type Human = {
   name: string;
 }
 type Duck = {
   name: string;
 }
 type Bool = Duck extends Human ? 'yes' : 'no'; // Bool => 'yes'

//因为 Duck 没有类型为string的age属性,类型Duck不满足类型Human的类型约束
type Human1 = {
  name: string;
  age: string;
}
type Duck1 = {
  name: string;

}
type Bool = Duck1 extends Human1 ? 'yes' : 'no'; // Bool => 'no'

继续看示例

  type A1 = 'x' extends 'x' ? string : number; // string
  type A2 = 'x' | 'y' extends 'x' ? string : number; // number
  
  type P<T> = T extends 'x' ? string : number;
  type A3 = P<'x' | 'y'> // ?

在这里插入图片描述
在这里插入图片描述

接口

属性接口

 interface Person {
   name:string;
   age:number;
   sex?:string;
 }
 function getName(info:Person){
   console.log('>>>>>>>>>>>>',info);
 }
 getName({
   name:"王小二",
   age:12
 })

 interface Person {
   name:string;
   age:number;
   sex?:string;
 }
 function getName(info:Person){
   console.log('>>>>>>>>>>>>',info);
 }
 let obj={
   name:"王小二",
   age:12,
   address:'芜湖市1'
 }
 getName(obj)

函数接口

interface Person {
   (name:string,age:number):void
 }
 let myGetName:Person= function(name:string,age:number):void{
   console.log('>>>>>>>>>>>>',name,age);
 }
 myGetName( "王大额热二",102)

类接口、对象接口

 interface Person{
   name:string;
   eat():void;  
  
 }
 class Mans implements Person {
   name:string;
   constructor(name:string) {
     this.name=name
   }
   eat():void{
     console.log('>>>>>>>>>>>>',`${this.name}吃自己`);
   }
 }
 let m =new Mans("达到调")
 m.eat()
interface aaa {
  name: string;
  hahaha(name: string): string;
  add: (a: number, b: number) => number;
}

class Aa1 implements aaa {
  constructor(name: string) {
    this.name = name
  }
  name: string;
  hahaha(name: string): string {
    return name + 1;
  }
   add = function (a: number, b: number) {
    return a + b
  }
}

const Bbb: aaa = {
  name!: '111',
  hahaha: function (name: string): string {
    return name + 1
  },
  add: function (a: number, b: number): number {
    return a + b
  }
}
const Bbb1 = <aaa>{
  name: '111',
  hahaha: function (name: string): string {
    return name + 1
  }
}

可索引接口

interface arr {
  [age:number]:string
}
let arr1:arr=["1","2","3","4"]
let arr2: arr= {0: 'a',1: 'b',2: 'c'};
let arr3:Array<arr>=["1","2","3","4"]
let arr4:Array<arr>=[["1","2","3","4"]]
console.log('>>>>>>>>>>>>',arr1,arr2,arr3,arr4);

interface ArrayKeyAny1 {
  [key: string]: number; // 根据需要,可把key 和 value 类型设置成指定的类型
}
const array1: ArrayKeyAny1 = { "123": 123 };
console.log("AppApp", array1[0]);

当接口指定了可索引类型后,在定义其他属性时,也必须满足可索引类型

interface qq {
  [propName: string]: number;
  // name: string; //这里会报错,因为不满足索引类型指定的返回值类型
}
let a: qq = {
  1: 1, aa: 2
}
console.log(a[1], a.aa)
interface IPerson {
  name: string
  age: number
  family?: string
  [propName: string]: any // 一个 interface 中任意属性只能有一个,[propName: string]: string | number // 一般设置 any,因为其他类型必需是任意类型的子集
}

接口继承

interface Shape {
  color: string
}
interface Triangle extends Shape {
  sideNum: number
}
const triangle = <Triangle>{ color: 'blue', sideNum: 3 };
const triangle1: Triangle = { color: 'blue', sideNum: 3 };
// const triangle2: Triangle; 编译失败
// triangle2.color='dsad' 编译失败
const triangle2 = <Triangle>{};
triangle.color = "blue";
triangle.sideNum = 3;

接口合并

interface Obj {
  item1: string;
  item3: boolean;
}
interface Obj {
  item1: string;  //后续属性声明必须属于同一类型
  item2: number;
}
const a12: Obj = {
  item1: "123",
  item2: 1234,
  item3:true
};

接口扩展

 interface Person{
   name:string;
   eat():void;  
 }

 class Food{
   foods:string;
   constructor(foods:string){
     this.foods=foods
   }
 }
 class Mans extends Food implements Person {
   name:string;
   constructor(name:string,foods:string) {
     super(foods)
     this.name=name
   }
   eat():void{
     console.log('>>>>>>>>>>>>',`${this.name}吃自己和${this.foods}`);
   }
 }
 let m =new Mans("小月月","葡萄")
 m.eat()

泛型

泛型接口

interface user1 {
  <T>(name: T): T
}
let myName1: user1 = function <T>(name: T): T {
  alert(name);
  return name
}
myName1<string>("你好啊大萨达")

interface user2<T> {
  (name: T): T
}

let myName2: user2<string> = function <T>(name: T): T { //把泛型参数提前到接口名
  alert(name);
  return name
}
myName2("丰东股份的喝")

泛型函数

function getName<T>(name:T):T{
 return name
}
alert(getName<number>(23))

function swap<T, U>(tuple: [T, U]): [U, T] {
  return [tuple[1], tuple[0]];
}
swap<number, string>([7, 'seven']); // ['seven', 7]

泛型类

class User<T>{
 name:T;
 constructor(name:T){
   this.name=name
 };
 add():T{
   alert(this.name)
   return this.name
 }
}
let us = new User<number>(0)
us.add()
class Animal<T> {
  //此处的泛型为限制构造函数参数类型以及类成员变量的类型
  name: T;
  constructor(name: T) {
    this.name = name;
  }
  action<T>(say: T): T {
    //此处为限制类中成员方法action, 若此处不写<T>则使用类声明的<T>
    console.log("action", say);
    return say;
  }
}
const dog = new Animal<string>("dog"); //此处的striing为限制构造函数参数类型以及类 成员变量的类型
dog.action(123); //此处为限制类中成员方法 action

泛型约束

当对泛型进行约束时,即只允许这个函数传入那些包含 length 属性的变量

interface Lengthwise {      //定义一个接口
    length: number;
    str:string
}
//  使用了 extends 约束了泛型 T 必须符合接口 Lengthwise 的形状,即必须包含 length 属性,不包含此属性就会报错
function loggingIdentity<T extends Lengthwise>(arg: T): T {  //泛型T继承接口
    console.log(arg.length);
    return arg;
}
const adsad={
  length:5,
  str:'dasd'
}
loggingIdentity<Lengthwise>(adsad)

甚至有多个多个类型参数时,类型参数之间也可以约束

//  使用两个类型参数,其中要求 T 继承 U,这样就保证了 U 上不会出现 T 中不存在的字段。
function copyFields<T extends U, U>(target: T, source: U): T {
    for (let id in source) {
        target[id] = (<T>source)[id];
    }
    return target;
}
let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });

类类型作为泛型

class User{
  name:string | undefined;
  age:number | undefined;
  status: string | undefined;
  constructor(info:{
    name:string | undefined,
    age:number | undefined,
    status?: string | undefined,
  }){
    this.name=info.name
    this.age=info.age
    this.status=info.status
  }
}
class MysqlEditUser<T> {
  constructor() {
  }
  add(info:T):boolean{
    console.log(info)
    return true
  }
}
let u = new  User({
  name:'大牛',
  age:22,
  status:'4545'
})
let mysql=new MysqlEditUser<User>()
mysql.add(u)

泛型参数的默认类型

//   为泛型T指定默认泛型参数为string
function createArray<T = string>(length: number, value: T): Array<T> {
   let result: T[] = [];
   for (let i = 0; i < length; i++) {
       result[i] = value;
   }
   return result;
}

声明合并

函数的合并(重载)

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
    if (typeof x === 'number') {
        return Number(x.toString().split('').reverse().join(''));
    } else if (typeof x === 'string') {
        return x.split('').reverse().join('');
    }
}

接口的合并

接口中的属性在合并时会简单的合并到一个接口中;方法合并与函数的合并(函数重载)一样

interface Alarm {
    price: number;
    alert(s: string): string;
}
interface Alarm {
    price: number;  //两个相同属性名合并,类型必须是唯一的(类型必须相同)
    weight: number;
    alert(s: string, n: number): string;
}
//  相当于:是叠加,不是覆盖
interface Alarm {
    price: number;
    weight: number;
    alert(s: string): string;
    alert(s: string, n: number): string;
}

in操作符

in的定义是:用于遍历枚举类型。
注意:in只能遍历枚举类型,不能遍历对象(keyof遍历的是对象(类型))。

type Keys = 'a' | 'b';
type Obj = {
	[p in Keys]: any
} // {a: any, b: any}

keyof

首先来看keyof的定义:keyof操作符可以用来枚举出一个对象中的所有key值。
通俗来说,keyof可以取出一个对象中的所有由key值组成的联合类型。

实际效果类似于Object.keys()

//keyof P 返回的是P的属性名组成的联合类型
interface Point {
	name: string;
	age: number;
	gender: string;
}
type Key = keyof Point // 返回的是'name' | 'age' | 'gender'

解决对象使用 [变量] 的调用方式

interface Obj {
  item1: string; 
  item2: number;
}
const a12: any = {
  item1: "123",
  item2: 1234,
};
//let key2: string = "item1"  // 没有给key 的类型进行定义,故key可以是any 类型
//console.log( a12[key2]);    // 报错,因为 key 可以是任何值,故会报错。
const key11: keyof Obj = "item1"; //把key 设置为 Obj 的key 的联合类型,即: "item1" | "item2"
console.log("a12[key1]", a12[key11]); //正确,不会报错

type dsaa = {
  name: string
  sex: string
  age: number
}
let ccda: keyof dsaa = 'name'
console.log("ccda", ccda)//ccda name

注意 keyof Obj 中的 Obj 是一个接口或者自定义type,若Obj是对象则需要使用keyof typeof Obj

keyof在函数中使用

// 若声明了对象
const a = {
  item1: "xxx1",
  item2: "xxx2",
};
function getValue(arg: keyof typeof a) {
  return a[arg];
}
console.log(getValue("item1"));

// 若声明了接口
interface A {
  item1: string;
  item2: string;
}
// 则 函数的参数类型可以这样写
function getValue1(arg: keyof A) {
  return a[arg];
}
console.log(getValue1("item2"));

返回T中K存在的变量值

function pluck<T, K extends keyof T>(o: T, names: Array<K>): T[K][] {
  return names.map(n => o[n]);
}
interface T {
  name: string
  age: number
}
type K = 'name' | "age"
let a1 = {
  name: "dasf",
  age: 2
}
let a2: Array<K> = ['name']
console.log("pluck(a1, ['name'])", pluck<T, K>(a1, a2))
//输出  pluck(a1, ['name']) ['dasf']

泛型工具

keyof 产生联合类型, in 则可以遍历枚举类型

interface Person {
  name: string;
  age: number;
}
type K1 = keyof Person; // "name" | "age"
//可以看到keyof Person返回的枚举类型。 "name" | "age"
let a1: { [key in K1]: string }

type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 
let a2: { [key in K2]: string }

type K3 = keyof { [x: string]: Person };  // string | number
let a3: { [key in K3]: string }

Exclude

Exclude是TS中的一个高级类型,其作用是从第一个联合类型参数中,将第二个联合类型中出现的联合项全部排除,只留下没有出现过的参数。

type Exclude<T, U> = T extends U ? never : T;
type A = Exclude<'key1' | 'key2', 'key2'> 
//这个定义就利用了条件类型中的分配原则,来尝试将实例拆开看看发生了什么:
type A = `Exclude<'key1' | 'key2', 'key2'>`
 
// 等价于
 
type A = `Exclude<'key1', 'key2'>` | `Exclude<'key2', 'key2'>`
 
// =>
 
type A = ('key1' extends 'key2' ? never : 'key1') | ('key'2 extends 'key2' ? never : 'key2')
 
// =>
 
// never是所有类型的子类型
type A = 'key1' | never

Partial

Partial 作用是将传入的属性变为可选项.

interface T1 {
  name: string
  age: number
}
//keyof T 拿到 T 所有属性名, 然后 in 进行遍历, 将值赋给 P, 最后 T[P] 取得相应属性的值
type Partial<T> = {
  [P in keyof T]?: T[P];
};
let aaa: Partial<T1> = {
  name: "lps3",
  age: 122,
}
console.log("aaa", aaa)
//let aaa: {
//  [p in keyof Partial<T1>]: T1[p]
//} = {
//  name: "lps3",
//  age: 122,
//}

Required

Required 的作用是将传入的属性变为必选项, 源码如下

interface T2 {
  name?: string
  age: number
}
//我们发现一个有意思的用法 -?, 这里很好理解就是将可选项代表的 ? 去掉, 从而让这个类型变成必选项,
//与之对应的还有个+? , 这个含义自然与-?之前相反, 它是用来把属性变成可选项的
type Required<T> = { 
  [P in keyof T]-?: T[P]
};

let bbb: Required<T2> = {
  name: "lps3",
  age: 122,
}
console.log("bbb", bbb)
[P in keyof T]?: T[P]:可选
[P in keyof T]+?: T[P]:可选,等价于?
[P in keyof T]-?: T[P]:必选
[P in keyof T]: T[P]:可选的依然可选,必选的依然必选

Pick

从T中提取出T与K属性值相同的属性

//假设Person类型中有name、age、sex属性,当我们想生成一个新的类型只支持name、age时,则可使用Pick
type Pick<T, K extends keyof T> = { 
  [P in K]: T[P]
};
interface Person {
  name: string,
  age: number,
  sex: string,
}

let person: Pick<Person, 'name' | 'age'> = {
  name: '小王',
  age: 21,
}

声明文件

声明文件以 .d.ts 为后缀

装饰器

/**
 * 装饰器
 */
//装饰器的执行顺序   说的是不是有问题?,属性、方法、参数装饰器的执行顺序是按照其在类内部的位置,依次向下执行,同一个属性、方法、参数有多个装饰器的时候,
//其上的装饰器从后往前执行,类装饰器总是在最后;当类有多个装饰器的时候也是从下往上(后前)执行,这样总结会不会更准确些?
//而且当一个方法既有方法装饰器也有参数装饰器,是先执行参数装饰器(多个的时候从后往前执行),在执行方法装饰器(多个的时候从后往前执行)

/**
 * @param target 类装饰器
 */
function logClass1(target:any){
  console.log('>>>>>>>>>>>>logClass1',target);
}
function logClass2(params:any){
  return function(target:any){
    console.log('>>>>>>>>>>>>logClass2<<<<<<<<<<<<<<<<<<<<<<<<<');
    // console.log('>>>>>>>>>>>>attr',target);
    // console.log('>>>>>>>>>>>>params',params);
  }
}
/**
 * @param target 属性装饰器
 */
function logAttr(params:any){
  return function (target:any,attr:any){
    console.log('>>>>>>>>>>>>logAttr<<<<<<<<<<<<<<<<<<<<<<<<<');
    // console.log('>>>>>>>>>>>>params',params);
    // console.log('>>>>>>>>>>>>target',target);
    // console.log('>>>>>>>>>>>>attr',attr);
  }
}
/**
 * @param target 函数(方法)装饰器
 */
 function logMethod(params:any){
  return function (target:any,methodName:any,methodDesc:any){
    console.log('>>>>>>>>>>>>>>>>>logMethod<<<<<<<<<<<<<<<<<<<<<<<<<<<');
    // console.log('>>>>>>>>>>>>params',params);
    // console.log('>>>>>>>>>>>>target',target);
    // console.log('>>>>>>>>>>>>methodName',methodName);
    // console.log('>>>>>>>>>>>>methodDesc',methodDesc);
  }
}
/**
 * 函数(方法)参数装饰器
 */
function logCanshu1(params:any){
  return function  (target:any,methodName:any,index:any) {
    console.log('>>>>>>>>>>>>>>>>>logCanshu1<<<<<<<<<<<<<<<<<<<<<<<<<<<');
    // console.log('>>>>>>>>>>>>params',params);
    // console.log('>>>>>>>>>>>>target',target);
    // console.log('>>>>>>>>>>>>methodName',methodName);
    // console.log('>>>>>>>>>>>>index',index);
  }
}
function logCanshu2(params:any){
  return function  (target:any,methodName:any,index:any) {
    console.log('>>>>>>>>>>>>>>>>>logCanshu2<<<<<<<<<<<<<<<<<<<<<<<<<<<');
    // console.log('>>>>>>>>>>>>params',params);
    // console.log('>>>>>>>>>>>>target',target);
    // console.log('>>>>>>>>>>>>methodName',methodName);
    // console.log('>>>>>>>>>>>>index',index);
  }
}

@logClass1
@logClass2("传入的类装饰器参数")
class Person{
  @logMethod('传入方法的参数1')
  setName(@logCanshu1("传入参数1的") cnashu1:any,@logCanshu2("传入的参数2") canshu2:any){

  }
  @logAttr('传入属性1的参数')
  names:string | undefined;
  constructor() {
   
  }
  @logMethod('传入方法的参数2')
  getNames(...res:any[]){
    console.log('>>>>>>>>>>>>getNames<<<<<<<<<<<<',res);
  }
 
  @logAttr('传入属性2的参数')
  urls:string | undefined;
}
let p=new Person();

// function logClass1(params:string){
//   return function(target:any){
//     console.log('类装饰器1')
//   }
// }

// function logClass2(params:string){
//   return function(target:any){
//     console.log('类装饰器2')
//   }
// }

// function logAttribute1(params?:string){
//   return function(target:any,attrName:any){
//     console.log('属性装饰器1')
//   }
// }

// function logAttribute2(params?:string){
//   return function(target:any,attrName:any){
//     console.log('属性装饰器2')
//   }
// }

// function logMethod1(params?:string){
//   return function(target:any,attrName:any,desc:any){
//     console.log('方法装饰器1')
//   }
// }
// function logMethod2(params?:string){
//   return function(target:any,attrName:any,desc:any){
//     console.log('方法装饰器2')
//   }
// }



// function logParams1(params?:string){
//   return function(target:any,attrName:any,desc:any){
//     console.log('方法参数装饰器1')
//   }
// }

// function logParams2(params?:string){
//   return function(target:any,attrName:any,desc:any){
//     console.log('方法参数装饰器2')
//   }
// }



// @logClass1('http://www.itying.com/api')
// @logClass2('xxxx')
// class HttpClient{

//   constructor(){
//   }

//   @logMethod1()
//   @logMethod2()
//   getData(){
//       return true;
//   }

//   @logAttribute1()
//   @logAttribute2()
//   public apiUrl:string | undefined;

//   @logMethod1()
//   @logMethod2()
//   setData(@logParams1() attr1:any,@logParams2() attr2:any,){

//   }
// }

// var http:any=new HttpClient();
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值