不以规矩,不能成方圆。
本人有幸经历了团队从缺乏标准到逐渐规范的一个过程,在此当做记录供大家参考。
本文从为什么需要规范以及建立规范的重要性出发,逐步引申出如何去建立适合自己团队的规范,详细讨论了前端具体涉及到的规范都有哪些,部分小节有具体的配置步骤和操作链接。
文中涉及到的所有配置均放在github上的 Demo 中,觉得不错的点个赞 ❤️❤️❤️。
下面我们开始吧?。
规范能给我们带来什么好处,如果没有规范会造成什么后果?这里主要拿代码规范来说。
统一代码规范的好处:
可读性、可维护性、可复用性、可移植性和可靠性,这会从根本上降低开发成本,也是最重要的一点。更加易于维护,因为团队内任何人都可以快速理解并修改。尽早发现问题,甚至完全预防问题,这将提高整个交付过程的效率。若不统一代码规范,可能会造成的后果:
代码风格不一,增加团队成员间的心理负担,极端情况下,某段代码只有某个人能修改(俗称屎山?)。适应不同的风格,会导致效率低下(阅读代码是我们花费时间最多的地方)。在这件事上,很难达成一致是我认为最重要的原因。并且,仅仅只是拥有规范也是不够的:
我曾想通过会议讨论的方式来制定规范,但效果却差强人意。将失败的原因总结为大致几点:
开发中依然有不少人选择无视规则。提出若干点优化建议后,没有对问题的优先级和侧重点进行划分,导致实际效果并不好。经历了上述的挫败之后,经过反复复盘总结,决定换一种方式来执行:
通过文档记录(wiki等),寻找业内最佳解决方案,在团队内进行统一。按照优先级和重要性进行排序划分,依次将问题纳入迭代,每个迭代重点解决其中几个即好。没有规则只是为了规则,制定规范的目的并不是一定要按照某某标准来执行,更多的是团队成员达成一致即可。经过两个月的迭代后,发现效果出奇的好?,大家的规范意识普遍增强,当遇到规范问题时也不再畏畏缩缩,而是大胆的抛出在群里讨论。
大致可以划分成这几个方向:
这里可能有小伙伴有疑问了,开发流程规范不是项目经理定的吗?️,跟我有什么关系?
这里想告诉大家的是,开发流程在一定程度上应该是由我们自己来掌控。不管是传统开发的模式还是敏捷开发的模式,对于开发者来说核心依旧是高质高效的完成用户提出的需求。
笔者曾见过不少开发者在拿到产品经理的需求后就开始急匆匆的写代码,以此来体现他们的高效,但往往却因为需求理解不到位和前期代码欠缺设计导致bug率高和返工。
如何找到适合自己的开发流程是需要依靠经验来支撑的,需要反复总结和思考,最终达到高质高效完成的目的。
说一说笔者自己比较喜欢的开发流程:

在接收到需求后应第一时间去了解这个需求的背景是什么?这么做到底有没有解决用户的痛点?或者说用户更深层次的需求是什么?如果团队的产品经理经验不丰富,往往可以在这个阶段砍掉很多不合理的需求(这一点真的很重要)。
对于复杂大功能往往还需要进行技术方案调研和技术方案设计,并输出详细的设计文档。涉及到细节上,则需要将数据流走向、组件设计等通过脑图的形式呈现出来。
由于每个开发者的IDE不同,即使IDE相同也会因为每个人的配置不一样导致格式化的结果不一样。如何确保团队内开发人员采用统一的格式化配置呢?
这里给推荐大家使用 prettier,它内置了一套格式化的规则,具体配置:
1). 安装依赖:
npm install --save-dev --save-exact prettier
// or
yarn add --dev --exact prettier
复制代码
2). 创建一个空配置文件,让编辑器和其他工具知道你正在使用 Prettier:
echo {}> .prettierrc.json
复制代码
3). 创建一个.prettierignore文件,让 Prettier CLI 和编辑器知道哪些文件不能格式化,example:
# Ignore artifacts:
dist
build
coverage
复制代码
4). 配置编辑器(VScode为例)
IDE中安装 Prettier-Code Formater 插件:

找到IDE中设置模块,搜索 format On Save,勾上这个就可以了。

