跳转到主要内容

标签(标签)

资源精选(342) Go开发(108) Go语言(103) Go(99) angular(82) LLM(75) 大语言模型(63) 人工智能(53) 前端开发(50) LangChain(43) golang(43) 机器学习(39) Go工程师(38) Go程序员(38) Go开发者(36) React(33) Go基础(29) Python(24) Vue(22) Web开发(20) Web技术(19) 精选资源(19) 深度学习(19) Java(18) ChatGTP(17) Cookie(16) android(16) 前端框架(13) JavaScript(13) Next.js(12) 安卓(11) typescript(10) 资料精选(10) NLP(10) 第三方Cookie(9) Redwoodjs(9) LLMOps(9) Go语言中级开发(9) 自然语言处理(9) 聊天机器人(9) PostgreSQL(9) 区块链(9) mlops(9) 安全(9) 全栈开发(8) ChatGPT(8) OpenAI(8) Linux(8) AI(8) GraphQL(8) iOS(8) 软件架构(7) Go语言高级开发(7) AWS(7) C++(7) 数据科学(7) whisper(6) Prisma(6) 隐私保护(6) RAG(6) JSON(6) DevOps(6) 数据可视化(6) wasm(6) 计算机视觉(6) 算法(6) Rust(6) 微服务(6) 隐私沙盒(5) FedCM(5) 语音识别(5) Angular开发(5) 快速应用开发(5) 提示工程(5) Agent(5) LLaMA(5) 低代码开发(5) Go测试(5) gorm(5) REST API(5) 推荐系统(5) WebAssembly(5) GameDev(5) CMS(5) CSS(5) machine-learning(5) 机器人(5) 游戏开发(5) Blockchain(5) Web安全(5) Kotlin(5) 低代码平台(5) 机器学习资源(5) Go资源(5) Nodejs(5) PHP(5) Swift(5) 智能体(4) devin(4) Blitz(4) javascript框架(4) Redwood(4) GDPR(4) 生成式人工智能(4) Angular16(4) Alpaca(4) SAML(4) JWT(4) JSON处理(4) Go并发(4) kafka(4) 移动开发(4) 移动应用(4) security(4) 隐私(4) spring-boot(4) 物联网(4) nextjs(4) 网络安全(4) API(4) Ruby(4) 信息安全(4) flutter(4) 专家智能体(3) Chrome(3) CHIPS(3) 3PC(3) SSE(3) 人工智能软件工程师(3) LLM Agent(3) Remix(3) Ubuntu(3) GPT4All(3) 软件开发(3) 问答系统(3) 开发工具(3) 最佳实践(3) RxJS(3) SSR(3) Node.js(3) Dolly(3) 移动应用开发(3) 编程语言(3) 低代码(3) IAM(3) Web框架(3) CORS(3) 基准测试(3) Go语言数据库开发(3) Oauth2(3) 并发(3) 主题(3) Theme(3) earth(3) nginx(3) 软件工程(3) azure(3) keycloak(3) 生产力工具(3) gpt3(3) 工作流(3) C(3) jupyter(3) 认证(3) prometheus(3) GAN(3) Spring(3) 逆向工程(3) 应用安全(3) Docker(3) Django(3) R(3) .NET(3) 大数据(3) Hacking(3) 渗透测试(3) C++资源(3) Mac(3) 微信小程序(3) Python资源(3) JHipster(3) 大型语言模型(2) 语言模型(2) 可穿戴设备(2) JDK(2) SQL(2) Apache(2) Hashicorp Vault(2) Spring Cloud Vault(2) Go语言Web开发(2) Go测试工程师(2) WebSocket(2) 容器化(2) AES(2) 加密(2) 输入验证(2) ORM(2) Fiber(2) Postgres(2) Gorilla Mux(2) Go数据库开发(2) 模块(2) 泛型(2) 指针(2) HTTP(2) PostgreSQL开发(2) Vault(2) K8s(2) Spring boot(2) R语言(2) 深度学习资源(2) 半监督学习(2) semi-supervised-learning(2) architecture(2) 普罗米修斯(2) 嵌入模型(2) productivity(2) 编码(2) Qt(2) 前端(2) Rust语言(2) NeRF(2) 神经辐射场(2) 元宇宙(2) CPP(2) 数据分析(2) spark(2) 流处理(2) Ionic(2) 人体姿势估计(2) human-pose-estimation(2) 视频处理(2) deep-learning(2) kotlin语言(2) kotlin开发(2) burp(2) Chatbot(2) npm(2) quantum(2) OCR(2) 游戏(2) game(2) 内容管理系统(2) MySQL(2) python-books(2) pentest(2) opengl(2) IDE(2) 漏洞赏金(2) Web(2) 知识图谱(2) PyTorch(2) 数据库(2) reverse-engineering(2) 数据工程(2) swift开发(2) rest(2) robotics(2) ios-animation(2) 知识蒸馏(2) 安卓开发(2) nestjs(2) solidity(2) 爬虫(2) 面试(2) 容器(2) C++精选(2) 人工智能资源(2) Machine Learning(2) 备忘单(2) 编程书籍(2) angular资源(2) 速查表(2) cheatsheets(2) SecOps(2) mlops资源(2) R资源(2) DDD(2) 架构设计模式(2) 量化(2) Hacking资源(2) 强化学习(2) flask(2) 设计(2) 性能(2) Sysadmin(2) 系统管理员(2) Java资源(2) 机器学习精选(2) android资源(2) android-UI(2) Mac资源(2) iOS资源(2) Vue资源(2) flutter资源(2) JavaScript精选(2) JavaScript资源(2) Rust开发(2) deeplearning(2) RAD(2)

