足球前端规范
一、Git提交注释部分(git comment -m ‘?’),建议规范起来。
原因
首先比较重要的一点就是,当出现代码合并冲突问题或者代码合并合漏的情况,可以根据注释模板标识来进行查找,具体的人具体的提交版本可以一目了然,不必打扰其他相关的人;其次就是分支回滚时,可以清楚的看出回滚位置,做到很便捷的回滚;最后一点,能清晰的反应开发者这一个周都做了什么,更利于开发者回顾这一个周业务或者技术开发,再现实点儿的意义就是便于写周报,😄
为此,我在我们足球业务组内部制定了一套注释草案,请大家建议、提意见或者查漏补缺:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//我们以type:description形式来写注释,中间的:表达的是在某一种类型下,提交的某某某版本注释
开发业务 'business:xxx'
修改bug 'fix:xxx'
优化 'optimize:xxx’
追加文档 'doc:xxx'
生态搭建 'build:xxx’
样式修改 'style:xxx'
重写 'rewrite:xxx'
单元测试 'test:xxx'
//这里面是有交集冲突的,比如:
//如果只改了样式,而没有动任何其他的配置,那就是style:xxx
//但如果改了样式又改了其他的部分,是要按照当时业务场景来看的,可能是business、fix、optimize、rewrite等等
//以此类推,就是当单纯改了某一个分类,就按照分类算,但如果改了不同的分类,那就按照业务场景来看。
//有的小伙伴英语不好,多学几个单词感觉费劲,咱也有中文版:
开发业务 '业务:xxx'
修改bug '问题:xxx'
优化 '优化:xxx'
追加文档 '文档:xxx'
生态搭建 '生态:xxx'
样式修改 '样式:xxx'
重写 '重写:xxx'
单元测试 '测试:xxx'
二、Jenkins发布系统步骤
原因
也就是为什么要有这个系统。我们的前端打包之后的文件实际上就是一些静态资源,开发人员每一次都要打包,而且还要上传到gitlab上面,静态资源是不应该放到gitlab代码管理库里面去的,有了这个发布系统,直接交给流程机器人进行打包处理,并自动发布到SIT、STG和PRD的环境上面,减少了人工的打包以及手动拉到服务器上面的时间,且静态资源也是分发到OSS静态资源服务器上面,可谓物归其所地。
步骤
打开http://ticket.hupu.io/desk/这个发布工单的系统,如果是创建一个新的项目应用,无论是后台还是前端,选择”新应用申请”,申请一个新项目应用,申请就会耗一些时间,运维老大会对你申请的这个应用提出意见,比如应用名字等等,按照规范改,之后要找不同的人(王节红(运维老大)跟他说你这个应用的gitlab所在地址等等确认、邰鹏(拓海CBA官网的运维负责人)的确认),最后新应用上线完成。
之后,需要进行前端配置,由于OSS只放html文件,打包后的静态文件(CSS、JS等),需要放入W1服务器,OSS不支持CDN加速,而W1服务器实际上就是一个CDN服务器,所以需要配置webpack中的publicPath,访问静态文件需要使用w1的域名,路径为:http://w1.hoopchina.com.cn/games/static/项目名。
缺点
当然也存在比较多的缺点,和前端小伙伴讨论的过程中,发现这个发布系统,只能单线程发布,对于多线程提交工单,并不支持,且进行提交工单的分支必须为release,所以当多人项目发布不同的功能点时,由于提交的时间点不同,当一人提交上去,另外的小伙伴就只能等着,等待这人的功能测试完全上线,才可以提交第二个工单,这样效率实际上是大大降低的。
优化建议
和小伙伴商量之后,希望发布工单系统可以支持多分支提交工单,不只依赖于release分支,可以选择多个分支并入release分支,既可以勾选并入,也可以消除勾选移除,发布时,先检测是否存在冲突,解决完冲突之后,再发布至release,release分支发至sit和stg环境,待测试测完之后,再选择prd发布至线上并合入master分支(想法gitlab上的项目默认接入ci/cd,可以暴露gitlab的菜单选项,针对于某一个项目自定义自己的发布工单系统)。
三、表单校验插件能在我们业务前端内使用
原因
抛弃之前的if else冗长的判断逻辑,将其集成插件使用,减少项目代码的冗余,提高规则插件的可复用性,此插件符合设计模式单一职责以及开放封闭的原则,使用策略模式来编写。
原理
此插件是以校验规则为基则,支持添加一或多个校验变量对应一或多个校验规则,来实现一对一、多对一、一对多和多对多的校验方式,数据类型支持数组或者是字符串,校验规则可以自定义,且对出现异常或者问题时的提示语,也支持type:提示异常语的形式来弹出或者飞入等自定义形式提示异常。
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import verify from "../utils/verify";
const {phone, code, password, passwordConfirm} = this.state;
return verify.addRule([
phone,
code,
password,
passwordConfirm
], [{
rule: 'isEmpty',
errMsg: [
'warning:请输入手机号',
'warning:请输入正确的验证码',
'warning:请输入6-16位密码',
'warning:请输入6-16位确认密码'
]
}]).addRule(
phone, [{
rule: 'isPhone:^1[3|4|5|6|7|8|9]\\d{9}$',
errMsg: [
'warning:输入的手机号不符合标准'
]
}]).addRule(
password,
[{
rule: 'passwordBit:6',
errMsg: [
'warning:请输入6-16位密码'
]
}]
).addRule(
password,
[{
rule: 'passwordReg:^[\\w]+$',
errMsg: [
'warning:请输入数字或者字母'
]
}]
).addRule(
password, [{
rule: `isConfirm:${passwordConfirm}`,
errMsg: [
'warning:两次输入得密码不一致'
]
}]).execute();
源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
//表单校验插件
let verify = (function () {
const ruleMap = new WeakMap();
const rulesList = {
//校验输入框是否为空
isEmpty(val, type, errMsg) {
if ((!val && val !== 0) || val.length <= 0) {
return {
type,
errMsg
};
}
return false;
},
//校验密码和确认密码是否相同
isConfirm(val, confirmVal, type, errMsg) {
if (val !== confirmVal) {
return {
type,
errMsg
};
}
return false;
},
//校验密码长度
passwordBit(val, length, type, errMsg) {
if (val.length < length) {
return {
type,
errMsg
};
}
return false;
},
//校验密码是否是只有数字或者字母
passwordReg(val, reg, type, errMsg) {
let _reg = new RegExp(reg, 'g');
if(!_reg.test(val)) {
return {
type,
errMsg
};
}
return false;
},
//校验手机号码
isPhone(val, reg, type, errMsg) {
let _reg = new RegExp(reg);
if(!_reg.test(val)) {
return {
type,
errMsg
};
}
return false;
}
};
class Verify {
constructor() {
ruleMap.set(this, []);
}
/**
* 添加校验规则
* @param vals
* @param rules
* @returns {Verify}
*/
addRule = (vals, rules) => {
const mapList = ruleMap.get(this);
vals = (Object.prototype.toString.call(vals) === "[object Array]") ? vals : [vals];
for (let [key, val] of vals.entries()) {
rules.forEach(ruleItem => {
mapList.push(() => {
let {rule, errMsg} = ruleItem;
const operation = rule.split(":");
errMsg = errMsg instanceof Array ? errMsg : [errMsg];
const currentErrMsg = errMsg[key];
const method = operation.shift();
operation.unshift(val);
operation.push(...currentErrMsg.split(":"));
return rulesList[method](...operation);
});
});
}
ruleMap.set(this, mapList);
return this;
};
/**
* 进行校验
* @returns {boolean|*}
*/
execute = () => {
const mapList = ruleMap.get(this);
let msg;
for (let mapVal of mapList.values()) {
if ((msg = mapVal())) {
this.clear();
return msg;
}
}
this.clear();
return false;
};
/**
* 将校验规则的方法都清除掉
*/
clear = () => {
ruleMap.set(this, []);
};
}
return new Verify();
})();
export default verify;
四、目录结构
原因
建立一个生态,最重要的就是目录结构,一个好的目录结构,能够使开发的效率变高,省去过多的查找目录、路径和文件的时间,而开发一个生态或者一个项目,往往是要和业务连接起来的,所以目录结构有必要与业务紧密相连。
详情
我们足球业务项目,建议采用umi的那种目录结构,配合业务界面的组件,无论是业务组件(不包含公用组件)、业务样式、私有业务样式变量,还是redux的行为action、触发redux action行为产生数据处理并返回的reducer、私有静态常量、私有工具,都放入和业务界面组件同一个目录路径下,这样在开发某一个界面的业务时,就不需要返回上一级甚至于根目录下寻找这些业务可能必备的样式、组件、工具、action、reducer和静态变量。
而公有的部分则放入到盛装业务界面组件根路径的外部,比如静态图片、文字字体、音频和视频资源、公有的样式、公有的样式变量、公有的工具和组件以及公有的静态常量,都放入外部,分目录盛装,名字叫啥就不建议了,最起码要语义清晰且常用,让人能够比较容易的理解。
五、react state和props的使用
概括
state花心,props专一 ; state如此善变,props始终如一
使用
在还没有redux统一管理数据的年代,我们一般使用state来承接变量,在一个组件的内部,我们一般使用state来控制这个组件的喜怒哀乐和言行举止,控制器呢,就是setState;但是当组件套用时,父子关系呈现出来,父组件发现没办法传授和教育子组件一些经验和知识,因为组件都是有血有肉有自己意识的,所以只能强制,父组件通过传入自己的state,来改变子组件的一些坏习惯,俗话说得好,“小树不修不直溜,人不修理哏啾啾”,自然在子组件还没有成熟时,传入子组件内部的父组件state(也就是子组件props),子组件是无法修改的,只能听之任之,接受洗礼,但是当子组件变成熟,父组件变老,子组件想要改变父组件的需求也就自然显示出来,那如何能修改呢,只能劝父组件授权给他,以为父组件好的名义,父组件听到了心里,觉得”儿啊,长大了,懂事了”,直接给予子组件一个方法,这个方法行为直接可以改变父组件的state生活行为,而这个方法是子组件控制的。
故state承接的是变量,而props只是state在父子传递state时的桥梁,桥梁是不可妄动的。
建议state中不要放入常量,如果嫌某常量只是在本组件内使用或者嫌重新建一个静态常量文件麻烦,那就直接使用组件内部的this.constants = {number: ‘这是常量’};或者不放入到react组件内,直接在文件顶部加一个const constants = {number: ‘这是常量’};也是可以的。
六、react事件
定义
事件往往代表一个对象、一个组件和一套工具的行为,而react事件的演化从初始阶段的bind显示硬绑定,至中期的箭头函数,直至现阶段比较好的箭头函数配合data-propsType属性的方式。
前期bind硬绑定
在前期,我们既希望事件函数的this指向React组件对象本身,又希望可以传递某些数据状态,且this不要指向事件所绑定的元素dom或者react虚拟dom,在这时候,我们选择了bind硬绑定,事件.bind(this, params)酱紫,但是发现这样大抵是为内存泄漏埋下了隐患。
我们先看一下模拟bind的js源代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Function.prototype.bind = function(context) {
let _this = this,
fBind,
args = [...arguments];
args.shift();
function F() {}
fBind = function () {
const newArgs = [...args, ...arguments];
return _this.apply(this instanceof fBind ? this : context, newArgs);
};
//这里让返回的函数的隐式原型指向其绑定的事件函数的显式原型
F.prototype = _this.prototype;
fBind.prototype = new F();
return fBind;
};
这样就是一个完整的bind硬绑定的函数,基于Function函数对象的原型链上面,但是大家可以看到,bind硬绑定形成了一个闭包,fBind的这个函数对象,在异步机制中的内存当中是一直存在的。那就意味着,我硬绑定多个事件,就会形成多个闭包,这样迟早会形成内存泄漏,实在是不可取的一个方式。
中期的箭头函数与现阶段的箭头函数配合data-propsType属性
对于箭头函数,我们极为熟悉,不能改变其this的指向、无arguments类参数数组、不能使用new绑定构造调用和引用关联以及不能使用super继承。这样既写法简单,this的指向又解决了,指向React组件对象本身,且不指向事件所绑定的元素dom或者react虚拟dom。那么参数如何传入呢,当然你如果不嫌麻烦,可以这样:
1
2
3
4
5
6
7
<div onClick={(e) => {
const type = 'me';
//这个就是你定义的箭头函数
onChangeHandler(type, e);
}}>
来吧,点击
</div>
但是太过麻烦,每次都要内部一个箭头函数,在外面又要再定义一个封装的箭头函数,感觉有点儿烦。所以我们可以利用自定义属性的方法,来解决这个问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Demo extends React.Component{
onChangeHandler = (e) => {
const {target} = e || {};
const type = target.getAttribute('data-type');
console.log(type);
};
render() {
const {onChangeHandler} = this;
return (
<div
data-type='me'
onClick={onChangeHandler}>
来吧,点击
</div>
);
}
}
这种箭头函数配合data-propsType属性的方式,简易实用,是现阶段比较好的事件实现过程。
七、添加注释
详情
注释是体现一个好的开发人员素养的标识,好且有效的注释在多人团队同时开发过程中的垫脚石,所以建议小伙伴们,无论是在业务逻辑还是技术逻辑比较复杂的部分,做好详细的注释,复杂技术逻辑只要讲清楚代码逻辑即可,而复杂业务逻辑,就需要讲业务的背景讲清楚,即是什么、为什么详细的表达明白。