叮咚,您有一份「前端项目开发规范」待查收!

·  阅读 2847
叮咚,您有一份「前端项目开发规范」待查收!

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

大家好,我是张三岁🤣,一只法系前端⚖️。爱分享🖋️、爱冰冰🧊🧊。
欢迎小伙伴们加我微信:maomaoibingbing,拉你进群,一起讨论,期待与大家共同成长🥂。

前言

项目开发规范相信小伙伴们都不陌生,良好的开发规范可以提高开发者的舒适度以及代码的可维护程度。本文将会以一个 React 项目前端开发者的视角,整理关于开发规范的所用所想。

说明:本文以梳理规范清单为主,举例会用最简单形式,细节部分不做过多延伸,部分章节有好文推荐。限于本人水平有限,叙述之中难免存在疏漏,还请小伙伴们多多谅解。另外还要感谢 弹吉他的Coder_Q 对我的引导与指正,让我能在开发规范的道路上更上一层楼。

一、由来与使命

1.1 开发规范的由来

在日常开发过程中,难免会遇到与他人协作的情况,例如前端开发者之间的协作、后端开发者之间的协作这样的同职位协作;也会有UI与前端开发者协作、前端开发者与后端开发者协作这样的异职位协作。为任务而进行职务划分、流程划分、阶段划分我们暂且称为项目流程管理,而任务的开发阶段的管理是本文的关注点。开发阶段管理的必要手段是制定开发规范

1.2 开发规范的使命与未来

开发规范的使命就是:

  • 定制统一标准
  • 提高代码可维护性
  • 降低阅读者理解成本
  • 提高项目的可扩展性

在代码生态日益完善的今天,开发者所的追求的代码是

  • 开源
  • 高效
  • 易维护
  • 可扩展
  • 规范化

对于我们普通开发者来说,我们需要做的就是尽可能拥抱开发规范,因为这不仅仅是大势所趋,更是符合事物发展规律的必由之路。

二、前端项目开发规范

1. 制定规范原则

实践是检验真理的唯一标准 ——《光明日报》

脱离了实践的真理就是一纸空谈,我们需要针对不同的项目灵活制定规范,制定规范的原则如下:

  • 降低阅读者理解成本
  • 解决或者规避一些问题
  • 符合当前项目的特点
  • 符合未来可能发生或将要发生的技术栈更新趋势

规范其实有很多种,我们只需选择最适合的即可。另外,千万不要被制定的规范所束缚,规范是服务于开发者而不是阻碍开发者的,如果一个开发规范反而大大降低了开发效率,那么我们就有理由质疑它存在的必要性。这里我们有制定规范三不提倡:

  • 不提倡制定意义不大的规范,任何规范都有使用成本
  • 不提倡制定收益远远小于成本的规范
  • 不提倡为了规范而规范,业务逻辑才是核心所在,不要在制定规范上花费过多时间

写到这里我突然想到一个问题:在项目中使用 TypeScript 明明增加了学习成本和使用成本,为什么还是有越来越多的优秀项目使用呢?答案就是它带来的收益也非常丰厚,提高了代码的健壮性,利于团队协作。但也并不是所有的项目都适用,如果项目本身非常小,那么使用它增加的成本可能是小于收益的,这个时候就可以选择不用。制定规范也一样,一定要把成本考虑在内。

2. 解耦、封装、复用

2.1 三者关系

谈到规范,就一定离不开这三座大山,这里浅谈一下我对三者关系的理解:解耦是为了更好的封装,封装是为了复用(代码块),复用是为了提高(开发)效率。

2.2 解耦

我们要打造高内聚、低耦合的模块,例如工具函数这样的,解耦就是不可或缺的步骤。下面简单列举几个例子:

/* 在 CSS 中解耦的应用 */

/* 某容器 */
.box {
    /* ... */
    background-color: greenyellow;
}

/* 控制背景色为粉色 */
.background-hotpink {
    background-color: hotpink;
}

/* 控制背景色为蓝色 */
.background-sklyblue {
    background-color: skyblue;
}
复制代码
<div class="box"></div>
<div class="box background-hotpink"></div>
<div class="box background-sklyblue"></div>
复制代码