这篇文章将帮助你避免以后很难(或只是令人厌烦)纠正的错误。如果你打算创建一个新项目,并想让它变得令人惊叹——继续阅读!

纯函数

这是最重要的:保持你的函数的纯粹性,尽可能多地使用它们。

维基百科就是这样定义纯函数的:

  • 对于相同的参数,函数返回值是相同的(局部静态变量、非局部变量、可变引用参数或输入流没有变化),并且
  • 该函数没有副作用(没有局部静态变量、非局部变量、可变引用参数或输入/输出流的突变)。

使用关键字this的函数并不纯粹——它们使用的信息超出了它们的作用域,因此它们可以为相同的参数返回不同的结果。

尽管如此,我们的一些函数显然必须使用这一点——尽管如此,还是要尽可能多地将代码从不纯函数转移到纯函数。

变异其论点的函数是邪恶的最大来源——不惜一切代价避免它们。

不可变动性

数据的意外突变通常会导致危险的错误,这就是JS社区创建工具为数据结构提供不变性的原因。如果你愿意的话,你可以找到它们并阅读它们的文档(至少阅读它们是个好主意)。

我将向您展示一个在许多情况下“足够好”的技巧-它不会像不变性工具那样将您从每一个错误中拯救出来,但它将涵盖绝大多数情况,并且不会花费您任何费用:

export type UserModel = {
  readonly name: string;
  readonly email?: string;
  readonly age?: string;
}

通过在数据结构的字段中添加readonly关键字,每次尝试对作为参数接收的数据进行变异时,都会出现TS错误。所有这些都只会在编译时产生成本——它将在编译后被删除,并且不会在运行时进行检查(0性能成本)。

只需创建一个浅拷贝就可以消除该错误,并创建一个具有修改字段的新对象:

updatedUser = {
  ...user,
  age: 25
}

此外,它不像可变性工具那样具有限制性——如果你知道自己在做什么,并且想在特定情况下忽略这种限制,你可以应用-readonly修饰符或fest或ts本质类型中的Writeable类型。

在这些库中,您还可以找到使数据结构递归(深层)只读的类型,但在实践中,它并不像预期的那样完美和可预测-如果您愿意,可以尝试它们,但我发现最好手动将字段声明为只读(并对嵌套结构递归),或者只使用不变性库。

不变性是一个大话题,对于一篇文章的一部分来说太大了,你可以找到更多的信息和观点。

如果你还没有使用它,我鼓励你尝试一下:一开始,这将是一条有点崎岖的道路,但最终,你会开始应用规则和编程模式,并考虑到不变性,它将变得和常规编程一样容易。此外,它特别适用于纯函数;)

可见性和可变性修饰符

有一条众所周知的规则:使用const而不是let来声明变量。

我建议您也使用这个:将只读修饰符添加到您不打算修改的每个字段(在类、类型、接口中)。它将带来0成本——如果你不打算修改它,那么编译器会捕捉到任何意外的尝试。如果您以后决定将此字段设为可写字段,则可以删除只读修饰符。

export class HealthyComponent {
   
   // do not modify this!
   private readonly stream$: Observable<UserModel>;
   
   // it's ok to modify this
   protected isInitialized: boolean = false;

   constructor() {
     // you can initialize readonly fields in the constructor
     this.stream$ = of({name: example});
   }
}

对于类的字段和方法(包括组件、管道、服务和指令),请使用可见性修饰符:private、protected和public。

稍后您将为此感谢自己:当某个字段的方法是私有的,并且在某个时刻您看到您的类可以删除它(或者您需要修改它)时,您可以确保没有其他代码在使用它,所以可以重构它。

受保护的字段和方法不仅对继承的类可见,而且在Angular模板中也是可见的,因此,对模板应该访问的字段和方式使用Protected修饰符是一个很好的理由,而对模板不需要的字段和方法使用private修饰符则是一个非常好的理由。

我不添加公共修饰符——默认情况下,字段和方法是公共的,没有修饰符——但这是您个人的选择。由于输入和输出应该是公共的,所以我不会为它们添加修饰符,以免重载语法。

与私有修饰符一样,protected会让你知道你可以安全地删除或修改一些字段或方法,而不用担心模板。

任何代码都会随着时间的推移而增加复杂性,人类无法记住所有内容:这就是为什么这些小的修饰符在重构过程中会产生很大的影响。

