# 编码风格
这份文档仅供统一项目编码风格
# 基本概念
实体(Entities),实体用于封装企业范围的业务规则。实体可以是拥有方法的对象,也可以是数据结构和函数的集合。如果没有企业,只是单个应用,那么实体就是应用里的业务对象。这些对象封装了最通用和高层的业务规则,极少会受到外部变化的影响。任何操作层面的改动都不会影响到这一层。
用例(Use Cases),用例是特定于应用的业务逻辑,一般用来完成用户的某个操作。用例协调数据流向或者流出实体层,并且在此过程中通过执行实体的业务规则来达成用例的目标。用例层的改动不会影响到内部的实体层,同时也不会受外层的改动影响,比如数据库、UI 和框架的变动。只有而且应当应用的操作发生变化的时候,用例层的代码才随之修改。
接口适配器(Interface Adapters) ,接口适配器层的主要作用是转换数据,数据从最适合内部用例层和实体层的结构转换成适合外层(比如数据持久化框架)的结构。反之,来自于外部服务的数据也会在这层转换为内层需要的结构。
# 目录即分层的具体实践
---- example
---- mock
---- public
---- src---- api
---- assets
---- components
---- directives
---- i18n
---- lib
---- router
---- store
---- utils
---- views---- login
---- __tests__
---- adapters
---- entity
---- interface
index.vue
- 具体业务包含自己的适配器,实体,单元测试的目录
- 实体的目录包含简单的数据模型,核心的业务逻辑
- 适配器做一些数据的转换
- .vue 做为用例层,编写用户行为的一些方法,其余的都只负责调用ts文件的方法。
这边与TS的整合,.vue 文件不使用vue-class-component来达到class的写法,保留之前的写法。
因为这种写法本身觉得绕了很多弯,是2.x为TS的支持补丁方式.目前来说,ts + vue 当然可用,
但是依然相当多的问题,没法如丝般顺滑的相互配合。尤大大他们完全打算通过 vue3 去彻底解决于 ts 共用的的问题,
不打算优化一下 2.x 当前存在的问题,那当下来说,vue文件我就不是很想用。(vue-class-component.js 通过gzip 有3.8KB, 源文件大小 11KB。vue-property-decorator.js 通过gzip 有2.4KB ,源文件大小9KB)(3.0兼容性到IE11。废弃支持class)
.vue文件lang=ts未解决的问题?
jsx Duplicate declaration "h"
其他 any/@ts-ignore
# 文件名文件结构
1.文件名全部为小写或包含-
2.单文件组件的顶级元素的顺序 template,script,style
3.有需要的生命周期按照被调用的顺序书写
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- activated
- deactivated
- beforeDestroy
- destroyed
<template>
</template>
<script>
export default {
data() {
return {
};
},
watch: {
},
created() {
},
beforeDestroy() {
},
methods: {
}
};
</script>
<style lang="scss">
</style>
# 命名规则
- css 采用 BEM 命名规则
eg:
.listCard 块,高层次组件
.listCard__title 元素
.listCard--feature 修饰符,表示不同状态
在选择器中,由以下三种符号来表示扩展的关系
- 中划线 :仅作为连字符使用,表示某个块或者某个子元素的多单词之间的连接记号。
__ 双下划线:双下划线用来连接块和块的子元素
_ 单下划线:单下划线用来描述一个块或者块的子元素的一种状态
# 块
一个块是设计或布局的一部分,它有具体且唯一地意义 ,要么是语义上的要么是视觉上的。
在大多数情况下,任何独立的页面元素(或复杂或简单)都可以被视作一个块。
它的HTML容器会有一个唯一的CSS类名,也就是这个块的名字。
针对块的CSS类名会加一些前缀( ui-),这些前缀在CSS中有类似 命名空间 的作用。
# 元素
块中的子元素是块的子元素,并且子元素的子元素在 bem 里也被认为是块的直接子元素。 一个块中元素的类名必须用父级块的名称作为前缀。
# 修饰符
一个“修饰符”可以理解为一个块的特定状态,标识着它持有一个特定的属性。
用一个例子来解释最好不过了。一个表示按钮的块默认有三个大小:小,中,大。
为了避免创建三个不同的块,最好是在块上加修饰符。
这个修饰符应该有个名字(比如:size )和值( small,normal 或者 big )。
# CSS Module
vue.config.js
css: {
modules: true,
},
开启了css module 配置,可以这么使用
<style lang="scss" module scoped>
.meeting-layout{
font-size:24px;
color: red;
}
</style>
script 中
this.$style['meeting-layout']
template 中
$style['meeting-layout']
在.tsx 文件中使用,需要先全局定义
declare module "*.scss" {
const content: any;
export default content;
}
import css from "./button.scss";
render() {
return (
<div class={css.button}>
</div>
);
}
- script 采用驼峰
类的命名:首字母必须采用大写的形式
变量的命名:首字母小写
# 样式变量
样式中有需要颜色的地方,统一定义到body下面。
给多彩提供脚本直接在当前替换能力。
兼容性问题:css-vars-ponyfill
document.body.style.setProperty('--bgColor','red');
eg :
body{
--bgColor: blue;
}
使用:
<style lang="scss" scoped>
.title{
color:var(--bgColor);
}
</style>
# 注释
尽量利用函数名称,参数或者返回值自身就足够传达信息。
短函数不需要太多描述,为只做一件事的短函数选个好名字,比写函数头注释要好。
好注释
1.对意图解释的注释
2.用于警告其他程序员会出现某种后果的注释
3.TODO 注释
写注释的常见动机之一是糟糕代码的存在,与其花时间编写解释你搞出的
糟糕的代码的注释,不如花时间清洁代码。如果编程语言足够有表达里,就不那么需要注释
eg:
// Async edge case fix requires storing an event listener's attach timestamp.
let _getNow: () => number = Date.now
这段代码第一反应让人觉得没有必要,但加了上面的解释,达到认同。
这段代码从命名完全看不出想做什么,为什么这么做,作者的解释,让阅读代码者更快地理解。
function createInvoker(
initialValue: any,
instance: ComponentInternalInstance | null
) {
const invoker: Invoker = (e: Event) => {
// async edge case #6566: inner click event triggers patch, event handler
// attached to outer element during patch, and triggered again. This
// happens because browsers fire microtask ticks between event propagation.
// the solution is simple: we save the timestamp when a handler is attached,
// and the handler would only fire if the event passed to it was fired
// AFTER it was attached.
if (e.timeStamp >= invoker.lastUpdated - 1) {
const args = [e]
const value = invoker.value
if (isArray(value)) {
for (let i = 0; i < value.length; i++) {
callWithAsyncErrorHandling(
value[i],
instance,
ErrorCodes.NATIVE_EVENT_HANDLER,
args
)
}
} else {
callWithAsyncErrorHandling(
value,
instance,
ErrorCodes.NATIVE_EVENT_HANDLER,
args
)
}
}
}
invoker.value = initialValue
initialValue.invoker = invoker
invoker.lastUpdated = getNow()
return invoker
}
# 函数
函数的第一个规则是要短小,第二条规则是只做一件事。
# 项目 README
- 运行命令
- 线上的示例或者最后的运行环境
- 相关的文档链接
- 相关人员的联系方式或群
- 代码组织结构图
# 开发前可考虑的事情
- 是否满足最小复杂度
- 设计时为维护工作的程序员考虑
- 信息隐藏: 这类,方法,组件该隐藏什么
- 找出容易改变的区域做划分
- 考虑潜在的变化,让这些变化的影响或范围与发生该变化的可能性成比
- 如何便于测试
- 层次性: 比如当需要调用设计不佳的旧代码的时候,就要编写一层同旧
代码交互的一层,这一层要隐藏代码的低劣质量,同时提供一组一致的服务。 - 参考常用的设计模式形成类似的解决方案
# OOP的设计步骤
- 辨识对象及其属性(方法和数据)
- 确定可以对各个对象进行的操作
- 确定各个对象能对其他对象进行的操作
- 确定对象的哪些部分对其他对象可见,哪些部分而已是public,哪些是private
- 定义每个对象的公开接口,即向其他对象暴露的数据及方法
# 测试驱动开发的具体实践
- 1.先编写伪代码,只有方法名以及流程
- 2.编写测试用例,描述功能点
- 3.交叉编写模块测试用例
- 4.具体逻辑开发
先编写测试,我们就能设身处地从代码使用者的角度考虑问题。
这种方式确保我们设计的每一个函数都由一系列测试驱动。
编写验证可考虑的类型: - 正向测试: 当前置条件满足时,验证代码的结果确实符合预期。
- 反向测试: 当前置条件或输入不符合要求时,代码能优雅地进行处理。
- 异常测试: 代码在应该抛出异常的地方正确地抛出异常。
jest官网 (opens new window)
import { PbxAPI } from '../entity/pbx';
import { transformToUse } from '../adapters/pbx';
test('测试构建函数对数据的限制', ()=> {
const pbxWrong = new PbxAPI(Object.create({id:''})).params;
expect(pbxWrong).toEqual({username:'',code:''});
const pbxRight = new PbxAPI().params;
expect(pbxRight).toEqual({username:'',code:''});
})
test('测试接口转换的结果', ()=> {
const data = transformToUse({id:'xxx'});
expect(data).toEqual({username:'',code:''});
})
其实最后会发现,最好写测试的就是无副作用的函数,只需要考虑,输入,输出。
# Typescript 运用
1.interface ,type
使用接口来确保类拥有的指定结构,或者约束对象。
接口好比一个协议,当我们签署它的时候,必须遵守他的规则。
interface ChinaMobile {
name: string;
website: string;
}
interface ChinaMobileList {
// 动态属性
[phone: string]: ChinaMobile
}
2.enum
旧版:
const typeMach = {
image: 'browse_original_image',
file: 'down_file'
}
改版:
enum typeMach {
image = 'browse_original_image',
file = 'down_file'
}
3.class,extends
在 .jsx 文件里,泛型可能会被当做 jsx 标签
const toArray = <T>(element: T) => [element]; // Error in .jsx file.
修改方式
const toArray = <T extends {}>(element: T) => [element]; // No errors.
4.namespace
命名空间,又称内部模块,用于组织一些具有某些内在联系的特性和对象,使代码结构更清晰。
单例模式
namespace Singleton {
export function someMethod() {}
}
Singleton.someMethod();
5.decorators (装饰者模式+切面编程,reflect-metadata)
tsconfig.json
compilerOptions:{
"experimentalDecorators": true
}
面向切面编程主要用于抽离与核心业务逻辑无关的功能,如日志统计、埋点、异常处理等等,
可以提高业务模块功能的纯净度与被分离模块的复用性。
装饰器用来为元素添加一些额外的逻辑或者元数据。
装饰器:类,属性,方法,参数
类装饰器:接受一个类构造函数作为参数的函数
方法装饰器:接受属性的对象,属性名和一个可选的参数
属性装饰器: 接受属性的对象和属性的属性名
参数装饰器: 接受被装饰参数的方法的对象,方法的名字和参数在参数列表中的索引。
reflect-metadata:反射元数据 API
元数据是指程序本身的信息数据,元数据存储在程序集中;
反射就是在运行时动态获取一个对象的一切信息:方法/属性等等。
参数的提交的各种处理,校验也可以用这种方式
6.泛型 T ,允许使用的时候定义类型(通用函数的考虑)
7.Omit
有时候我们需要复用一个类型,
但是又不需要此类型内的全部属性,
因此需要剔除某些属性 -- Omit
# 定制化构想
目标:
1.减少if
2.不用后端配合
思路:
利用国际key在当前模块相对唯一,用脚本依据源码生成一份Map文件,并把
把flag(目录+国际key+标签)生成自定义节点,去管理页面元素。
提供管理界面操作页面元素,更新Map文件之后重新执行打包运行,生成定制的代码。
如果不是简单的元素有无差异,而是功能上的不同,提供差异模块替换的可选。
在使用webcomponet之前需要先安装
npm install babel-plugin-transform-custom-element-classes
.babelrc
"plugins": [
"transform-runtime",
"transform-custom-element-classes"
],
class CustomClose extends HTMLElement {
triple=null;
constructor() {
super();
this.addEventListener('click', e => {
if(e.target.className === 'ripple') {
this.parentElement.removeChild(this);
this.triple = null;
}
});
this.addEventListener('mouseover',e=>{
this.toggleRipple();
});
}
toggleRipple() {
if(this.triple) {
return;
}
let div = document.createElement('div');
div.classList.add('ripple');
div.innerHTML = 'x';
this.triple = div;
this.appendChild(this.triple);
}
}
window.customElements.define('nb-close', CustomClose);
<style>
nb-close{
cursor: pointer;
border: 1px solid red;
border-radius: 5px;
}
.ripple{
border: 1px solid red;
display: inline-block;
width: 15px;
height: 15px;
line-height: 12px;
color:red;
text-align: center;
border-radius: 50%;
}
</style>
自定义loader,用于去正则匹配生成节点
module.exports = function (source) {
try {
let result = source.match(/\<template\>[\s\S]*\<\/template\>/gmi).map((it)=>{
return it.replace(/(\{\{\$t\(.*\)\}\})/g,'<nb-close>$1</nb-close>');
}).join('');
return source.replace(/\<template\>([\s\S]*)\<\/template\>/gmi,result);
} catch (error) {
return source;
}
}
webpack 的配置修改
{
test: /\.vue$/,
use:[
{
loader: 'vue-loader',
options: vueLoaderConfig
}, {
loader: path.resolve(__dirname, './loader.js')
}
]
},
# 领域驱动设计
首先DDD,无关技术,而是理解,发现业务价值。
DDD关注的是业务战略,业务战略的发展方向。
开发者不应该只是热衷于技术,而应该将眼界放得更宽。
不管使用什么技术,我们的目的都是提供业务价值。
而业务价值最直观的就是用户体验。
DDD的开发步骤和测试驱动开发的唯一区别是:
‘精通业务’的人(目前这个角色的既定于产品,测试),
可以通过阅读测试代码来检验领域对象是否满足业务需求。
那么测试代码就需要有一定的业务表达力。
这种表达力就需要一种‘通用性语言’的制定,这里依旧是个问号。