安庆大理运城常德铜陵江西
投稿投诉
江西南阳
嘉兴昆明
铜陵滨州
广东西昌
常德梅州
兰州阳江
运城金华
广西萍乡
大理重庆
诸暨泉州
安庆南充
武汉辽宁

如何在TypeScript中使用装饰器

7月11日 吴梦筱投稿
  TypeScript是JavaScript语言的扩展,它使用JavaScript的运行时和编译时类型检查器。
  这种组合允许开发人员使用完整的JavaScript生态系统和语言功能,同时,还可以在其之上添加可选的静态类型检查、枚举、类和接口。这些额外功能之一是装饰器的支持。
  装饰器是一种装饰类成员或类本身的方法,具有额外的功能。
  当我们将装饰器应用于类或类成员时,我们实际上是在调用一个函数,该函数将接收被装饰内容的详细信息,然后,装饰器实现将能够动态转换代码,添加额外的功能,并且减少样板代码。
  它们是在TypeScript中进行元编程的一种方式,TypeScript是一种编程技术,使程序员能够创建使用来自应用程序本身的其他代码作为数据的代码。
  本教程将分享如何在TypeScript中为类和类成员创建自己的装饰器,以及如何使用它们。
  它将引导我们完成不同的代码示例,我们可以在自己的TypeScript环境或TypeScriptPlayground(一个允许我们直接在浏览器中编写TypeScript的在线环境)中遵循这些示例。
  准备工作
  要完成本教程实例,我们需要做如下准备:一个环境,我们可以在其中执行TypeScript程序以跟随示例。要在本地计算机上进行设置,您将需要以下内容。
  为了运行处理TypeScript相关包的开发环境,同时,安装了Node和npm(或yarn)。本教程使用Node。js版本14。3。0和npm版本6。14。5进行了测试。
  如果是要在macOS或Ubuntu18。04上安装,请按照如何在macOS上安装Node。js和创建本地开发环境或如何在Ubuntu18。04上安装Node。js的使用PPA安装部分中的步骤进行操作。如果您使用的是适用于Linux的Windows子系统(WSL),这也适用。此外,我们还需要在机器上安装TypeScript编译器(tsc)。为此,请参阅官方TypeScript网站。如果我们不想在本地机器上创建TypeScript环境,我们可以使用官方的TypeScriptPlayground来跟随。我们将需要足够的JavaScript知识,尤其是ES6语法,例如解构、rest运算符和导入导出。本教程将参考支持TypeScript并显示内联错误的文本编辑器的各个方面。这不是使用TypeScript所必需的,但确实可以更多地利用TypeScript功能。为了获得这些好处,我们可以使用像VisualStudioCode这样的文本编辑器,它完全支持开箱即用的TypeScript。你也可以在TypeScriptPlayground中尝试这些好处。本教程中显示的所有示例都是使用TypeScript4。2。2版创建的。
  在TypeScript中启用装饰器支持
  目前,装饰器在TypeScript中仍然是一个实验性功能,因此,必须先启用它。在本节中,我们将了解如何在TypeScript中启用装饰器,具体取决于您使用TypeScript的方式。
  TypeScript编译器CLI
  要在使用TypeScriptCompilerCLI(tsc)时启用装饰器支持,唯一需要的额外步骤是传递一个附加标志experimentalDecorators:tscexperimentalDecorators
  tsconfig。json
  在具有tsconfig。json文件的项目中工作时,要启用实验性装饰器,我们必须将实验性装饰器属性添加到compilerOptions对象:{compilerOptions:{experimentalDecorators:true}}
  在TypeScriptPlayground中,装饰器默认启用。
  使用装饰器语法
  在本节中,我们将在TypeScript类中应用装饰器。
  在TypeScript中,我们可以使用特殊语法expression创建装饰器,其中expression是一个函数,将在运行时自动调用,其中包含有关装饰器目标的详细信息。
  装饰器的目标取决于我们添加它们的位置。目前,装饰器可以添加到类的以下组件中:类声明本身特性访问器方法参数
  例如,假设我们有一个名为seal的装饰器,它在类中调用Object。seal。要使用我们的装饰器,我们可以编写以下内容:sealedclassPerson{}
  请注意,在突出显示的代码中,我们在密封装饰器的目标之前添加了装饰器,在本例中为Person类声明。
  这同样适用于所有其他类型的装饰器:classDecoratorclassPerson{propertyDecoratorpublicname:accessorDecoratorgetfullName(){。。。}methodDecoratorprintName(parameterDecoratorprefix:string){。。。}}
  要添加多个装饰器,请将它们一个接一个地添加在一起:decoratorAdecoratorBclassPerson{}
  在TypeScript中创建类装饰器
  在本节中,我们将完成在TypeScript中创建类装饰器的步骤。
  对于名为decoratorA的装饰器,我们告诉TypeScript它应该调用函数decoratorA。将调用decoratorA函数,其中包含有关如何在代码中使用装饰器的详细信息。
  例如,如果您将装饰器应用于类声明,则该函数将接收有关该类的详细信息。此功能必须在您的装饰器工作的范围内。
  要创建自己的装饰器,我们必须创建一个与装饰器同名的函数。也就是说,要创建您在上一节中看到的密封类装饰器,您必须创建一个接收一组特定参数的密封函数。让我们这样做:sealedclassPerson{}functionsealed(target:Function){Object。seal(target);Object。seal(target。prototype);}
  传递给装饰器的参数将取决于装饰器的使用位置。第一个参数通常称为目标。
  密封装饰器将仅用于类声明,因此,我们的函数将接收一个参数,即目标,其类型为Function。这将是应用装饰器的类的构造函数。
  然后,在密封函数中,在目标(即类构造函数)以及它们的原型上调用Object。seal。当这样做时,不能将新属性添加到类构造函数或其属性中,并且现有属性将被标记为不可配置。
  重要的是要记住,目前在使用装饰器时无法扩展目标的TypeScript类型。这意味着,例如,你无法使用装饰器将新字段添加到类并使其成为类型安全的。
  如果在密封类装饰器中返回了一个值,该值将成为该类的新构造函数。如果想完全覆盖类构造函数,这很有用。
  已经创建了第一个装饰器,并将它与一个类一起使用。
  接下来,我们将学习如何创建装饰器工厂。
  创建装饰器工厂
  有时,我们需要在应用装饰器时将其他选项传递给装饰器,为此,我们必须使用装饰器工厂。
  在这里,我们将学习如何创建和使用这些工厂。
  装饰器工厂是返回另一个函数的函数。他们收到这个名字是因为他们不是装饰器实现本身。
  相反,它们返回另一个负责实现装饰器的函数并充当包装函数。通过允许客户端代码在使用装饰器时将选项传递给装饰器,它们在使装饰器可定制方面很有用。
  假设,有一个名为decoratorA的类装饰器,并且,我们想添加一个可以在调用装饰器时设置的选项,例如,布尔标志,可以通过编写类似于以下的装饰器工厂来实现此目的:constdecoratorA(someBooleanFlag:boolean){return(target:Function){}}
  在这里,decoratorA函数返回另一个带有装饰器实现的函数。注意,装饰器工厂如何接收一个布尔标志作为它的唯一参数:constdecoratorA(someBooleanFlag:boolean){return(target:Function){}}
  我们可以在使用装饰器时传递此参数的值。
  请参阅以下示例中突出显示的代码:constdecoratorA(someBooleanFlag:boolean){return(target:Function){}}decoratorA(true)classPerson{}
  在这里,当我们使用decoratorA装饰器时,将调用装饰器工厂,并将someBooleanFlag参数设置为true。
  然后,装饰器实现本身将运行。这允许我们根据使用方式更改装饰器的行为,从而,使我们的装饰器易于自定义和通过应用程序重用。
  请注意,我们需要传递装饰器工厂预期的所有参数。如果,我们只是应用装饰器而不传递任何参数,如下例所示:constdecoratorA(someBooleanFlag:boolean){return(target:Function){}}decoratorAclassPerson{}
  TypeScript编译器会给你两个错误,这可能会因装饰器的类型而异。对于类装饰器,错误是1238和1240:Unabletoresolvesignatureofclassdecoratorwhencalledasanexpression。Type(target:Function)voidisnotassignabletotypetypeofPerson。Type(target:Function)voidprovidesnomatchforthesignaturenew():Person。(1238)ArgumentoftypetypeofPersonisnotassignabletoparameteroftypeboolean。(2345)
  我们刚刚创建了一个能够接收参数并根据这些参数更改其行为的装饰器工厂。
  在下一步中,我们将学习如何创建属性装饰器。
  创建属性装饰器
  类属性是另一个可以使用装饰器的地方,在这里,我们将了解如何创建它们。
  任何属性装饰器都接收以下参数:对于静态属性,类的构造函数,对于所有其他属性,类的原型。成员的姓名。
  目前,没有办法获取属性描述符作为参数。这是由于TypeScript中属性装饰器的初始化方式。
  这是一个装饰器函数,它将成员的名称打印到控制台:constprintMemberName(target:any,memberName:string){console。log(memberName);};classPerson{printMemberNamename:stringJ}
  当我们运行上面的TypeScript代码时,你会在控制台中看到如下打印:name
  我们可以使用属性装饰器来覆盖被装饰的属性。这可以通过Object。defineProperty与属性的新setter和getter一起使用来完成。
  让我们看看如何创建一个名为的装饰器allowlist,它只允许将属性设置为静态允许列表中存在的值:constallowlist〔Jon,Jane〕;constallowlistOnly(target:any,memberName:string){letcurrentValue:anytarget〔memberName〕;Object。defineProperty(target,memberName,{set:(newValue:any){if(!allowlist。includes(newValue)){}currentValuenewV},get:()currentValue});};
  首先,我们要在代码顶部创建一个静态许可名单:constallowlist〔Jon,Jane〕;
  然后,我们创建一个属性装饰器:constallowlistOnly(target:any,memberName:string){letcurrentValue:anytarget〔memberName〕;Object。defineProperty(target,memberName,{set:(newValue:any){if(!allowlist。includes(newValue)){}currentValuenewV},get:()currentValue});};
  请注意,我们如何使用any作为目标的类型:constallowlistOnly(target:any,memberName:string){
  对于属性装饰器来说,目标参数的类型可以是类的构造函数,也可以是类的原型,在这种情况下使用any比较容易。
  在装饰器实现的第一行中,我们将被装饰的属性的当前值存储到currentValue变量中:letcurrentValue:anytarget〔memberName〕;
  对于静态属性,这将设置为其默认值(如果有)。
  对于非静态属性,这将始终未定义。这是因为在运行时,在编译的JavaScript代码中,装饰器在实例属性设置为其默认值之前运行。
  然后,我们将使用Object。defineProperty覆盖该属性:Object。defineProperty(target,memberName,{set:(newValue:any){if(!allowlist。includes(newValue)){}currentValuenewV},get:()currentValue});
  Object。defineProperty调用有一个getter和一个setter。getter返回存储在currentValue变量中的值。
  如果currentVariable在允许列表中,setter会将其值设置为newValue。
  让我们使用您刚刚编写的装饰器。创建以下Person类:classPerson{allowlistOnlyname:stringJ}
  我们现在将创建类的新实例,并测试设置并获取name实例属性:constallowlist〔Jon,Jane〕;constallowlistOnly(target:any,memberName:string){letcurrentValue:anytarget〔memberName〕;Object。defineProperty(target,memberName,{set:(newValue:any){if(!allowlist。includes(newValue)){}currentValuenewV},get:()currentValue});};classPerson{allowlistOnlyname:stringJ}constpersonnewPerson();console。log(person。name);person。namePconsole。log(person。name);person。nameJconsole。log(person。name);
  运行代码,我们应该看到以下输出:OutputJonJonJane
  该值永远不会设置为Peter,因为Peter不在允许列表中。
  如果我们想让代码更具可重用性,允许在应用装饰器时设置允许列表,该怎么办?这是装饰器工厂的一个很好的用例。
  让我们通过allowlistOnly装饰器变成装饰器工厂来做到这一点。constallowlistOnly(allowlist:string〔〕){return(target:any,memberName:string){letcurrentValue:anytarget〔memberName〕;Object。defineProperty(target,memberName,{set:(newValue:any){if(!allowlist。includes(newValue)){}currentValuenewV},get:()currentValue});};}
  在这里,我们将之前的实现包装到另一个函数中,即装饰器工厂。装饰器工厂接收一个名为允许列表的参数,它是一个字符串数组。
  现在,要使用的装饰器,我们必须通过许可名单,如以下突出显示的代码所示:classPerson{allowlistOnly(〔Claire,Oliver〕)name:stringC}
  尝试运行与之前编写的代码类似的代码,但有新的更改:constallowlistOnly(allowlist:string〔〕){return(target:any,memberName:string){letcurrentValue:anytarget〔memberName〕;Object。defineProperty(target,memberName,{set:(newValue:any){if(!allowlist。includes(newValue)){}currentValuenewV},get:()currentValue});};}classPerson{allowlistOnly(〔Claire,Oliver〕)name:stringC}constpersonnewPerson();console。log(person。name);person。namePconsole。log(person。name);person。nameOconsole。log(person。name);
  输出如下:OutputClaireClaireOliver
  显示它按预期工作,person。name永远不会设置为Peter,因为Peter不在给定的白名单中。
  现在,我们已经使用普通装饰器函数和装饰器工厂创建了第一个属性装饰器,是时候看看如何为类访问器创建装饰器了。
  创建访问器装饰器
  在这里,我们将了解装饰类访问器。
  就像属性装饰器一样,访问器中使用的装饰器接收以下参数:对于静态属性,类的构造函数,对于所有其他属性,类的原型。成员的姓名。
  但与属性装饰器不同的是,它还接收第三个参数,即访问器成员的属性描述符。
  鉴于PropertyDescriptors包含特定成员的setter和getter,访问器装饰器只能应用于单个成员的setter或getter,而不能同时应用于两者。
  如果我们从访问器装饰器返回一个值,该值将成为getter和setter成员的访问器的新属性描述符。
  下面是一个可用于更改gettersetter访问器的可枚举标志的装饰器示例:constenumerable(value:boolean){return(target:any,memberName:string,propertyDescriptor:PropertyDescriptor){propertyDescriptor。}}
  请注意示例中,我们是如何使用装饰器工厂的。这允许我们在调用装饰器时指定可枚举标志。
  以下是如何使用装饰器:classPerson{firstName:stringJonlastName:stringDoeenumerable(true)getfullName(){return{this。firstName}{this。lastName};}}
  访问器装饰器类似于属性装饰器。唯一的区别是它们接收带有属性描述符的第三个参数。现在,我们已经创建了第一个访问器装饰器。
  接下来,我们将学习如何创建方法装饰器。
  创建方法装饰器
  在这里,我们将学习如何使用方法装饰器。
  方法装饰器的实现与创建访问器装饰器的方式非常相似。传递给装饰器实现的参数与传递给访问器装饰器的参数相同。
  让我们重用之前创建的同一个可枚举装饰器,但这次是在以下Person类的getFullName方法中:constenumerable(value:boolean){return(target:any,memberName:string,propertyDescriptor:PropertyDescriptor){propertyDescriptor。}}classPerson{firstName:stringJonlastName:stringDoeenumerable(true)getFullName(){return{this。firstName}{this。lastName};}}
  如果我们从方法装饰器返回一个值,该值将成为该方法的新属性描述符。
  让我们创建一个deprecated的装饰器,它在使用该方法时将传递的消息打印到控制台,记录一条消息说该方法已被弃用:constdeprecated(deprecationReason:string){return(target:any,memberName:string,propertyDescriptor:PropertyDescriptor){return{get(){constwrapperFn(。。。args:any〔〕){console。warn(Method{memberName}isdeprecatedwithreason:{deprecationReason});propertyDescriptor。value。apply(this,args)}Object。defineProperty(this,memberName,{value:wrapperFn,configurable:true,writable:true});returnwrapperFn;}}}}
  在这里,我们正在使用装饰器工厂创建装饰器。这个装饰器工厂接收一个字符串类型的参数,这是弃用的原因,如下面突出显示的部分所示:constdeprecated(deprecationReason:string){return(target:any,memberName:string,propertyDescriptor:PropertyDescriptor){。。。}}
  deprecationReason将在稍后将弃用消息记录到控制台时使用。在不推荐使用装饰器的实现中,我们正在返回一个值。当我们从方法装饰器返回值时,该值将覆盖该成员的属性描述符。
  我们正在利用这一点为装饰类方法添加一个吸气剂。这样,我们就可以更改方法本身的实现。
  但是为什么不直接使用Object。defineProperty而不是为方法返回一个新的属性装饰器呢?这是必要的,因为,我们需要访问this的值,对于非静态类方法,它绑定到类实例。
  如果,我们直接使用Object。defineProperty,将无法检索this的值,并且如果该方法以任何方式使用this,则当从装饰器实现中运行包装的方法时,装饰器会破坏我们的代码。
  在这样情况下,getter本身的this值绑定到非静态方法的类实例,并绑定到静态方法的类构造函数。
  然后,在你的getter中创建一个本地包装函数,称为wrapperFn,此函数使用console。warn将消息记录到控制台,传递从装饰器工厂收到的deprecationReason,然后使用propertyDescriptor。value调用原始方法。
  apply(this,args),以这种方式调用原始方法,并将其this值正确绑定到类实例,以防它是非静态方法。
  然后,我们将使用defineProperty覆盖类中方法的值。这就像一种记忆机制,因为对同一方法的多次调用将不再调用getter,而是直接调用wrapperFn。
  我们现在正在使用Object。defineProperty将类中的成员设置为将wrapperFn作为其值。
  让我们使用已弃用的装饰器:constdeprecated(deprecationReason:string){return(target:any,memberName:string,propertyDescriptor:PropertyDescriptor){return{get(){constwrapperFn(。。。args:any〔〕){console。warn(Method{memberName}isdeprecatedwithreason:{deprecationReason});propertyDescriptor。value。apply(this,args)}Object。defineProperty(this,memberName,{value:wrapperFn,configurable:true,writable:true});returnwrapperFn;}}}}classTestClass{staticstaticMinstanceMember:stringhellodeprecated(Useanotherstaticmethod)staticdeprecatedMethodStatic(){console。log(insidedeprecatedstaticmethodstaticMember,this。staticMember);}deprecated(Useanotherinstancemethod)deprecatedMethod(){console。log(insidedeprecatedinstancemethodinstanceMember,this。instanceMember);}}TestClass。deprecatedMethodStatic();constinstancenewTestClass();instance。deprecatedMethod();
  在这里,我们创建了一个具有两个属性的TestClass:一个是静态的,一个是非静态的。我们还创建了两种方法:一种是静态的,一种是非静态的。
  然后,我们将已弃用的装饰器应用于这两种方法。运行代码时,控制台中会出现以下内容:Output(warning)MethoddeprecatedMethodStaticisdeprecatedwithreason:UseanotherstaticmethodinsidedeprecatedstaticmethodstaticMembertrue(warning))MethoddeprecatedMethodisdeprecatedwithreason:UseanotherinstancemethodinsidedeprecatedinstancemethodinstanceMemberhello
  这表明这两种方法都使用了包装函数正确包装,该函数将一条消息记录到控制台并说明弃用原因。
  你现在已经使用TypeScript创建了你的第一个方法装饰器。
  接下来,我们将学习如何创建TypeScript支持的最后一个装饰器类型,即参数装饰器。
  创建参数装饰器
  参数装饰器可以用在类方法的参数中。
  在这里,我们将学习如何创建一个与参数一起使用的装饰器函数,
  接收以下参数:对于静态属性,类的构造函数。对于所有其他属性,类的原型。成员的姓名。
  方法参数列表中参数的索引。
  无法更改与参数本身相关的任何内容,因此,此类装饰器仅对观察参数使用本身有用(除非您使用更高级的东西,例如反射元数据)。
  这是一个装饰器的示例,它打印被装饰的参数的索引以及方法名称:functionprint(target:Object,propertyKey:string,parameterIndex:number){console。log(Decoratingparam{parameterIndex}from{propertyKey});}
  然后,你可以像这样使用你的参数装饰器:classTestClass{testMethod(param0:any,printparam1:any){}}
  运行上述代码应在控制台中显示以下内容:Decoratingparam1fromtestMethod
  我们现在已经创建并执行了一个参数装饰器,并打印出返回装饰参数索引的结果。
  总结
  在本教程中,我们已经实现了TypeScript支持的所有装饰器,将它们与类一起使用,并了解了它们之间的区别。
  现在可以开始编写自己的装饰器来减少代码库中的样板代码,或者更加自信地使用带有库(例如Mobx)的装饰器。
  以上就是我跟你分享的全部内容,如果你觉得有用,请记得分享给你身边的朋友,也许能够帮助到他。
投诉 评论 转载

科技新活力连遭利空消息轰炸!石头科技困局待突破?犹记得,石头科技(688169。SH)的股价(以下均指前复权)在上市后一路狂飙,最高曾涨到1494。99元股的高价。彼时,其业绩表现也还不俗,增速虽然在持续放缓,但归母净利润却……国内哪家证券公司发展的比较好,各大证券公司排名如何?国内券商公司我首推三个有代表性的头部券商,他们分别是中信证券、中金公司、东方财富!中信证券,中国目前上市公司中最大的也是最成功的券商公司,隶属于中国五大财团之一的中信集团……如何在TypeScript中使用装饰器TypeScript是JavaScript语言的扩展,它使用JavaScript的运行时和编译时类型检查器。这种组合允许开发人员使用完整的JavaScript生态系统和语……适配多达37款机型!ColorOS11系统最新适配名单出炉在手机圈,有一句反复被用户验证的名言:硬件配置是吸引用户换机的首要因素,而软件体验则是留住用户的根本。这也意味着软件优化、系统体验,相对于一些有着唬人参数的配置来说,更得到了用……社交电商的四种典型商业模式实体经济双重受挫的后疫情时代,思购臻选社交电商社交电商有哪些模式,各自的特点是什么?社交电商不是简单的社交电商,它的形态可分为很多种:第一种:电商社交电商社交……实战入门PODpod是k8s中最小的管理单元,程序在容器中,容器在pod中k8s通过管理pod来管理容器,通过管理容器进而管理程序pod可以说是容器的封装,一个pod中可以存在一……为什么大家拍摄视频不用摄像机,反而选用单反照相机呢?大家好,我是把天聊死的剪辑师,从事影视制作很多年。曾经用过索尼和松下的DV带那种机器,中途用过5d2,5d3,现在用的是佳能C100mark2和松下的GH5,今年打算上RED。……英国斥130亿妥协拆除华为设备,OPPO出新品手机英国还是妥协了,斥130亿拆除华为设备!中企在英投资已大跌900亿英国电信(BT)开始按照指令拆除本国电信网络中的华为设备,替换成诺基亚设备。同时,被替换的不仅仅是刚刚安……美国盯上台积电?中美ampampquot芯片大战ampamp台积电已经成为了地缘战略的必争之地,成为了中美芯片大战的胜负手。这场芯片大战,中国可以从美国内部入手。美国为何盯上台积电?据《参考消息网》援引路透社报道,台积电曾在……2021年数学和计算机的6大突破2021年,数学家和计算机科学家们除了保存逐渐消失的知识和重新审视老问题,还在集合论、拓扑学和人工智能方面取得了令人激动的突破。他们在该领域的基本问题上取得了新的进展,庆祝跨越……滴滴出行彻底全网下架近期国家网信办通报,滴滴出行App由于存在严重违法违规收集使用个人信息问题现已被下架。官方公告显示,滴滴出行App存在严重违法违规收集使用个人信息问题。国家互联网信息办公……鞭牛晚报知网回应被中科院停用知乎否认裁员王思聪微博被禁言编者按:鞭牛士将以晚报形式盘点今日内发生的重要事件,内容涵盖国际、国内科技互联网,为科技行业从业者、用户传递行业信息。国内新闻1、张庭夫妇公司名下96套房产被查封,……
2022年魅族发布了699元的手机,曾经的国产手机老大哥怎么未来科技,屏下摄像头手机大盘点,哪一款才是你的菜?数字经济跨步向前护好隐私才能留住流量现货的红米K40为什么突然不香了?米粉的回答很实在,说到心坎信息超材料为6G做前瞻铺垫姚振华坚持实业报国宝能集团聚创新能源高价值发明专利获授权一只耳朵没有听力,另一只耳朵听力也不好,怎么保护好残余听力?鸿蒙新机被炒到29999元!堪称年度理财产品,真有这么香吗?家里备台汉印照片打印机,留下动态的美好记录小白挑战学c语言第十天文件操作以算法对算法浙江外卖在线为互联网监管蹚路8K电视新款齐亮相,全民8K还会远吗角落里造句用角落里造句大全线下购机真的是智商税?解脱单身证明参考范本三篇切尔诺贝利发现的真菌,靠吞辐射繁殖,有可能成为航天黑科技破壁机太吵了怎么隔音逛庙会的小学作文国外服装数码产品医药等多个行业连锁店宣布关闭部分内容分发三分天下编辑算法与社交特种兵的战斗力有多强?别被美剧给骗了,真实的他们真没那么厉害七种方法改变不良情绪化妆水和爽肤水的区别揭秘化妆水与爽肤之间的故事

友情链接:中准网聚热点快百科快传网快生活快软网快好知文好找七猫云易事利