上述案例就把控制背景色的代码单独拿了出来,算是 CSS 中最常见的一种解耦方式了。

// JavaScript 中的解耦

// 解耦前
const myFun = () => {
    // 完成 A 任务代码 ...
    // 完成 B 任务代码 ...
};

// 解耦后 拆分成完成两个不同任务的方法,单独维护,代码变多了,可维护性增强了
const funA = () => {
    // 完成 A 任务代码 ...
};
const funB = () => {
    // 完成 B 任务代码 ...
};
const myFunNew = () => {
    funA();
    funB();
};
复制代码

复杂的例子就不列举了,相信大家也都知道解耦的实际用法。这里还是要重申一下,不要过度解耦,毕竟解耦的最初目的是为了更好地维护我们的代码,要不要进行解耦还是要看具体的需求和代码的复杂程度的。最后简单总结一下对解耦的理解:

  • 可以降低代码的复杂程度,保持一段代码(一个方法)只专注于一个任务
  • 有利于代码的封装
  • 有利于代码的维护

2.3 封装

封装的目的是便于复用和维护。我们来看一段简单的代码:

// 给一个按钮绑定点击事件

// 获取元素方法
document.getElementById('myButton').onclick = () => {
    console.log('点击了~');
};

// 封装成方法后,在元素上增加 onclick 即可
const handleClickMyButton = () => {
    console.log('点击了~');
};
复制代码

相比之下,封装后的代码更利于维护和复用。平常使用的方法、hook、组件等都可以进行封装,通常都是在开发一个模块时候,如果能发现其他组件或页面中也会用到的完全相同或部分相同的代码抽离出来(可能是方法也可能是包含HTML的代码片段)进行封装。

2.4 复用

顾名思义,就是将封装好的代码进行多次使用,是节省开发成本的一种做法。往小处说,一个项目中可能存在多个封装好的方法、模块、组件;往大处说,由多个方法组成的解决一类问题的 插件库 ,由多个组件组成的 UI 库,为解决问题而诞生的一整套解决方案——框架都是复用的体现。(当然还有开源精神)

3. 公共模块

在项目中,我们会封装一些全局的公共组件、方法等进行复用,各模块间各司其职。下面将常见的公共模块进行说明。

├── ...
└── src
    ├── assets
    ├── components
    ├── hooks
    ├── services
    ├── styles
    └── utils
复制代码
  • assets 全局静态资源,一般存放图片、字体等文件。
  • components 全局公共组件,存放多个页面/组件中会多次使用的 UI 组件。
  • hooks 全局hook,存放可复用的hook。
  • services 全局公共请求。
  • styles 全局公共样式。
  • utils 全局公共方法。

4. UI 组件化

组件化通常是指把页面拆分成一个一个的组件,每个组件彼此独立,互相配合,也指将可复用的包含样式的代码进行封装以复用。下面是一个最简单的组件拆分:

父子组件.png

// src/pages/Home/index.tsx
// 父组件
import { CSSProperties } from 'react';
import Child from './components/Child'; // 子组件

const Home: React.FC = () => {
  const parentStyle: CSSProperties = {
    margin: '50px',
    padding: '50px',
    fontSize: '24px',
    backgroundColor: 'cornflowerblue',
  };
  return (
    <div style={parentStyle}>
      我是父组件
      <Child />
    </div>
  );
};

export default Home;
复制代码
// src/pages/Home/components/Child/index.tsx
// 子组件
import { CSSProperties } from 'react';

const Child: React.FC = () => {
  const childtStyle: CSSProperties = {
    marginTop: '30px',
    padding: '50px',
    backgroundColor: 'skyblue',
  };
  return <div style={childtStyle}>我是子组件</div>;
};

export default Child;
复制代码

当然你也可以封装若干公共组件,提高代码的复用性和可维护性。UI库 其实就是大量方便易用的组件的集合。实践证明,学习优秀的代码更易于我们技术的提升,推荐阅读:

5. 命名规范化

