起因
当我们的项目越来越大的时候,Git
冲突是团队协作中令人非常苦恼的事情,不仅仅浪费了我们时间,而且很容易解决冲突的时候出现问题。
我能想到的就是模块细分化,每个人都负责自己相关的模块,这样开发者之间的代码就不会相互影响,也就不会有代码的冲突。但多人协作项目中公共的代码必不可少,比如我们常见的公共变量文件 constant.js
,并且大部分的冲突来源于此。
基于此,Leader
提出了另外一个解决方案——控制书写代码的顺序
为什么顺序那么重要
对于这个问题,我们要先清楚,为什么会有代码冲突?
因为我们改了同一个文件中同一行的代码
举个例子,比如我们常量定义中有如下:
export const Employees = {
Andy: 'I can sing',
Oliver: 'I can run',
Ivan: 'I can rap'
};
同事 A 和我都要改到 Employees
这个常量,他添加 Brad: 'I am Brad'
, 我添加了 Patrick: 'I am Patrick'
,我们都加到了该变量的后面,就会导致冲突,类似如下
@@@ -1,4 -1,4 +1,8 @@@
Andy: 'I can sing',
Oliver: 'I can run',
Ivan: 'I can rap',
<<<<<<< HEAD
+Brad: 'I am Brad',
=======
+ Patrick: I am Patrick,
>>>>>>>
但假如我们两个都是有顺序的会变成怎样呢?
首先调整之后文件的顺序如下:
export const Employees = {
Andy: 'I can sing',
Ivan: 'I can rap',
Oliver: 'I can run'
};
同事 A 添加代码如下:
export const Employees = {
Andy: 'I can sing',
+ Brad: 'I am Brad',
Ivan: 'I can rap',
Oliver: 'I can run'
};
我添加代码如下:
export const Employees = {
Andy: 'I can sing',
Ivan: 'I can rap',
Oliver: 'I can run',
+ Patrick: 'I am Patrick'
};
这个时候,当我们合代码的时候就没有冲突了
通过工具去排序
知道了顺序对于我们避免解决冲突的重要性,那么接下来就是要执行了,如果要求同事们都时刻遵循写代码的顺序,显然不太合理,我们打算用工具去执行。
我用 vue-cli
演示下
具体步骤如下:
时机:在 git commit
之前,可以使用 git hooks
做到
在 package.json 中加入如下代码,意思是在 git commit
之前会去执行 node ./auto-fix/index.js
以及 git add
命令
"lint-staged": {
"*.{js}": [
"node ./auto-fix/index.js",
"git add"
]
},
"gitHooks": {
"pre-commit": "lint-staged"
},
如果发现没有触发的话,可以看下是不是没有这两个包 lint-staged
和 yorkie
。其中 yorkie 是 尤大大 folk husky,它俩功能是一样的,都是生成一些 git hooks
文件,读取项目中package.json
的相关配置项去执行一些命令,区别是尤大做了一些逻辑和配置上的改动
npm i lint-staged --save-dev
npm i yorkie --save-dev
读取和排序:寻找指定目录下的文件(下面示例为 src/constant
目录下),匹配出文件中的对象,针对对象排序
这里排序的策略是先针对 value
值进行排序,如果 value
值相同,再针对 key
值排序
写入:将排好序的文件写回原文件
提示:成功或者失败的提示
新建文件 auto-fix/index.js
, 代码如下:
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
function resolve(p) {
return path.join(__dirname, '..', p);
}
const objRegex = /\{[^}\/\/]*\}/g;
function fileDisplay(filePath) {
let file = resolve(filePath);
let fileList = fs.readdirSync(file, 'utf8');
function compare() {
return function ([key1, value1],[key2, value2]) {
key1 = Number.isNaN(Number(key1)) ? key1.toLowerCase() : key1;
key2 = Number.isNaN(Number(key2)) ? key2.toLowerCase() : key2;
value1 = Number.isNaN(Number(value1)) ? value1.toLowerCase() : value1;
value2 = Number.isNaN(Number(value2)) ? value2.toLowerCase() : value2;
if (value1 === value2) {
return key1 < key2 ? -1 : key1 > key2 ? 1 : 0; // 升序
} else {
return value1 < value2 ? -1 : value1 > value2 ? 1 : 0;
}
}
}
// 读取文件
fileList.forEach(filename => {
let fileDir = path.join(filePath, filename);
console.log(chalk.cyan(`Auto fix ${fileDir}`))
let fileContents = fs.readFileSync(fileDir, 'utf8');
function sortObj(item) {
item = item.replace(/(\S+):/g,"\"$1\":")
.replace(/'/g, '"');
let arr = [];
item = JSON.parse(item);
arr = Object.entries(item);
// 排序
arr = arr.sort(compare());
let tempObj = {};
// 将排序好的数组拼接成对象
tempObj = arr.reduce((_sortedObj, [key, val]) => ({
..._sortedObj,
[key]: val
}), {});
let tempStr = JSON.stringify(tempObj, null, 2);
// 去掉 key 值的双引号
tempStr = tempStr.replace(/"/g, "").replace(/\: /g,"\: \'").replace(/\,/g,"\'\,").replace(/\n\}/g,"\'\n\}");
return tempStr;
}
// 匹配对象并排序并替换
fileContents = fileContents.replace(objRegex, function(match) {
return sortObj(match)
});
// 输出到文件中
fs.writeFileSync(fileDir, fileContents, 'utf8');
});
}
try {
fileDisplay('src/constant');
console.log(chalk.green(`Auto fix complete`))
} catch (e) {
console.log(e);
console.log(chalk.red(`Auto fix Error, You can check the the problem in the auto-fix/index.js directory`))
throw new Error(`Auto fix Error, You can check the the problem in the auto-fix/index.js directory`)
}
结果
Gif
演示一下
探索:对象属性遍历有顺序么?
我们知道,数组遍历是有顺序的,也经常说对对象遍历是无序的。
但实际上我理解这个“无序”指的只是不会按照属性排列前后的顺序而已,对象属性遍历本身是有自己的一套规则的。
可以实验一下
let obj = { [Symbol()]:0, b:0, 10:0, 2:0, a:0 };
// for...in循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)
// 输出 2 10 b a
for (let key in obj) {
console.log(key)
}
// Object.keys返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)的键名。
// [ '2', '10', 'b', 'a' ]
console.log(Object.keys(obj));
// Object.getOwnPropertyNames返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但是包括不可枚举属性)的键名。
// [ '2', '10', 'b', 'a' ]
console.log(Object.getOwnPropertyNames(obj));
// Object.getOwnPropertySymbols返回一个数组,包含对象自身的所有 Symbol 属性的键名。
// [ Symbol() ]
console.log(Object.getOwnPropertySymbols(obj));
// Reflect.ownKeys返回一个数组,包含对象自身的(不含继承的)所有键名,不管键名是 Symbol 或字符串,也不管是否可枚举。
// [ '2', '10', 'b', 'a', Symbol() ]
console.log(Reflect.ownKeys(obj));
总结如下:
- 首先遍历所有数值键,按照数值升序排列
- 其次遍历所有字符串键,按照加入时间升序排列
- 最后遍历所有 Symbol 键,按照加入时间升序排列
回顾实现——也存在类似问题
上面的实现中,我是先根据属性的 value
值排序,如果 value
值相同再根据 key
值排序。排序后得到一个有顺序的二维数组,类似如下:
[["Andy","I am Andy"],["Gopal","I am Gopal"],["Ivan","I am Ivan"],["Oliver","I am Oliver"],["Patrick","I am Patrick"]]
然后遍历数组依次写入对象中,这样看起来似乎是没有问题,结果却是也是!但假如 key 值是 number
类型呢?比如类似如下:
export const Employees = {
Andy: 'I am Andy',
Gopal: 'I am Gopal',
Ivan: 'I am Ivan',
1: 'Z',
2: 'A',
3: 'J',
Oliver: 'I am Oliver',
Patrick: 'I am Patrick'
};
按照预期,按照 value 排序,应该得到
export const Employees = {
2: 'A',
Andy: 'I am Andy',
Gopal: 'I am Gopal',
Ivan: 'I am Ivan',
Oliver: 'I am Oliver',
Patrick: 'I am Patrick'
3: 'J',
1: 'Z'
};
但事实上,我们得到了类似如下的结果:
export const Employees = {
1: 'Z',
2: 'A',
3: 'J',
Andy: 'I am Andy',
Gopal: 'I am Gopal',
Ivan: 'I am Ivan',
Oliver: 'I am Oliver',
Patrick: 'I am Patrick'
};
这个实际上就是跟我们上面提到的属性遍历规则有关,因为首先遍历所有数值键,按照数值升序排列。其次遍历所有字符串键,按照加入时间升序排列。
缺点
除了上面所说的属性顺序问题,如果你细读上面的代码,其实是会发现一些问题,也算是一些 TODO
项,如果有朋友解决了,欢迎给我提个 pr
- 目前只对简单的对象有用,嵌套的对象无效
- 对象中如果书写注释,将导致对象无法匹配到
- 对开发者的代码进行了更改,有可能开发者会有疑惑(这一点,我尽量使用提示去说明)
- 能避免大部分冲突,但实际上不能 100%
总结
本文记录了一次通过利用 git hooks
在代码提交之前给相关的代码排序,从而减少合代码时候的冲突,也探讨了一下 Js
对象属性遍历属性的问题,希望对大家有所启发。
涉及到的 demo
已放 Github,有更好的想法可以提 PR
哈
原创不易,欢迎点赞评论~
往期优秀文章推荐
- 【webpack 性能优化】编译速度从 50S 到 7S
- 一个合格的中级前端工程师应该掌握的 20 个 Vue 技巧
- 【Vue进阶】——如何实现组件属性透传?
- 前端应该知道的 HTTP 知识【金九银十必备】
- 最强大的 CSS 布局 —— Grid 布局
- 如何用 Typescript 写一个完整的 Vue 应用程序
- 前端应该知道的web调试工具——whistle
网友评论