[toc]
1 工作流规范
1.1 git规范
1.1.1 分支管理规范
git版本管理中主要有以下几种类型的分支:master、develop、feature、release、hotfix。
- 主分支
master
代码库应该有且仅有一个。所有提供给用户使用的正式版本,都在这个主分支上发布; - 开发分支
develop
我们把开发用的分支,叫做develop分支。开发分支(develop)应该总能够获得最新开发进展的代码。 - 临时分支
除了常设分支以外,还有一些临时性分支,用于应对一些特定目的的版本开发。临时性分支主要有三种:
- 功能
feature
分支
feature分支是为了开发某种特定功能,从develop分支上面分出来的。开发完成后,要并入develop。功能分支的名字,可以采用xx-feature-[功能名称/版本信息]的形式命名。 - 预发布
release
分支
release分支是指发布正式版本之前(即合并到master分支之前),我们可能需要有一个预发布的版本进行测试而从develop创建的分支。 - 修补bug
hotfix
分支
正式发布以后,难免会出现bug。这时就需要创建一个分支,进行bug修补。
修补bug分支是从Master分支上面分出来的。修补结束以后,再合并进master和develop分支。它的命名,可以采用hotfix-x的形式。
1.1.2 分支操作规范:
禁止master
分支直接用来作为开发分支,针对开发新功能,从master
分支拉出来一个作为本次上线开发分支。
其中一个fe对多个rd开发一个上线版本的多个功能点,建议先从master
分支拉出一个公共的版本开发分支用来上线,然后根据不同rd开发的功能区别建立分支,从master
拉出分支,例如:xx-feature-dev1.1、xx-feature-dev1.1。开发完成后,合并到xx-feature-dev2.0分支,部署上线。
1.1.3 分支命名规范:
结合分支类型及我们的gitlab ci + rancher 自动化部署方案,因此分支命名皆以开发人员姓名缩写开头+分支类型,如wy1-hotfix-[bug编号]、wy2-feature-[功能名称/版本信息]。(不同数字用来区分开发人员的多套环境)
1.1.4 提交规范
commit message
的作用,可以提供更多的历史信息,方便快速浏览。
其中commit message包含三个部分:header , body , footer。通常body和footer都可以省略。
header部分包含三个字段:type(必须)、scope(可选)、subject(必须)。
- type(必须)
- 用于说明
commit
的类别,只允许使用下面10个标识。- feat:新功能(feature)
- fix:修补bug
- docs:文档(documentation)
- style: 格式(不影响代码运行的变动)
- refactor:重构(即不是新增功能,也不是修改bug的代码变动)
- perf:性能优化
- test:增加测试
- chore:构建过程或辅助工具的变动
- revert:回退
- build:打包
- 用于说明
- scope(可选)
- 用于说明
commit
影响的范围,比如Button组件、store、首页、路由等等,视项目不同而不同。
- 用于说明
- subject(必须)
- 是
commit
目的的简短描述,不超过50个字符。- 以动词开头,使用第一人称现在时,比如change,而不是changed或changes
- 第一个字母小写
- 结尾不加句号(.)
示例
- 是
git commit -m "feat: 新增用户详情页接口"
git commit -m "fix: 修复用户注册时电话号码校验逻辑问题"
git commit -m "docs: 新增项目Readme 文档"
提交的代码必须通过 eslint 的校验
1.2 上线流程
①打包 → ②预发布 → ③上线 → ④发布
- 如第一步打包失败,查看打包的log,确认原因后重新打包。
常见原因1
:上次上线后未发布,本次分支打包失败;
解决办法:上次上线预发布后的版本进行发布操作,之后将本次分支合并一下master,再次打包、预发布。
常见原因 2
:master分支上线,未勾选is_tag;
选择master分支→ build_type选择「online」→ 勾选is_tag,再次打包:
其中打包的校验逻辑如下图所示:
- 如第二步预发布失败,查看预发布的log,确认原因后重新打包,重新预发布。
常见原因
:落后master,未合并master不允许预发布上线。
解决办法:合并master,重新打包→ 预发布。
其中预发布的校验规则如下图所示:
如预发布成功,发现线上代码有误,可选择取消预发布,包会从线上撤回。 -
如第四步发布失败,查看发布的log,确认原因后,在原来的基础上,重新发布。
如发布成功,即将上线分支合并到master。无需手动合并。
其中发布规则如下图所示:
uat上线
流程如下:
- 提工单,在Jenkins绑定Job,绑定过后,打包页面的build_type的列表里多一个uat的环境。
- 在上线系统申请要创建的模块的环境,上线系统的命名规范是:产品线服务名环境名,以小哥管理平台举例,产品线为sds,服务名为fsds,环境为uat-master,则build_type为:sds_fsds_uat-master。
- 新建一个sds_fsds_uat-master,则可准备打包了。
对不同分支的理解:
其中 master 分支一般和我们线上的版本同步。uat-master分支为我们的内测分支,内部同事可以在uat-master环境测试、体验我们的最新版本,原则上项目在上线前需将代码同步至uat-master分支,让 pm 及相关的同事测试,测试通过后再部署线上。
2 编码规范
2.1 Javascript
2.1.1 JS 变量命名
命名方法:小驼峰式命名
命名规范:前缀应当是名词。(函数的名字前缀为动词,以此区分变量和函数)
命名建议:尽量在变量名字中体现所属类型,如:length、count等表示数字类型;包含name、title表示为字符串类型。
// 推荐
const maxCount = 10;
const tableTitle = 'LoginTable';
// 不推荐
const setCount = 10;
const getTitle = 'LoginTable';
2.1.2 JS 函数命名
命名方法:小驼峰式命名法
命名规范:前缀应当为动词
命名建议:可使用常见动词约定
动词 | 含义 | 返回值 |
---|---|---|
can | 判断是否可执行某个动作(权限) | 函数返回一个布尔值。true:可执行;false:不可执行 |
has | 判断是否含有某个值 | 函数返回一个布尔值。true:含有此值;false:不含有此值 |
is | 判断是否为某个值 | 函数返回一个布尔值。true:为某个值;false:不为某个值 |
get | 获取某个值 | 函数返回一个非布尔值 |
set | 设置某个值 | 无返回值、返回是否设置成功或者返回链式对象 |
load | 加载某些数据 | 无返回值或者返回是否加载完成的结果 |
2.1.3 JS 常量命名命名方法:名称全部大写
命名规范:使用大写字母和下划线来组合命名,下划线用以分割单词
示例
const MAX_COUNT = 10;
const URL = 'http://www.sf-express.com/';
2.2 Html
2.2.1 元素及标签闭合
HTML元素共有以下5种:
空元素:area、base、br、col、command、embed、hr、img、input、keygen、link、meta、param、source、track、wbr
原始文本元素:script、style
RCDATA元素:textarea、title
外来元素:来自MathML命名空间和SVG命名空间的元素。
常规元素:其他HTML允许的元素都称为常规元素。
所有具有开始标签和结束标签的元素都要写上起止标签,某些允许省略开始标签或和束标签的元素亦都要写上。
我是h1标题
我是一段文字,我有始有终,浏览器能正确解析
我是h1标题
我是一段文字,我有始无终,浏览器亦能正确解析
2.2.2 类型属性
不需要为 CSS、JS 指定类型属性,HTML5 中默认已包含
2.2.3 元素属性
元素属性值使用双引号语法
元素属性值可以写上的都写上
2.2.4 特殊字符引用
在 HTML 中不能使用小于号 “<” 和大于号 “>”特殊字符,浏览器会将它们作为标签解析,若要正确显示,在 HTML 源代码中使用字符实体
more>>
more>>
2.2.5 附标准模板
HTML5标准模版
2.3 Css
2.3.1 命名规范
所有的命名用小写的英文单词
不使用简单的方位词直接命名,如"left","bottom"
不缩写单词,除非一看就明白的单词
公共命名参考如下:
参考命名 | 模块 |
---|---|
wrapper | 页面外围控制整体布局宽度 |
container | 容器,用于最外层 |
layout | 布局 |
header | 页头部分 |
footer | 页脚部分 |
nav | 主导航 |
sub_nav | 二级导航 |
menu | 菜单 |
sub_menu | 子菜单 |
side_bar | 侧栏 |
sidebar_l | 左边栏 |
message | 提示信息 |
tips | 小技巧 |
vote | 投票 |
friendlink | 友情链接 |
title | 标题 |
summary | 摘要 |
search_input | 搜索输入框 |
search | 搜索 |
list | 列表 |
home_page | 首页 |
sub_page | 二级页面子页面 |
download | 下载 |
2.3.2 推荐展开格式
// 推荐
.sftc{
display: block;
width: 50px;
}
// 不推荐
.sftc{ display: block;width: 50px;}
2.3.3 样式选择器,属性名,属性值关键字全部使用小写字母书写
/* 推荐 */
.sftc{
display:block;
}
/* 不推荐 */
.SFTC{
DISPLAY:BLOCK;
}
2.3.4 分号
每个属性声明末尾都要加分号;
为单个css选择器或新申明开启新行
// 推荐
.sftc,
.sftc_logo,
.sftc_hd {
color: #ff0;
}
.nav{
color: #fff;
}
// 不推荐
.sftc,sftc_logo,.sftc_hd {
color: #ff0;
}.nav{
color: #fff;
}
2.3.5 颜色值 rgb() rgba() hsl() hsla() rect() 中不需有空格,且取值不要带有不必要的 0
// 推荐
.sftc {
color: rgba(255,255,255,.5);
}
// 不推荐
.sftc {
color: rgba( 255, 255, 255, 0.5 );
}
2.3.6 属性值十六进制数值能用简写的尽量用简写
// 推荐
.sftc {
color: #fff;
}
// 不推荐
.sftc {
color: #ffffff;
}
2.3.7 不要为 0 指明单位
// 推荐
.sftc {
margin: 0 10px;
}
// 不推荐
.sftc {
margin: 0px 10px;
}
2.4 代码格式
2.4.1 eslint
使用如下几个规则集合,校验 typescript 和 react。
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'prettier',
// 'plugin:prettier/recommended',
],
2.4.2 prettier
需要安装 eslint-config-prettier 和 eslint-plugin-prettier
代码格式化工具,统一代码风格
module.exports = {
singleQuote: true,
semi: true,
tabWidth: 2,
proseWrap: 'preserve',
endOfLine: 'lf',
printWidth: 100,
};
项目文件改动
- 增加依赖项 [email protected]、[email protected] 和 eslint-webpack-plugin@^2.6.0
- 根目录下增加文件 .eslintignore、.eslintrc.js、.prettierignore、.prettierrc.js、.gitattributes
- 删除 package.json 里的 eslintConfig 和 prettier
- lint-stage 增加代码风格检查
- 增加项目的vs code settings 里 eslint 自动修复并关闭保存时格式化(与prettier 冲突)
具体的包的详细内容请查看EsLint 详细。无特殊情况请不要关闭或修改项目中的rules。
2.5 注释规范
2.5.1 单行及多行注释
单行注释
句首加空格
句尾不加标点符号
// 我是单行注释
const sum = 0;
多行注释
多行注释使用 /** ... */,而不是多行的 //
// bad
// make() returns a new element
// based on the passed in tag name
function make(tag) {
// ...
return element;
}
// good
/**
* make() returns a new element
* based on the passed-in tag name
*/
function make(tag) {
// ...
return element;
}
2.5.2 使用特殊注释标记
发现某个可能的 bug,但因为一些原因还没法修复;或者某个地方还有一些待完成的功能,这时我们需要使用相应的特殊标记注释来告知未来的自己或合作者。常用的特殊标记有两种:
FIXME: 说明问题是什么
TODO: 说明还要做什么或者问题的解决方案
class Calculator extends Abacus {
constructor() {
super();
// FIXME: shouldn’t use a global here
total = 0;
// TODO: total should be configurable by an options param
this.total = 0;
}
}
2.5.3 函数、类、文件、事件等使用 jsdoc 规范
/** 开始,注此处两个星
@description 简要描述
@param {类型} 参数 单独类型的参数
@param {[类型|类型|类型]} 参数 多种类型的参数
@param {类型} [可选参数] 参数 可选参数用[]包起来
@return {类型} 说明
@author 作者 创建时间 修改时间(短日期)改别人代码要留名
@example 举例(如果需要)
*/
/**
* @description 减法运算
* @param {Num} num1 减数
* @param {Num} num2 被减数
* @return {Num} result 结果
*/
function minus(num1,num2){
return num1 – num2;
}
2.6 React
2.6.1 命名属性规范(组件/属性)
扩展名: 用 .tsx 作为组件扩展名。
文件名: 用大驼峰作为文件名,如:ReservationCard.tsx。
参数命名: React 组件用大驼峰,组件的实例用小驼峰。
// bad
import reservationCard from './ReservationCard';
// good
import ReservationCard from './ReservationCard';
// bad
const ReservationItem = ;
// good
const reservationItem = ;
组件命名: 文件名作为组件名。例如:ReservationCard.tsx 应该用 ReservationCard 作为参数名。 然而,对于一个文件夹里的跟组件,应该用 index.tsx 作为文件名,同时用文件夹名作为组件名
// bad
import Footer from './Footer/Footer';
// bad
import Footer from './Footer/index';
// good
import Footer from './Footer';
Props 命名: 避免用 DOM 组件的属性名表达不同的意义
// bad
// bad
// good
2.6.2 hook规则
Hooks 只能应用于函数式组件中
只在 React 函数最顶层使用 Hooks
不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层调用他们
// bad
function a () {
const [count, setCount] = useState(0)
useEffect(function persistForm() {
localStorage.setItem('formData', accountName)
})
const x = function () {}
const [timer, setTimer] = useState(0)
// main logic
}
// bad
function a () {
const [count, setCount] = useState(0)
useEffect(function persistForm() {
localStorage.setItem('formData', accountName)
})
const [timer, setTimer] = useState(0)
const x = function () {}
// main logic
}
2.6.3 状态管理规范
现有 sds 项目中有三种状态管理工具,分别是:reudx-saga、redux-toolkit、dva,dva暂时只在 newModule 模块中使用。每一个页面都有其页面的 ”store“,页面的状态单独管理,不与其他页面耦合。
newModule 以外的模块中有reudx-saga、redux-toolkit,新页面使用redux-toolkit,采用动态注入方式,在每个页面的index.tsx中使用
slice
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { createServiceAsyncThunkCreator } from 'utils/asyncRedux';
const createSimpleAsyncThunk = createServiceAsyncThunkCreator(NAMESPACE, services);
export const getDataList = createSimpleAsyncThunk('getDataList');
export const initialState: IStore = {
isEditing: false,
selectCourierInfo: {},
filterDataObjParent: [],
filterDataObj: {},
};
const slice = createSlice({
name: NAMESPACE,
initialState,
reducers: {
updateTableData(state, action: PayloadAction) {
state.tableData = action.payload;
},
updatePagination(state, action: PayloadAction) {
state.pagination = action.payload;
}
},
extraReducers: {
[getDataList.pending.type]: (state) => {
state.tableLoading = true;
},
[postEditEntity.fulfilled.type]: (state) => {
....
},
[getDataList.rejected.type]: (state) => {
state.tableLoading = false;
},
},
});
export const { actions, reducer } = slice;
injectReducer
index 中使用一次即可。
const withReducer = injectReducer({ key: NAMESPACE, reducer });
withReducer(Component);
ConnectedProps
react-redux 提供一个类型,能自动推导出,connected props 类型, 无需手写类型。
import { connect, ConnectedProps } from 'react-redux';
// 使用 ConnectedProps 自动推导 connected props 类型
type PropsFromRedux = ConnectedProps;
const Component: React.FC = (props) => {
return ...
}
// connector 使用使用多少、引入多少
const connector = connect(
createStructuredSelector({
reminderMoal: selectors.reminderMoal,
}),
{
initTable: actions.initTable,
updateFilterData: actions.updateFilterData,
updateBatchImportModal: actions.updateBatchImportModal
}
);
export default connector(Component);
newModule
这个模块使用 dva
- 定义类型 State
- 定义类型 Model
- 定义 model
- 从 store 里导出 useSelector 和 useDispatch**
- 获取类型良好的开发体验
// 详细参见项目中的README
interface State {
name: string;
}
/**
* 定义Model
* Reducer<当前State, reducer名字和对应的参数类型>
* Effect<可选参数类型>
* EffectWithType 使用数组形式定义effects
*/
interface Model {
namespace: 'demo';
state: State;
reducers: Reducers<
State,
{
// 参数类型 model中recuder中的payload类型,以及dispatch中payload校验使用
modString: string;
}
>;
effects: {
// 参数类型 model中effect中的payload类型,以及dispatch中payload校验使用
modPeople: Effect<{ name: string }>;
};
}
const model: Model = {
...
// 智能提示 Reducer 里的 payload
// 提示和校验 call 里的请求参数
// 提示和校验 put 的参数
namespace: 'demo',
state: initialState,
effects: {
*getTableDate({ payload }, { put, call }) {
const result = yield call(getTableList, payload);
console.log(result);
},
reducers: {
update(state, { payload }) {
return {
...state,
obj1: payload,
};
},
}
// 从 store 里导出 useSelector 和 useDispatch
import { useSelector, useDispatch } from 'src/store';
// 校验 dispatch 的 type 和 payload 的类型 是否准确
const dispatch = useDispatch();
dispatch({
type: 'demo/modPeople',
payload: {
name: ''
}
})
export default model;
// 从 store 里导出 useSelector 和 useDispatch
import { useSelector, useDispatch } from 'src/store';
const Transportation: React.FC = () => {
// 自动推导toolConfigState 类型
const toolConfigState = useSelector((s) => s.toolConfigStandard);
const dispatch = useDispatch();
dispatch({
// 校验dispatch type是否正确
type: 'toolConfigStandard/updateStateData',
// 校验payload参数类型是否正确
payload: {
type: 'addAndEditModal',
value: { type: 'edit', show: true, data: row },
},
});
}
2.6.4 其他注意事项
2.6.4.1 UI兼容性
我们还有较多网点使用的49版本的chrome,因此对于大版本迭代,提测后,都需要自查 chrome 49 版本的兼容性。
2.6.4.2 空值兼容
对于后端返回的需要前端有使用特定数据类型方法的值都需要做类型校验,如对数组类型的map方法等。对于嵌套对象的key值读取操作也需要做非空判断。
2.6.4.3 防连击处理
经常因为网络等问题,用户多次点击,导致产生不必要的数据,影响后续操作。
防连击具体场景:
①数据提交(用户填写数据后,验证完毕后,将数据提交给后端)
②登录验证(用户填写完信息,验证完用户身份后,登录操作)
③单页面的多个数据提交按钮(某个页面上有多个表单提交的需求,这时候设置点击的开关需要区分)
防连击的几种处理方式:
①固定时间,比如固定2秒的时间,两秒之后方可进行下一次点击【固定时间不是很灵活、看需求】
②等待返回结果之后方可进行第二次点击【若是没有返回结果的话,用户就无法进行点击了】
③等待返回结果,设置开关可进行第二次点击,与此同时设置定时器,超过5秒或一个固定时间将开关放开【防止没有返回结果的情况】
3 协作规范
3.1 协作流程规范
前后端团队经过长期的合作,一般可以总结出一条对于双方开发效率最优的协作流程. 将这个落实为规范,后面的团队成员都遵循这个步调进行协作。
一个典型的前后端协作流程如下:
- 需求分析。参与者一般有前后端、测试、以及产品. 由产品主持,对需求进行宣贯,接受开发和测试的反馈,确保大家对需求有一致的认知。
- 前后端开发讨论。讨论应用的一些开发设计,沟通技术点、难点、以及分工问题。
- 设计接口文档。可以由前后端一起设计;或者由后端设计、前端确认是否符合要求。
- 并行开发。前后端并行开发,在这个阶段,前端可以先实现静态页面; 或者根据接口文档对接口进行Mock, 来模拟对接后端接口。
- 在联调之前,要求后端做好接口测试。
- 真实环境联调。前端将接口请求代理到后端服务,进行真实环境联调。
3.2 接口规范
明确数据类型、明确空值的意义、响应避免冗余的嵌套
3.3 接口文档规范
后端通过接口文档向前端暴露接口相关的信息。通常需要包含这些信息:
- 版本号
- 文档描述
- 服务的入口. 例如基本路径
- 测试服务器. 可选
- 简单使用示例
- 安全和认证
- 具体接口定义
7.1 方法名称或者URL
7.2 方法描述
7.3 请求参数及其描述,必须说明类型(数据类型、是否可选等)
7.4 响应参数及其描述, 必须说明类型(数据类型、是否可选等)
7.5 可能的异常情况、错误代码、以及描述
7.6 请求示例,可选