良好的变量命名也是规范化的重中之重,好的变量名称甚至可以起到媲美注释的作用。下面简单说一下命名的通用性要求:

  • 有意义
  • 用英文单词组合优先
  • 长度适中
  • 有规律性
/* ============ 这些命名不太好 ============ */

// 无意义
// 缺点:容易混淆,维护困难
const a1, b1, c1;

// 拼音
// 目前不流行用拼音,大多数人觉得会觉得不习惯
const biaoti, tupian, julao;

// 过于简单的单词
// 缺点:容易重复,可能一个文件中会用的很多相似用途的名称(例如名称总不能都叫name吧)
const title, img, name;

// 没有使用驼峰命名
const pagetitle, routeparam, allstatus;

// 过于大众化的名称
const myFunction = () => {};

/* ============ 这些命名真不戳 ============ */

// 常见命名规则:前缀 + 名词
// 优点:有意义、长度适中、驼峰形式的单词组合
const isTitleShow;
const handleSendMsg = () => {};
复制代码

如果实在不知道如何起名也可以借助工具网站:CODELF

CODELF.png

一个优秀的开发者需要具备良好的命名习惯,推荐阅读:

6. 合理的目录拆分与命名

6.1 目录的拆分

6.1.1 模块目录

一般情况下,一个模块/组件一个文件夹,如果拆分出子组件,则可以在当前模块下创建 components 文件夹。

└── ModuleA 某模块
    ├──components 存放当前模块子组件
        ├── Module1 子组件
        └── Module2
    ├── index.tsx 目录主文件通常命名为 index
    └── ...
复制代码

6.1.2 静态资源目录

静态资源的管理也相当重要。一个项目的静态资源一般都放在 src/assets 中,如果不加以分类,就可能像下面这样:

└── assets
    └── images
        ├── xxx.png
        └── ... x100
复制代码

放文件一时爽,找文件火葬场。对此我们应该按照项目中的模块划分进行分组:

└── assets
    ├── fonts 存放字体文件
    └── images
        ├── common 存放公共文件
        ├── moduleA
            └── A模块所用到的文件目录命名和模块命名相同或有关联,如果该模块文件较多则可以继续拆分
        ├── moduleB
        └── ...
复制代码

6.2 目录与文件的命名

对于目录的命名,我们应当注意以下方面:

  • 命名时应当注意复数形式,例如:assets、hooks、images 等
  • 常见的公共的目录应当小写,例如:layouts、pages、services 等
  • 组件目录应当遵循大驼峰式命名,例如:UserCenter、AboutUs

对于文件的命名,我们应当注意以下方面:

  • 每个模块的主文件应当命名为 index.xxx ,这样命名可以在引用时使用简写(如 /Module 相当于 /Module/index.tsx

7. HTML 相关

在此不单纯讨论 .html 的文件,还包括 Vue 中的 templateReactjsx 。我们需要注意的是:

  • 合理的使用换行
  • 嵌套的节点应该缩进
  • 合理的使用 meta 标签
  • html 标签书写 lang 属性
  • 标签上的属性使用双引号
  • 标签属性的书写顺序,重要的放前面
  • 尽量不使用生僻标签和兼容性不好的标签属性
// 省略非重点代码

// 某组件
// 此处传入参数过多,换行方便阅读
const ArticleItem = (
    {
        item = {},
        type = 'ARTICLE',
        isCurrentUser = false,
        isDel = false,
        titleStatus = false,
        division = false
    }
) => {
    // ...
};

export default () => {
    // 同样的,使用该组件的时候加换行让其显示更清晰
    return (
        <>
            <ArticleItem
                key={value?.id}
                item={value}
                type={value?.type}
                isCurrentUser={isCurrentUser}
                titleStatus
                division
            />
        </>
    );
};
复制代码

关于 meta 标签,此处奉上MDN文档传送门,请大家自行阅读:HTMLElement.lang

8. JavaScript 相关

8.1 功能模块化

构建的页面越复杂,编写的 JavaScript 也就越复杂,模块化正是为此降低程序复杂程度这一问题提供了一种切实有效的解决方案。它支持把封装好的代码进行按需导入与导出,从而提高部分代码的可复用性。常见的模块化规范有: CommonJSAMDRequireJS 等。

推荐阅读:

8.2 易于理解

也许你自认为写的代码“很酷”,维护你代码的同事🙂可不一定这么想。

尽量让你写的代码通俗易懂,不要使用以下魔法🧙痛击你的工友:

  • 第一式:冗余的代码
  • 第二式:糟糕的缩进换行
  • 第三式:从天而降的变量
  • 第四式:多此一举的操作
  • 第五式:令人头痛的位运算
  • 第六式:超过三层的判断嵌套
  • 第七式:和注释描述完全不一样的方法(堪称最令人窒息的操作)

8.3 使用语法糖和新特性

合理的使用 ES6 以及之后的新语法及新特性,使代码更简洁且易于维护,下面列举四个出镜率较高的语法:

/* ============ 箭头函数 ============ */
// before
var oldFunction = function () {
    var _this = this;
    // do something
};
var oldFunction2 = function () {
    return "hello world";
};

// after
const newFunction = () => {
    // do something
};
const newFunction2 = () => "hello world";

/* ============ 解构赋值 ============ */
// before
var person = {
    name: "王冰冰",
    sex: "female",
    job: {
        jobName: "compere",
    },
};
var name = person.name;
var sex = person.sex;
var jobName = person.job.jobName;

// after
const {
    name,
    sex,
    job: {
        jobName
    },
} = person;

/* ============ 模板字符串 ============ */
// before
var myStr = "world";
var helloWorld = "hello " + myStr + " !";

// after
const str = `hello ${myStr} !`;

/* ============ 对象属性简写 ============ */
// before
var a1, b1, c1;
var obj = {
    a1: a1,
    b1: b1,
    c: c1
};

// after
cosnt obj = {
    a1,
    b1,
    c1
};
复制代码

推荐阅读:

8.4 进行错误处理

为了保证代码的健壮性,我们应当做好错误处理和错误预防。为此我们至少需要:

  • 合理地抛出异常,进行错误提示
  • 对于可能出错的代码使用 try/catch
  • 默认数据占位UI 保证出错时页面不会崩溃
  • 不信任后端接口返回的任何数据格式(尤其是数组),使用数据前进行处理

推荐阅读:

8.5 进行单元测试

为保证代码整体的健壮性,可以在提交代码至仓库前进行前端单元测试。关于单元测试的具体操作流程这里就不展开讲了,此处附上 JavaScript 测试框架 Jest 官方文档传送门:www.jestjs.cn/

推荐阅读:

9. 样式相关

9.1 规则总结

在项目中我们一般使用 CSS 预处理器 例如:SassLessStylus,这里以 Less 为例子进行说明。通常,我们在使用中需注意:

  • 统一的命名规则
  • 合理使用嵌套语法
  • 合理定义公共变量
  • 非最末级元素,少用或不用标签选择器
  • 组件样式一一对应原则,一个组件对应一个样式文件
  • 公共组件或页面级组件中,尽量少使用易重复短类名形如:box、title、info
  • 可以在一个组件的最外层元素定义一个不容易重复的类名,释放组件内类名的命名空间

9.2 命名规则

一个项目我们需要制定一个统一且合理的命名规则,例如采用短横线分割式命名或者下划线分割式命名。为了与JavaScript 变量进行区分,通常不用驼峰式命名。

// 短横线 "-" 分割式命名
.hd-title {}

// 下划线 "_" 分割式命名
.banner_box {}
复制代码

9.3 合理嵌套

嵌套语法,既存之,必用之。但是也要尽可能避免嵌套过深的问题,用尽可能少的类名实现样式需求,同时还要保持可扩展性是一件考验前端功力的事。

// 嵌套越深,维护起来越麻烦,尽可能减少嵌套。当然这还需要合理的 DOM 元素嵌套
.classname-a {
    .classname-b {
        .classname-c {
            .classname-d {
                // ...
            }
        }
    }
}
复制代码

在嵌套中使用符号 & 代替父元素,优化代码。

.btn {

    // ...
    &.active {
        color: #0094ff;
    }
}
复制代码

9.4 公共变量

定义公共变量的目的是为了便于维护,避免改动一个全局配色,每个样式文件都得进行替换的麻烦。通常我们有如下的变量定义规则:

  • 根据实际项目需求,定义不同的公共变量
  • 不同类型的变量有不同的命名前缀或后缀,便于区分
  • 不同类型的变量各司其职,如果某个类型的变量数量过多可以单独拆分出一个文件进行维护
// 以下仅为示例代码

/* ============ 布局 ============ */
// 间距-大
@space-large: 36px;

// 间距-中
@space-middle: 24px;

// 间距-小
@space-small: 12px;

/* ============ 配色 ============ */
// 主题色
@theme-color: #0094ff;

// 底色
@background-lightgray-color: #f9f9f9;
复制代码

9.5 全局公共样式文件

每个项目都会有的全局样式文件,存放或引用了公共变量,公共类名,公共自定义类名等。需要我们用心去维护。(每个公共的模块都需要小心维护,避免出错)下面是一个示例样式文件:

// src/styles/index.less
// 全局公共样式

// ! 使用多行注释分割不同类型的样式代码,变量优先

// ! 这是一级分类
/* ======================== 公共变量 ======================== */

// ! 这是二级分类
/* ------------ 布局 ------------ */
// 版心宽度
@container-width: 1200px;

// 间距-大
@space-large: 36px;

// 间距-中
@space-middle: 24px;

// 间距-小
@space-small: 12px;

/* ------------ 配色 ------------ */
// 主题色
@theme-color: #0094ff;

// 底色
@background-lightgray-color: #f9f9f9;

// 标题
@title-color: #000c;

/* ======================== 公共类名 ======================== */
/* ------------ 布局 ------------ */
// 版心
.container {
    margin: 0 auto;
    width: @container-width;
    min-width: @container-width;
}

// ! 这是三级分类,同一分类下可能有不同公共类名/工具类名
// 外边距 margin
.mg-0-auto {
    margin: 0 auto !important;
}

.mg-b-16 {
    margin-bottom: 16px !important;
}

.mg-l-auto {
    margin-left: auto !important;
}

/* ------------ 文本 ------------ */
// 文字大小
.font-16 {
    font-size: 16px !important;
}

.font-24 {
    font-size: 24px !important;
}

// 文本对齐
.text-align-center {
    text-align: center !important;
}

// 单行文字超出隐藏
.text-ellipsis {
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
}

// ! 自定义类名就是在很多页面组件中需要使用的公共类名,如无必要,尽量少用
/* ------------ 自定义 ------------ */
// 公共盒子
.common-box {
    background-color: #fff;
    // ...

    // 灰色背景色
    &.gray-bg {
        background-color: @background-lightgray-color;
    }

    // 盒子内一级标题
    .box-title {
        color: @title-color;
        font-weight: bold;
    }
}
复制代码

10. 注释的使用

代码千万行,注释第一行。代码不规范,同事泪两行。

写代码最好养成随手加注释的好习惯,方便别人理解你的代码,也方便后期自己进行代码维护。一般情况下,这些地方需要添加注释:

  • 文件的第一行,标明文件的用途,如xxx组件、xxx样式
  • 同一个文件的不同业务逻辑之间,分割代码块
  • 函数定义的前一行
  • 函数的重要逻辑部分附近
  • 重要变量前一行或变量之后

推荐阅读:

11. 代码提交规范

这里指的提交代码到仓库的描述,即 git commit -m [message] 中的 message ——描述信息。杂乱无章的描述信息会增加后续问题溯源和责任划分的难度。限于篇幅,此处不再展开,推荐小伙伴们阅读代码提交规范相关文章:

12. 规范相关插件(VSCode)

12.1 格式化代码插件

Beautify

插件文档:Beautify

插件截图:

Beautify.png

Prettier - Code formatter

插件文档:Prettier - Code formatter

插件截图:

Prettier - Code formatter.png

12.2 代码风格检查

ESLint

插件文档:ESLint

插件截图:

ESLint.png

13. README.md

13.1 Why README ?

一个好的项目往往有一个好的文档,一般浏览或维护一个项目,最先查看的就是项目的说明文档, README 即“读我”的意思。

13.2 语法

Markdown是一种轻量级的标记语言,可用于将格式设置元素添加到纯文本文档中。Markdown 由John Gruber于2004年创建,如今已成为世界上最受欢迎的标记语言之一。—— MarkDown 中文网

有想了解更多有关于 Markdown 详情的小伙伴请移步 MarkDown 中文网

13.3 基本组成

一个优秀的项目文档一般包括以下内容:

  1. 项目介绍(包括项目名称、logo、简介、官网等基本信息)
  2. 使用技术栈
  3. 安装与运行
  4. 项目的特性、项目的功能清单
  5. FAQ
  6. 更新日志

推荐阅读:

14. 代码规范文档

要更加系统的学习代码规范,强烈推荐小伙伴们去 Code Guide 这里瞧瞧,干货满满!以下为该网站截图:

Code Guide.gif

三、与UI设计师对接

1. 前端规范与UI设计

一般的,一个项目对UI设计来说会有一套设计原则,即一系列风格统一的组件、配色、距离、字体等。而对于前端开发者来说,组件一般由开源的UI库提供,我们需要关心的是剩下的其他规范,定义相对应的变量加以控制。下图为项目色彩设计规范(部分)示例:

项目色彩设计规范示例.png

2. 我与UI设计师君哥的QA

问答背景

页面的美观程度很大程度上取决于UI的设计灵感,但是页面的风格统一缺取决于UI的设计规范。前端开发者在项目开发过程中可以根据样式的公共部分定义公共变量,封装公共组件,那么从开发流程上一级来看,UI设计者如果能给出一套完整的样式设计规范,会大大降低前端开发者的后期维护需求。开发的一个重要流程就是交流。怀着这样一种心情,我展开了与项目组UI设计师君哥的问答。

问题1:从您的工作出发,您觉得前端在UI部分的规范应该注意那些方面?

都很重要。一个完整设计规范是根据原子设计理论进行设计的,在界面中组织体现为由分子原子组成的模块,原子是所有物质的基本组成部分,如果只注意一个原子,是无法构成一整个分子的。—— UI设计师君哥

问题2:给每个项目都出一份前端开发规范(UI部分)您觉得收益是否大于付出呢?

我觉得开发规范更重要的是为了解决用户体验一致性,并且方便后期的维护,让设计的迭代交接更加无缝,使每一阶段的设计修改都有标准可参照,减少设计成本提高设计效率,不会使更迭偏离整体风格,同时使设计师不在花费更多的时间在样式上,跟多去思考业务与体验。—— UI设计师君哥

四、好书推荐

《代码整洁之道》

《代码整洁之道》.jpg

本书提出一种观念:代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。作为编程领域的佼佼者,本书作者给出了一系列行之有效的整洁代码操作实践。这些实践在本书中体现为一条条规则(或称“启示”),并辅以来自现实项目的正、反两面的范例。只要遵循这些规则,就能编写出干净的代码,从而有效提升代码质量。—— 豆瓣读书

《重构(第2版)》

《重构(第2版)》.jpg

本书是经典著作《重构》出版20年后的更新版。书中清晰揭示了重构的过程,解释了重构的原理和实践方式,并给出了何时以及何地应该开始挖掘代码以求改善。书中给出了60多个可行的重构,每个重构都介绍了一种经过验证的代码变换手法的动机和技术。本书提出的重构准则将帮助开发人员一次一小步地修改代码,从而减少了开发过程中的风险。—— 豆瓣读书

小结

说一千道一万,规范还是三步走:制定规范遵循规范维护规范。这三个步骤都很重要。规范市面上有很多,根据实际需求选择,不要同时选择两个相抵触的规范。有规范不遵守等于没有规范!最后认识到,任何事物都是不完美的,程序和代码也是,但我们可以一步步完善它,很多优秀的项目都是不断的迭代来的。最后,希望小伙伴们能看完此文后有所收获,谢谢!

参考资料

分类:
前端