类型别名

我希望有人早点告诉我:使用类型别名,而不是模型和其他数据结构的接口。

export type Model = {
  readonly field: string;
  readonly isExample?: boolean;
}

在另一个文件中:

import type { Model } from '@example/models';

通过这样做,当您只需要几个模型时,可以避免加载整个库,因为在TypeScript编译:documentation链接期间,像这样的导入将被完全删除。

此外,您将避免隐式接口声明合并,并将使用显式交集类型(如果您愿意的话)。没有其他显著差异,因此类型别名是最佳选择。

品牌类型

另一个技巧最好在项目开始时就开始使用。

“品牌类型”类似于常规类型,但有一些附加信息。代码可以忽略此添加,编译器将使用此添加来帮助您。

export type UUID = string & { __type: 'UUID' };

在上面的例子中,UUID仍然像字符串一样工作,但它有一条附加信息(“品牌”),这将有助于将其与普通字符串区分开来。

当您将用户的密码而不是电子邮件传递给某个功能时,品牌类型将使您免受这种情况的影响。当你发送错误的ID时,它会捕捉到错误——如果没有品牌类型,这是最难捕捉的情况,因为ID通常存储在具有类似名称和类型的变量和字段中。

我们可以使用一个独特的符号,也可以不使用:

// Method with additional fields:

export type UUID = string & { 
  readonly __type: 'UUID' 
};

export type DomainUUID = UUID & {
  readonly __model: 'Domain'
}

export type Domain = {
  readonly uuId: DomainUUID;
  readonly isActive: boolean;
  readonly name: string;
}

export type UserUUID = UUID & {
  readonly __model: 'User'
}

export type User = {
  readonly uuId: UserUUID;
  readonly name: string; 
}

// Method with a unique symbol:
declare const brand: unique symbol;

export type Brand<T, TBrand extends string> = T & {
  readonly [brand]: TBrand;
}

export type UUID = Brand<string, 'UUID'>;

// you can extend not only primitive types
export type DomainUUID = Brand<UUID, 'Domain'>;

export type Domain = {
  readonly uuId: DomainUUID;
  readonly isActive: boolean;
  readonly name: string;
}

export type UserUUID = Brand<UUID, 'User'>;

export type User = {
  readonly uuId: UserUUID;
  readonly name: string; 
}

你可以从代码中看到,这里唯一的符号优雅地取代了我们的人工字段__model。

关于品牌类型及其实现方法的更多信息,您可以在这个Twitter帖子中阅读。

类型化函数

请键入您的函数:它们的参数和返回的结果。

当你创建它们时,它们应该得到什么以及应该返回什么对你来说是显而易见的。但添加类型有两个原因:

  • 对于代码的其余部分,它们应该向该函数发送什么以及它保证返回什么并不明显;
  • 对你来说,几个月后,这也不会很明显。

如果我们应该声明返回类型,有不同的意见:我建议您声明它们,排除(如果您愿意的话)那些不返回任何内容的类型。

最初的主要好处并不明显,但在重构过程中会非常有帮助:

  • 如果您更改了函数的代码,并且不小心更改了它的返回类型,它将被编译器捕获;
  • 如果您有意更改函数的返回类型,而使用此函数的某些代码还没有准备好,那么它将被编译器捕获。

在推断类型的情况下,一些代码很可能会接受返回的结果,但会改变行为:

// before refactoring
function isAuthenticated(user: User) { 
  //...
  return true;
  // inferred type: boolean
}

if (isAuthenticated(user)) {
  markItemAsPaid();
} else {
  redirectToLogin();
}

// after refactoring
function isAuthenticated(user: User) {  
  //...
  return someApiRequest(user);
  // inferred type: Observable<boolean>
}

在这个例子中,编译器不会引发任何错误:“Observable”是一个对象,if(isAuthenticated(user))可以工作,但它总是返回true。

这是一个简单的例子,但对于更复杂的代码,发生这种情况的几率更高。

此外,它还显著提高了代码的可读性,这是一个非常重要的指标,比保存几个符号进行键入更重要。

继承

使用组合而非继承原则。

上面链接的文本解释了Angular上下文之外的内容,我将解释为什么在Angular中使用抽象类和继承组件和指令不是一个好主意。

除了继承带来的常见问题外,父类中声明的输入和输出也将被继承,因此您必须在子类中支持它们,即使您在特定的子类中不需要它们。

此外,在组件的情况下,每个Angular组件都应该有一个模板。这里有两种方法:

  • 链接到父母的模板:你不能覆盖任何东西,所以父母的模板会有很多分支来处理每个孩子的需求和特殊情况;
  • 创建一个子模板:您将不得不复制整个模板,这会降低代码的可重用性——这也是继承的最初原因。

与本文中的其他建议一样,这一建议带来了一些额外的动作和思考,但没有什么好的东西是免费的。这篇文章是为了帮助你,而不是争论或批评:尽可能多地使用本文中的建议✌️

阅读“掌握角度”: