关于自由职业者团队如何编写全栈JavaScript应用程序的故事

该材料的作者(我们今天将其翻译发表于此)说,他所工作的GitHub存储库以及其他一些自由职业者由于各种原因,在3天内获得了约8,200星的评价。 该存储库在HackerNews和GitHub Trending上排名第一,有20,000名Reddit用户投票赞成。



该存储库反映了本文专门讨论的用于开发全栈应用程序的方法。

背景知识


我打算花相当长的时间写这篇材料。 我相信,当我们的存储库非常受欢迎时,我找不到比这更好的时刻了。


GitHub趋势第一

我在自由职业者团队中工作。 我们的项目使用React / React Native,NodeJS和GraphQL。 本材料供那些想了解我们如何开发应用程序的人使用。 此外,这对于将来加入我们团队的人也很有用。

现在,我将讨论我们在开发项目时使用的基本原理。

越简单越好。


“越简单越好”说起来容易做起来难。 大多数开发人员都知道,简单性是软件开发中的重要原则。 但是这个原则并不总是容易遵循的。 如果代码很简单,这将有助于项目支持并简化该项目的团队工作。 此外,遵守该原则有助于处理六个月前编写的代码。

这是我在此原则上遇到的错误:

  • 满足DRY原则的不合理愿望。 有时复制和粘贴代码是很正常的。 无需抽象每两个彼此有点相似的代码片段。 我自己犯了这个错误。 也许所有人都承诺了。 DRY是一种很好的编程方法,但是选择失败的抽象只会使情况恶化并使代码库复杂化。 如果您想进一步了解这些想法,建议阅读Kent A Dodds的AHA编程
  • 拒绝使用可用的工具。 此错误的一个示例是使用reduce而不是mapfilter 。 当然,使用reduce可以重现map的行为。 但是,鉴于“代码的简单性”是一个主观的概念,这可能会导致代码的大小增加,并使其他人更难以理解该代码。 有时可能需要使用just reduce 。 而且,如果使用链中组合的mapfilter调用并使用reduce来比较数据集的处理速度,则可以发现第二个选项的工作速度更快。 在带有reduce的选项中reduce您必须查看一组值,而不是两个。 在我们面前是关于生产率和简单性的辩论。 在大多数情况下,我更喜欢简单性,并尝试避免过早的代码优化,也就是说,我将选择一个map / filter对而不是reduce 。 而且,如果事实证明mapfilter的构造成为系统的瓶颈,它将转换代码来reduce

下面将要讨论的许多想法旨在使代码库尽可能简单,并使其保持这种状态。

保持相似的实体彼此靠近


该原则(“共置原则”)适用于应用程序的许多部分。 这是存储客户代码和服务器代码的文件夹的结构,这是项目代码在一个存储库中的存储,这也是确定某个文件中哪个代码的决策。

▍仓库


建议您将客户端和服务器代码保存在同一存储库中。 很简单 不要使不必要的复杂化。 使用这种方法,可以方便地在项目上组织协调的团队工作。 我从事的项目使用各种存储库来存储材料。 这不是灾难,但是单一存储库使生活更加轻松。

▍应用程序客户端部分的项目结构


我们正在编写全栈应用程序。 即,客户端代码和服务器代码。 典型客户端项目的文件夹结构包括用于组件,容器,操作,化简和路由的单独目录。

使用Redux的项目中存在操作和简化程序。 我努力做到没有这个库。 我确信有一些使用相同结构的高质量项目。 我的某些项目为组件和容器提供了单独的文件夹。 组件文件夹可以存储带有诸如BlogPostProfile实体代码的文件之类的东西。 在容器文件夹中,有一些文件存储着容器代码BlogPostContainerBlogPostContainer 。 容器从服务器接收数据并将其传递给“愚蠢”的子组件,该子组件的任务是在屏幕上显示此数据。

这是一个工作结构。 它至少是同质的,这非常重要。 这导致这样一个事实,即参与该项目工作的开发人员将迅速了解其中正在发生的事情以及其各个部分所起的作用。 由于我最近一直尝试不使用这种方法,因此它的缺点是它迫使程序员不断地在代码库中移动。 例如, BlogPostContainerBlogPostContainer没有什么共同点,但是它们的文件彼此相邻,并且与实际使用它们的文件相距甚远。

一段时间以来,我一直在努力将计划共享其内容的文件放置在同一文件夹中。 这种项目结构化方法是基于文件的功能进行分组的。 通过这种方法,例如,如果将父组件及其“愚蠢”子组件放置在同一文件夹中,则可以大大简化您的生活。

通常,我们使用routes / screens文件夹和components文件夹。 组件文件夹通常存储诸如ButtonInput项目的代码。 该代码可以在应用程序的任何页面上使用。 路由文件夹中的每个文件夹都是一个单独的应用程序页面。 同时,具有与此路径相关的组件代码和应用程​​序逻辑代码的文件位于同一文件夹中。 并且,在多个页面上使用的组件的代码将落入components文件夹中。

在route文件夹中,您可以创建其他文件夹,在其中将负责形成页面不同部分的代码分组。 在路由由大量代码表示的情况下,这是有意义的。 但是,在这里,我要警告读者,不值得从嵌套程度很高的文件夹中创建结构。 这使项目的移动复杂化。 深度嵌套的文件夹结构是使项目过于复杂的标志之一。 应该注意的是,使用专用工具(例如搜索命令)为程序员提供了方便的工具,以便他们使用项目代码并查找他的需求。 但是项目的文件结构也会影响其可用性。

通过对项目代码进行结构化,您可以不基于路由,而是基于这些文件实现的项目功能对文件进行分组。 就我而言,这种方法在单页项目中完美展示,该项目在其唯一的页面上实现了许多功能。 但应注意,按路线对项目材料进行分组比较容易。 这种方法不需要做出特殊的脑力劳动就可以决定应将哪些实体彼此相邻并进行搜索。

如果我们进一步对代码进行分组,则可以决定将容器和组件的代码合理地放置在同一文件中。 您甚至可以走得更远-将两个组件的代码放在一个文件中。 我想您现在可能已经在想,推荐这样的事情真是亵渎神灵。 但是实际上,一切都远没有那么糟。 实际上,这种方法是完全合理的。 而且,如果您使用React钩子或生成的代码(或两者都使用),则建议您使用这种方法。

实际上,如何将代码分解为文件的问题并不是最重要的。 真正的问题是,为什么您可能需要将组件分为聪明的和愚蠢的。 这种分离有什么好处? 这个问题有几个答案:

  1. 以这种方式构建的应用程序更易于测试。
  2. 开发此类应用程序可以更轻松地使用诸如Storybook之类的工具。
  3. 愚蠢的组件可以与许多不同的智能组件一起使用(反之亦然)。
  4. 智能组件可以在不同平台上使用(例如,在React和React Native平台上)。

所有这些都是支持将组件分为“智能”和“愚蠢”的真实论点,但是它们并不适用于所有情况。 例如,在创建项目时,我们经常将Apollo Client与钩子一起使用。 为了测试此类项目,您可以创建Apollo响应模拟或挂钩模拟。 故事书也是如此。 如果我们谈论混合和共享“智能”和“愚蠢”组件,那么实际上我在实践中从未遇到过。 关于代码的跨平台使用,有一个项目我打算做类似的事情,但是从来没有做过。 它应该是Lerna 单一存储库 。 如今,您可以选择使用React Native Web来代替这种方法。

结果,我们可以说,将组件分为“智能”和“傻”有一定的意义。 这是一个值得了解的重要概念。 但是通常,您不必为此担心,尤其是考虑到React钩子的最新出现。

在一个实体中结合“智能”和“愚蠢”组件功能的优点是,它可以加快开发速度,并且可以简化代码的结构。

此外,如果出现这种需要,则可以始终将一个组件分为两个单独的组件-“智能”和“愚蠢”。

程式化


我们将情感 / 样式化组件用于样式化应用程序。 总是有将样式分离到单独文件中的诱惑。 我已经看到一些开发人员这样做。 但是,在尝试了两种方法之后,最终我找不到将样式移到单独文件中的理由。 就像我们在这里谈论的许多其他事情一样,开发人员可以通过将它们所关联的样式和组件组合到一个文件中来简化工作。

▍应用程序服务器部分的项目结构


就构造应用程序的服务器端代码而言,上述所有内容都是正确的。 我个人试图避免的典型结构可能看起来像这样

 src │ app.js #     └───api #   Express      └───config #      └───jobs #    agenda.js └───loaders #     └───models #    └───services # - └───subscribers #      └───types #    (d.ts)  Typescript 

我们通常在项目中使用GraphQL。 因此,他们使用存储模型,服务和识别器的文件。 我没有将它们分散在项目的不同位置,而是将它们收集在一个文件夹中。 通常,这些文件将被共享,如果将它们存储在同一文件夹中,则使用它们会更容易。

不要多次覆盖类型定义


我们在项目中使用了许多与数据类型相关的解决方案。 这些是TypeScript,GraphQL,数据库架构,有时是MobX。 结果,可以发现相同实体的类型被描述了3-4次。 应该避免这样的事情。 我们应该努力使用自动生成类型描述的工具。

在服务器上,可以将TypeORM / Typegoose和TypeGraphQL组合使用。 这足以描述所有使用的类型。 TypeORM / Typegoose允许您描述数据库架构和相应的TypeScript类型。 TypeGraphQL将帮助创建GraphQL和TypeScript的类型。

这是确定一个文件中TypeORM(MongoDB)和TypeGraphQL类型的示例:

 import { Field, ObjectType, ID } from 'type-graphql' import { Entity, ObjectIdColumn, ObjectID, Column, CreateDateColumn, UpdateDateColumn, } from 'typeorm' @ObjectType() @Entity() export default class Policy { @Field(type => ID) @ObjectIdColumn() _id: ObjectID @Field() @CreateDateColumn({ type: 'timestamp' }) createdAt: Date @Field({ nullable: true }) @UpdateDateColumn({ type: 'timestamp', nullable: true }) updatedAt?: Date @Field() @Column() name: string @Field() @Column() version: number } 

GraphQL代码生成器还可以生成许多不同的类型。 我们使用此工具在客户端上创建TypeScript类型,以及用于访问服务器的React挂钩。

如果使用MobX来控制应用程序的状态,然后使用几行代码,就可以自动生成TS类型。 如果还使用GraphQL,则应查看新软件包MST-GQL ,该软件包根据GQL方案生成状态树。

一起使用这些工具将使您免于重写大量代码,并避免了常见错误。

其他解决方案,例如PrismaHasuraAWS AppSync ,也可以帮助避免重复的类型声明。 当然,使用此类工具有其优点和缺点。 在我们创建的项目中,并非总是使用此类工具,因为我们需要将代码部署在我们自己的组织服务器上。

尽可能使用自动代码生成的方法


如果不使用上述工具自动生成代码,而是查看创建的代码,那么程序员经常会写同样的东西。 我可以提供的主要建议是,您需要为经常使用的所有内容创建摘要。 如果您经常输入console.log命令,请创建一个片段,如cl ,它将自动变成console.log() 。 如果您不这样做,请我帮助您进行代码调试,这会让我很不高兴。

有很多带有摘要的程序包,但是创建自己的摘要很容易。 例如,使用代码片段生成器

这是允许我向VS Code添加一些我最喜欢的代码片段的代码:

 { "Export default": {   "scope": "javascript,typescript,javascriptreact,typescriptreact",   "prefix": "eid",   "body": [     "export { default } from './${TM_DIRECTORY/.*[\\/](.*)$$/$1/}'",     "$2"   ],   "description": "Import and export default in a single line" }, "Filename": {   "prefix": "fn",   "body": ["${TM_FILENAME_BASE}"],   "description": "Print filename" }, "Import emotion styled": {   "prefix": "imes",   "body": ["import styled from '@emotion/styled'"],   "description": "Import Emotion js as styled" }, "Import emotion css only": {   "prefix": "imec",   "body": ["import { css } from '@emotion/styled'"],   "description": "Import Emotion css only" }, "Import emotion styled and css only": {   "prefix": "imesc",   "body": ["import styled, { css } from ''@emotion/styled'"],   "description": "Import Emotion js and css" }, "Styled component": {   "prefix": "sc",   "body": ["const ${1} = styled.${2}`", "  ${3}", "`"],   "description": "Import Emotion js and css" }, "TypeScript React Function Component": {   "prefix": "rfc",   "body": [     "import React from 'react'",     "",     "interface ${1:ComponentName}Props {",     "}",     "",     "const ${1:ComponentName}: React.FC<${1:ComponentName}Props> = props => {",     " return (",     " <div>",     " ${1:ComponentName}",     " </div>",     " )",     "}",     "",     "export default ${1:ComponentName}",     ""   ],   "description": "TypeScript React Function Component" } } 

除了代码片段之外,代码生成器还可以帮助节省时间。 您可以自己创建它们。 我喜欢为此使用plop

Angular有自己的内置代码生成器。 使用命令行工具,您可以创建一个由4个文件组成的新组件,其中提供了可以在组件中找到的所有内容。 遗憾的是,React没有这样的标准功能,但是可以使用plop单独创建类似的功能。 如果您创建的每个新组件都应以文件夹的形式显示,其中包含一个包含组件代码的文件,一个测试文件和一个Storybook文件,那么生成器将通过一个命令来帮助创建所有这些组件。 在许多情况下,这极大地延长了开发人员的寿命。 例如,向服务器添加新功能时,只需在命令行上运行一个命令即可。 之后,将自动创建包含所有必要基本结构的实体,服务和识别器的文件。

代码生成器的另一个优点是,它们有助于团队开发的统一性。 如果每个人都使用相同的plop生成器,那么每个人的代码将非常统一。

自动代码格式化


格式化代码是一项简单的任务,但是,不幸的是,它始终无法正确解决。 不要浪费时间手动对齐代码或在其中插入分号。 提交时,使用Prettier自动格式化代码。

总结


在本文中,我向您介绍了我们在多年的工作,反复的尝试中学到的一些知识。 有许多方法可以构造项目的代码库。 但是在他们当中没有人可以被称为唯一正确的人。

我想传达给您的最重要的一点是,程序员应该为组织项目的简单性,同质性和使用易于理解的结构而努力。 这简化了团队开发项目。

亲爱的读者们! 您如何看待本文概述的开发全栈JavaScript应用程序的想法?



Source: https://habr.com/ru/post/zh-CN456340/


All Articles