你的代码混乱?10个实用技巧助你大幅提升代码质量

我们都经历过这种情况:你开始了一份新工作,感到很兴奋。面试者对公司绘声绘色,这让你决定离开之前的工作。在所谓“假期”后的一个星期里——在许多情况下,这不过就是一次短暂的常规工作休息——终于是时候开启你的新职业生涯了。期望值很高,你将项目克隆到电脑上,突然面对一个真正的混乱:会让你痛哭的临时解决方案、缺乏标准、自成一套逻辑的代码。你看着它,想“这到底是什么?我不知道它是怎么工作的,但我不敢做任何细微的改动,因为任何改变似乎都会使一切停止工作。”

虽然这种情况可能比你想象的要少见,但它确实比你想象的要常见。为了防止这种情况发生,开发人员,特别是那些担任高级职位的人,需要起带头作用。毕竟,C级管理人员、技术负责人、产品负责人和其他人通常没有时间或没有把重点放在代码质量上,因为他们更关心为公司提供价值和成果,而不管代码质量如何。为了避免这种挑战,我在项目中采用了一些我认为必不可少并且每个人都应该采用的做法。尽管并非每个人都会记住你的代码,但那些进行代码审查的人肯定会注意并称赞你对细节的关注。这可能是在公司脱颖而出并可能获得应得升迁的最有效方法之一。

1. 使用绝对路径而不是相对路径

进入一个新项目时,经常会遇到充满“…/…/…/…/…/”的路径。这些路径被称为相对路径,虽然它们是导入文件的一种方式,但并不是最推荐的方法。理想的做法是使用绝对路径,它提供文件的完整路径。要在项目中实现这一点,需要进行一些配置,特别是如果使用Webpack或TypeScript。

设置Webpack(create-react-app):

如果使用create-react-app,配置绝对路径相对简单。首先在项目的根目录下创建一个名为“jsconfig.json”的文件,并添加以下内容:

{
  "compilerOptions": {
    "baseUrl": "src"
  },
  "include": ["src"]  
}

设置TypeScript:

如果使用TypeScript,请在“tsconfig.json”文件中添加以下配置:

{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@/*": ["src/*"]    
    }
  },
  "include": ["src"]
}

这样,你可以将这样的代码段:

import { Button } from '../../../../components/Button'
import { Icon } from '../../../../components/Icon'  
import { Input } from '../../../../components/Input'

转换成更清晰易读的代码,如:

import { Button } from '@/components/Button' 
import { Icon } from '@/components/Icon'
import { Input } from '@/components/Input'

2. 使用“导出条桶”进行模块组织

在看到前面提到的代码时,我想起了一种可以显著提高代码可读性和可维护性的技术:“导出条桶”,也称为“重新导出”。这种方法涉及在一个文件夹中创建一个名为“index.js”(或如果使用 TypeScript,则为“index.ts”)的文件,并从该文件导出文件夹中的所有模块。

为了说明这一点,假设你有一个名为“components”的文件夹,其中包含以下文件:“Button.tsx”、“Icon.tsx”和“Input.tsx”。使用“导出条桶”技术,你将创建一个“index.ts”文件,填写如下:

export * from './Button'
export * from './Icon' 
export * from './Input'

这样,当你想在页面或另一个模块中使用这些组件时,可以一次性导入它们,如下所示:

import { Button, Icon, Input } from '@/components' 

这种做法简化了代码组织,提高了维护性,因为它减少了每个组件的单独导入。此外,它使代码更清晰易懂,这在中大型项目中至关重要。

3. 在“默认导出”和“命名导出”之间进行选择

当我们深入探讨“导出条桶”的主题时,有必要注意到它可能与“export default”的使用存在冲突。如果这一点不清楚,我将通过例子进行说明:

让我们回到我们的组件:

export const Button = () => {
  return 
}
export default Button
export const Icon = () => {
  return Icon 
}
export default Icon
export const Input = () => {
  return   
}
export default Input

假设每个组件都在一个单独的文件中,并且你想一次性导入它们全部。如果你习惯于默认导入,你可能会尝试这样的事情:

import Button from '@/components'
import Icon from '@/components'
import Input from '@/components'

然而,这行不通,因为JavaScript无法确定使用哪个“export default”,从而导致错误。你会被迫这样做:

import Button from '@/components/Button'
import Icon from '@/components/Icon'
import Input from '@/components/Input'

然而,这取消了“导出条桶”的优势。你如何解决这个困境呢?解决方案很简单:使用“命名导出”,也就是不使用“default”进行导出:

import { Button, Icon, Input } from '@/components'

与“export default”相关的另一个关键问题是导入时无法重命名。我将分享我职业生涯早期遇到的一个真实例子。我继承了一个React Native项目,其中前一位开发人员为所有的东西使用了“export default”。有名为“Login”、“Register”和“ForgotPassword”的屏幕。然而,这三个屏幕都是相同的拷贝,只做了细微的修改。问题在于,在每个屏幕文件末尾都有一个“export default Login”。这导致了混乱,因为路由文件正确导入了:

import Login from '../../screens/Login'
import Register from '../../screens/Register'
import ForgotPassword from '../../screens/ForgotPassword'  

// 进一步在路由下使用:

  {
    ResetPassword: { screen: ResetPassword },
    Login: { screen: LoginScreen }, 
    Register: { screen: RegisterScreen },
  }

但在打开屏幕文件时,它们都导出了相同的名字:

const login() {
  return <>登录页面  
}
export default Login 
const login() {
  return <>注册页面
}
export default Login
const login() {
  return <>忘记密码页面
}
export default Login

这给维护带来了恶梦般的压力,不断混淆并需要极度警惕以避免错误。

总之,在项目中大多数情况下高度建议使用“命名导出”,只在严格必要时使用“export default”。有些情况下可能需要使用“export default”,比如Next.js路由和React.lazy。但是,在代码清晰性和遵守特定要求之间找到平衡是至关重要的。

4. 合适的文件命名约定

假设你有一个组件文件夹,其中包含以下文件:

--components:
----Button.tsx 
----Icon.tsx
----Input.tsx

现在,假设你想将这些组件的样式、逻辑或类型分离到单独的文件中。你将如何命名这些文件呢?一个明显的方法可能是:

--components:  
----Button.tsx
----Button.styles.css
----Icon.tsx
----Icon.styles.css  
----Input.tsx
----Input.styles.css

当然,当你打算进一步将组件划分为不同的文件(如逻辑或类型)时,这种方法可能看起来有些杂乱无章,难以理解。但是你如何保持结构的组织性呢?这里是解决方案:

--components:
----Button  
------index.ts (导出所有必需的)  
------types.ts
------styles.css
------utils.ts
------component.tsx
----Icon
------index.ts (导出所有必需的)
------types.ts
------styles.css  
------utils.ts
------component.tsx
----Input
------index.ts (导出所有必需的)  
------types.ts
------styles.css
------utils.ts  
------component.tsx

这种方法可以轻松识别每个文件的用途,简化了搜索所需内容的过程。 此外,如果使用Next.js或类似的框架,你可以调整文件命名以指示组件是针对客户端还是服务器端。例如:

--components:  
----RandomComponent
------index.ts (导出所有必需的)  
------types.ts 
------styles.css
------utils.ts
------component.tsx
----RandomComponent2  
------index.ts (导出所有必需的)
------types.ts
------styles.css 
------utils.ts
------component.server.tsx

这样,无需打开代码进行验证,就可以非常简单地区分组件是用于客户端还是服务器端。 组织和标准化文件命名对于维护开发项目的清晰性和效率至关重要。

5. 正确使用 ESLint 和 Prettier 进行代码标准化

想象一下,你正在与10多名同事一起在一个项目上工作,每个人都带来了自己以前的编码风格。这就是 ESLint 和 Prettier 发挥作用的地方。它们在保持整个团队的代码一致性方面发挥着关键作用。

Prettier 充当代码格式的“守护者”角色,确保每个人都遵守项目设置的风格指南。例如,如果项目标准规定使用双引号,那么你就不能单纯为了使用单引号而这样做,因为 Prettier 会自动替换它们。此外,Prettier 可以执行各种其他修复和格式设置,如代码对齐、在语句末尾添加分号等。你可以在官方文档中查看 Prettier 的具体规则:Prettier 选项。

另一方面,ESLint 强制执行特定的代码规则,帮助维护一个内聚而连贯的代码库。例如,它可以强制使用箭头函数而不是常规函数,确保 React 依赖数组被正确填充,禁止使用 “var” 声明而支持 “let” 和 “const”,并应用驼峰命名等命名约定。你可以在官方文档中找到 ESLint 的具体规则:ESLint 规则。

ESLint 和 Prettier 的组合使用有助于维持源代码的一致性。没有它们,每个开发人员都可以遵循自己的风格,这可能导致未来出现冲突和维护困难。 设置这些工具对项目的长期发展至关重要,因为它有助于保持代码的组织性和易于理解。 如果你还没有使用 ESLint 和 Prettier,请认真考虑将它们纳入你的工作流程中,因为它们会极大地使你的团队和项目整体受益。

6. Husky和Lint-Staged: 加强代码标准化

如果你已经熟悉ESLint和Prettier,你会知道在某些情况下可以绕过这些工具定义的规则。 为了确保遵守你建立的代码准则并避免格式问题,强烈建议使用 Husky 和 Lint-Staged。

这两个工具在你的开发工作流程中发挥着至关重要的作用,允许你设置 ESLint 和 Prettier 在进行提交之前运行。 这意味着除非代码符合你设置的规则,否则你将无法提交。 你还可以配置这些工具以在将代码推送到存储库之前检查代码。

此外,Husky 支持在提交或推送之前运行其他脚本或操作,这扩大了你自动执行验证任务和确保代码质量的可能性。

Husky 和 Lint-Staged 的另一个优点是它们与 GitHub 等代码托管平台的集成。 这使你可以设置自动测试和质量检查,以在接受拉取请求之前确保提交的代码符合你建立的规则,最大限度地减少问题并确保一致性。

这些工具对于防止开发人员进行有明显问题的提交,并确保代码始终与建立的准则保持一致至关重要。 ESLint、Prettier、Husky 和 Lint-Staged 的组合是维护项目代码质量和标准化的高效做法。

7. 自定义钩子用于逻辑重用

在使用 React 时,通常会使用由 react-router-dom、Next.js 或 react-navigation(用于 React Native)等库提供的导航钩子。 然而,这些通用的导航钩子不了解你的应用程序中的特定页面,这可能会带来局限性。 一个有效的解决方案是创建自定义的导航钩子,这些钩子知道你的应用程序中的所有页面,从而更容易在它们之间进行导航。

以下是创建自定义导航钩子的示例:

import { useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import type { Routes } from '@/types'  

export const useRouter = () => {
  const navigate = useCallback((path: Routes) => goTo(path), [goTo])

  return { navigate } 
}

由于抽象导航钩子的表面复杂性,可能会遇到一些初始抵制。 然而,从长远来看,这种方法提供了几个优势。 它简化了调用钩子的过程,并为该函数提供自动完成功能,使代码更清晰易懂。 此外,它简化了维护,因为如果将来需要更改导航库,你只需要在自定义钩子中进行更改,而不必更改使用该导航钩子的所有地方。

这种创建自定义钩子的想法也可以应用于你的应用程序的其他方面,例如管理 cookie、本地存储(localStorage)、API 调用等。 这种方法允许你在项目的多个地方轻松重用逻辑,促进模块化和简化代码维护。

你可能感兴趣的:(前端,javascript,node.js,开发语言)