现在当我们 Ctrl + S 保存代码时,插件就会帮助我们自动格式化了。
这里有小伙伴要问了,要是有人将没有格式化的代码提交上去怎么办?
这时候就需要在 git commit 的阶段自动将提交的代码进行格式化,这里我们借助工具 husky,它主要可以帮助我们在 git 阶段检查提交消息、运行测试、检查代码等。没接触过的小伙伴可以去官网了解一下,配置如下:
npm install --save-dev husky lint-staged
npx husky install
npm set-script prepare "husky install"
npx husky add .husky/pre-commit "npx lint-staged"
// or
yarn add --dev husky lint-staged
npx husky install
npm set-script prepare "husky install"
npx husky add .husky/pre-commit "npx lint-staged"
复制代码
package.json中:{
"lint-staged": {
"**/*": "prettier --write --ignore-unknown"
}
}
复制代码
这段配置的意思是:当执行 git commit 阶段前,先执行lint-staged,lint-staged中的内容就是对暂存区的文件执行格式化的命令。
其他:若使用的是脚手架工具搭建的项目,会自带eslint配置(eslintConfig)。prettier 和 eslint 会有一些配置上的冲突,这个时候需要安装eslint-config-prettier 以使 ESLint 和 Prettier 相互配合,安装完后在.eslintrc中配置(以Create-React-App为例):
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest",
"prettier"
]
},
复制代码
这样就可以用"prettier"的部分规则覆盖前面的规则,让它们都能正常工作。
更多详情见:Prettier。
JS/TS主流的大致有这几种:
比较推荐使用 Airbnb JavaScript Style Guide,它在 Github 上足有 12万 star,几乎覆盖了 JavaScript 的每一项特性。
具体配置:
1). 安装依赖
npm install eslint --save-dev
// or
yarn add eslint --dev
复制代码
2). 生成配置文件
npm init @eslint/config
// or
yarn create @eslint/config
复制代码
跟着终端中的提示,按照自身需求一步步选择即可。
有了具体的规范后,我们同样需要使用工具去约束:还是通过在git commit阶段校验,若不通过则取消提交。
配置(还是在 package.json 中的 lint-staged ):
"lint-staged": {
"**/*": "prettier --write --ignore-unknown", //格式化
"src/*": "eslint --ext .js,.ts,.tsx" //进行eslint校验
},
复制代码
注意: 这里如果选用的Typescript,则会默认使用
@typescript-eslint/parser解析器,官方为了追求更快的解析速度,并不会对.ts文件中的类型进行检查,只会做语法检测。如果需要对类型也进行检测,需要在extends中加上plugin:@typescript-eslint/recommended-requiring-type-checking。
但是在笔者的使用中发现效果并不好,一些基本的类型依然检测不出来,索性这里换了另一种方式:在
pre commit中执行yarn run tsc,这里的意思是对项目中ts文件进行类型检测,默认会读取根目录中的tsconfig.json配置。这种方式并不完美,它的弊端就在于
全量检测,如果项目不大还好,若是项目代码量够多,检测10-20s也是常有的事。
更多详情查看eslint官网:eslint.org/docs/user-g…
CSS检查代码规范使用 stylelint 插件,规范则推荐使用 stylelint-config-standard:
1). 安装
npm install --save-dev stylelint stylelint-config-standard
复制代码
2). 在项目的根目录中创建一个配置文件.stylelintrc.json,内容如下:
{
"extends": "stylelint-config-standard"
}
复制代码
3). 解决与prettier配置的冲突:
npm install --save-dev stylelint-config-prettier
复制代码
4). 将下面配置复制到.stylelintrc.json中:
{
"extends": ["stylelint-config-standard", "stylelint-config-prettier"]
}
复制代码
5). 在 git commitv阶段进行检测:
"lint-staged": {
"**/*": "prettier --write --ignore-unknown", //格式化
"src/**.{js,jsx,ts,tsx}": "eslint --ext .js,.jsx,.ts,.tsx", //对js文件检测
"**/*.{less,css}": "stylelint --fix" //对css文件进行检测
},
复制代码
下面列一些团队内定的其他规范:
(1)命名规范
变量的命名中应尽量减少缩写的情况发生,做到见名知意。
// ? 自我感觉良好的缩写:
let rContent = 'willen';
// ? 无需对每个变量都写注释,从名字上就看懂
let firstName = 'jackie';
// ? 从命名无法知道返回值类型
function showFriendsList() {....} // // 无法辨别函数意图,返回的是一个数组,还是一个对象,还是true or false?
// ? 明确函数意图,对于返回true or false的函数,最好以should/is/can/has开头
function shouldShowFriendsList() {...}
function isEmpty() {...}
function canCreateDocuments() {...}
function hasLicense() {...}
function sendEmailToUser(user) {.... } //动词开头,函数意图就很明显
复制代码
(2)写注释
在每个文件的顶部明确说明该组件做什么,有没有业务理解难点等等,对业务特殊函数/变量也需要写注释
/**
* 导航页面-右边区域
*/
const Content=>()=>xxx
const MAX_INPUT_LENGTH = 8; //用于限制密码输入框
function Component(props) {
return (
<>
{/* 如果用户没有订阅则不展示广告 */}
{user.subscribed ? null : <SubscriptionPlans />}
</>
)
}
复制代码
(3)变量兜底
// ? 对于求值获取的变量,没有兜底
const { data } = getApiRequest();
data.map((s) => s.id); //没有考虑data异常的情况,代码一跑就爆炸
// ? 对于求值变量,做好兜底
const { data = [] } = getApiRequest();
data.map((s) => s?.id); //没有考虑data异常的情况,代码一跑就爆炸
复制代码
(4)辅助函数必须是纯函数
// ? 不要让功能函数的输出变化无常
function plusAbc(a, b, c) { // 这个函数的输出将变化无常,因为api返回的值一旦改变,同样输入函数的a,b,c的值,但函数返回的结果却不一定相同。
var c = fetch('../api');
return a+b+c;
}
// ? 功能函数使用纯函数,输入一致,输出结果永远唯一
function plusAbc(a, b, c) { // 同样输入函数的a,b,c的值,但函数返回的结果永远相同。
return a+b+c;
}
复制代码
(5)优先使用函数式编程
// ? 使用for循环编程
for(i = 1; i <= 10; i++) {
a[i] = a[i] +1;
}
// ? 使用函数式编程
let b = a.map(item => ++item)
复制代码
(6)优先使用函数式组件
除非需要用到错误边界,否则函数式组件应该是首选方法。
(7)组件复杂度
如果一个组件做的事情太多,应适当提取一些逻辑,将其拆分为更小的组件。
如果提取的组件很复杂,则需要依照一定的规则和条件一一提取它。
代码行数并不是一个客观的衡量标准,更多是需要考虑责任划分和抽象。
(8)用错误边界
当需要对大量数据进行渲染处理时,需要通过错误边界组件对其进行降级处理。
更多详情见之前写的专题文章:浅析前端异常及降级处理 。
function Component() {
return (
<Layout>
<ErrorBoundary>
<CardWidget />
</ErrorBoundary>
<ErrorBoundary>
<FiltersWidget />
</ErrorBoundary>
<div>
<ErrorBoundary>
<ProductList />
</ErrorBoundary>
</div>
</Layout>
)
}
复制代码
(9)props参数传递
props一层层传递一直是我们很头疼的一个问题,最核心的问题是不清楚props是从哪个初始组件传来的,以及props中到底有哪些东西,上下文是什么?
因此对于传递较深的场景我推荐直接使用 context,对于 props 中的内容和上下文通过 TS 来解决。
// A.tsx
interface AProps {
param: string;
}
const A = ({ param }: AProps) => {
return <B param = {param} />;
};
// ? 上下文清晰
// B.tsx
const B = ({ param }: { param: AProps['param'] }) => {
return <div>hello world</div>;
};
复制代码
(10)props传参数量
如果超过 5 个props,就该考虑是否拆分该组件。在某些情况下,这是需要对组件进行重构的标志。
注意:组件使用的props越多,重新渲染的理由就越多。
(11)避免嵌套三元运算符
三元运算符在第一级之后变得难以阅读,虽然看起来节省了代码空间,但最好在代码中明确意图,保持良好的阅读性。
// ? 不够清晰,要是再嵌套一层两层呢
isSubscribed ? (
<ArticleRecommendations />
) : isRegistered ? (
<SubscribeCallToAction />
) : (
<RegisterCallToAction />
)
// ? 将判断逻辑进行拆分
function CallToActionWidget({ subscribed, registered }) {
if (subscribed) {
return <ArticleRecommendations />
}
if (registered) {
return <SubscribeCallToAction />
}
return <RegisterCallToAction />
}
function Component() {
return (
<CallToActionWidget
subscribed={subscribed}
registered={registered}
/>
)
}
复制代码
(12)将列表组件封装成独立组件
// ? 列表渲染和其他逻辑杂糅在一起
function Component({ topic, page, articles, onNextPage }) {
return (
<div>
<h1>{topic}</h1>
{articles.map(article => (
<div>
<h3>{article.title}</h3>
<p>{article.teaser}</p>
<img src={article.image} />
</div>
))}
<div>You are on page {page}</div>
<button onClick={onNextPage}>Next</button>
</div>
)
}
// ? 将列表组件提取出来,一目了然
function Component({ topic, page, articles, onNextPage }) {
return (
<div>
<h1>{topic}</h1>
<ArticlesList articles={articles} />
<div>You are on page {page}</div>
<button onClick={onNextPage}>Next</button>
</div>
)
}
复制代码
(13)避免嵌套渲染函数
// ? 不要将其定义在渲染函数组件中
function Component() {
function renderHeader() {
return <header>...</header>
}
return <div>{renderHeader()}</div>
}
// ? 将其抽离到独立的组件中去
import Header from '@modules/common/components/Header'
function Component() {
return (
<div>
<Header />
</div>
)
}
复制代码
(14)组件/函数导入导出
// ? 在文件头部导入,顺序依次为: 第三方库 > 公共组件/方法 > 非公共部分组件/方法
import React from 'react'
import _ from 'loadsh'
import Header from '@components/header'
import Content from './Content'
// ? 在底部导出
export { Content, Header }
export default Component
复制代码
...
不得不说,在这件事上我们团队曾经花费了大量的经历去思考和实践,也是信了React官方的鬼话:不要花超过五分钟在选择项目文件组织结构上。
在项目初期若不重视,到了后期就是到处天马行空,你很难在期望的目录下找到你想要的文件。
文件夹名称全部采用小写 + "-" 来隔开,index.ts更多是用来做导出作用,要不然最后编辑器中满屏的index.tsx,很难区分。
- src 开发目录
- pages 视图
- module-a 模块A
- components 私有组件
- ComA.tsx
- ComB.tsx
- index.module.less
- index.tsx
- Content.tsx
- module-b 模块B
- components 公共组件
- index.ts 导出所有组件
- header
- index.tsx
- index.module.less
- User.tsx
- useGetBaseInfo.hooks.ts
- routers 路由文件
- store redux中的数据
- utils 这里是以utils为后缀
- index.ts
- a.utils.ts
- b.utils.ts
- hooks 这里是以hooks为后缀
- index.ts
- a.hooks.ts
- b.hooks.ts
- styles 静态资源文件
- service api请求,这里是以api为后缀
- a.api.ts 按照后端微服务进行划分
- b.api.ts
- constans 常量
复制代码
通过对工具函数、hooks、api 等加上后缀,更加容易区分引入的文件。
git commit 规范主要可以帮助开发人员在 code review 期间更容易理解提交的内容,现在大部分主流 commit 规范都是基于Angular 团队的规范而衍生出来的,它的 message 格式如下:
<type>(<scope>): <subject>
<BLANK LINE>
<body>
<BLANK LINE>
<footer>
复制代码
每个提交消息都包含一个subject、一个body和一个footer (中间使用空行分割),提交信息的任何一行不能超过 100 个字符。
? type主要有以下几种类型:
? scope:可以是影响范围的任何内容。
? subject:包含对更改的简洁描述,规则:
? body:commit 具体修改内容, 可以分为多行,应该包括改变的动机,并与以前的行为进行对比。
? footer: 一些备注, 通常是修复的 bug 的链接。
截取一张开源库的 commit:

有了规范后,我们需要通过工具去约束:commitlint。它要做的就是在我们每次提交 git commit 的时候,都会帮我们检查 commit message 是否符合一定的规范,如果不符合,就让这次提交失败。
具体配置:
# 安装 commitlint cli 和 conventional config
npm install --save-dev @commitlint/{config-conventional,cli}
# Windows:
npm install --save-dev @commitlint/config-conventional @commitlint/cli
配置要使用的 commitlint 规则
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js
加入到husky中:
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
or
yarn husky add .husky/commit-msg 'yarn commitlint --edit $1'
复制代码
更多详情见官网:github.com/conventiona…
优秀的开发者应该反向推动UI的规范,并且能够支撑UI规范的落地。
UI 规范的最大好处就是能够提质提效:
那到底应该怎么去推动UI规范?我的做法是先让设计师去给出一系列的规范,没有相关规范就拉上产品经理一起制定规范。然后前端建立一套自己的组件库,再将组件库提供给UI设计师,以此来相互监督是否达成了规范协议。
具体参考 Antd设计规范。
统一规范的最根本目的是为了保证团队成员的一致性,从而减少沟通成本,提高开发效率。
学会热爱标准,但要确保它们不是一成不变的。如果制定的规范不适合您的团队,请确保可以适应和重写所需要的任何规则。它并不是要强制执行一种工作方式,而是为了帮助促进团队之间的互动 ???。
参考资料:前端规范
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/
我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为
我有一大串格式化数据(例如JSON),我想使用Psychinruby同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解