规范与每个团队和个人都是息息相关的,因为其影响的不只是只是代码的维护和理解成本,严重的时候是会影响成员开发的心情
一个团队的编码规范、git规范等,并没有绝对的最优解,心里要清楚明白没有银弹,规范是为了让团队统一,提高代码阅读性、降低代码维护成本等,本文是记录一些在项目code review中常见的规范,仅供参考
JS部分和渲染无关的数据
vue中data的数据默认便会进行双向数据绑定,若是将大量的和渲染无关的数据直接放置在data中,将会浪费双向数据绑定时所消耗的性能,将这些和渲染无关的数据进行抽离并配合Object.freeze进行处理
table中columns数据可以单独提取一个外部js文件作为配置文件,也可以在当前.vue文件中定义一个常量定义columns数据,因为无论如何都是固定且不会修改的数据,应该使用Object.freeze进行包裹,既可以提高性能还可以将固定的数据抽离,一些下拉框前端固定的数据也建议此操作
const columnList = Object.freeze([
{ title: '姓名', key: 'name', align: 'center' },
{ title: '性别', key: 'gender', align: 'center' }
])
需要注意的是 Object.freeze() 冻结的是值,这时仍然可以将变量的引用替换掉,还有确保数据不会变才可以使用这个语法,如果要对数据进行修改和交互,就不适合使用冻结了。
Modal框的控制
一个页面种通常会存在很多个不同功能的弹框,若是每一个弹框都设置一个对应的变量来控制其显示,则会导致变量数量比较冗余和命名困难,可以使用一个变量来控制同一页面中的所有Modal弹框的展示
比如某个页面中存在三个Modal弹框
// bad
// 每一个数据控制对应的Modal展示与隐藏
new Vue({
data() {
return {
modal1: false,
modal2: false,
modal3: false,
}
}
})
// good
// 当modalType为对应的值时 展示其对应的弹框
new Vue({
data() {
return {
modalType: '' // modalType值为 modal1,modal2,modal3
}
}
})
debounce使用
例如远程搜索时需要通过接口动态的获取数据,若是每次用户输入都接口请求,是浪费带宽和性能的
当一个按钮多次点击时会导致多次触发事件,可以结合场景是否立即执行immediate
<Select :remote-method="remoteMethod">
<Option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}}Option>
Select>
import {debounce} from 'lodash'
methods:{
remoteMethod:debounce(function (query) {
// to do ...
// this 的指向没有问题
}, 200),
}
图片
功能的开发过程中,图片的处理往往是比较容易被忽略的环节,也会在一定程度影响开发的效率和页面的性能
路由组件传参
在组件中使用$route会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
使用props将组件和路由解耦:
取代与$route的耦合
const User = {
template: 'User {{ $route.params.id }}'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
通过props解耦
这样你便可以在任何地方使用该组件,使得该组件更易于重用和测试。
const User = {
props: ['id'],
template: 'User {{ id }}'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
参考:路由组件传参
Vue生命周期
在父子组件中,掌握父子组件对应的生命周期钩子加载顺序可以让开发者在更合适的时候做适合的事情父组件
home
子组件
list
加载时父子组件的加载顺序
home beforeCreate --> home created --> home beforeMount --> list created --> list beforeMount --> list mounted
销毁时父子组件的销毁顺序
home beforeDestroy --> list beforeDestroy --> list destroyed --> home destroyed
实际开发过程中会遇到当子组件某个生命周期完成之后通知父组件,然后在父组件做对应的处理
emit up
// 子组件在对应的钩子中发布事件
created(){
this.$emit('done')
}
// 父组件订阅其方发
hook
通过@hook监听子组件的生命周期
Select优化
下拉框遍历时,需要注意options标签保持同一行,若是存在换行,会导致选中时的值存在多余的空白
<Select :remote-method="remoteMethod">
<Option v-for="item in temoteList" :value="item.value" :key="item.id">
{{item.label}}
Option>
Select>
需要将Options和下拉框的值保持在同一行
<Select :remote-method="remoteMethod">
<Option v-for="item in temoteList" :value="item.value" :key="item.id">{{item.label}}Option>
Select>
data数据层级
data数据具有数据层级结构,切勿过度扁平化或者嵌套层级过深,若是过度扁平化会导致数据命名空间冲突,参数传递和处理,若是层级嵌套过深也会导致vue数据劫持的时候递归层级过深,若是嵌套层级丧心病狂那种的,小心递归爆栈的问题。而且层级过深会导致数据操作和处理不便,获取数据做容错处理也比较繁琐。一般层级保持2-3层最好。
若是只有一层数据,过于扁平
{
name: '',
age: '',
gender: ''
}
导致处理不方便
// 作为接口参数传递
ajax({
this.name, this.age, this.gender
})
// 接口获取数据,批量处理
ajax().then(res => {
const {name, age, gender} = res.data
this.name = name
this.age = age
this.gender = gender
})
适当的层级结构不仅增加代码的维护和阅读性,还可以增加操作和处理的便捷性
{
person: { // 个人信息
name: '',
age: '',
gender: ''
}
}
可以针对person进行操作
// 作为接口参数传递
ajax(this.person)
// 接口获取数据,批量处理
ajax().then(res => {
const {name, age, gender} = res.data
this.$set(this, 'person', {name, age, gender})
})
策略模式
策略模式的使用,避免过多的if else判断,也可以替代简单逻辑的switch
const formatDemandItemType = (value) => {
switch (value) {
case 1:
return '基础'
case 2:
return '高级'
case 3:
return 'VIP'
}
}
// 策略模式
const formatDemandItemType2 = (value) => {
const obj = {
1: '基础',
2: '高级',
3: 'VIP',
}
return obj[value]
}
解构
解构赋值以及默认值,当解构的数量小于多少时适合直接解构并赋值默认值,数据是否进行相关的聚合处理
const {
naem = '',
age = 10,
gender = 'man'
} = res.data
// bad
this.name = name
this.age = age
this.gender = gender
// good
this.person = {
naem,
age,
gender
}
职责单一
任何时候尽量是的一个函数就做一件事情,而不是将各种逻辑全部耦合在一起,提高单个函数的复用性和可读性
每个页面都会在加载完成时进行数据的请求并展示到页面
created() {
this.init();
},
methods: {
// 将全部的请求行为聚合在init函数中
// 将每个请求单独拆分
init() {
this.getList1()
this.getList2()
},
getList1() {
// to do ...
},
getList2() {
// to do ...
}
}
v-bindHTML部分html书写
编写template模板时,属性过多时,是否换行
class="ba30-8fb0-c9ff-a82f icon-button go-up"
icon-left="keyboard_arrow_up"
v-tooltip="$t('org.vue.components.folder-explorer.toolbar.tooltips.parent-folder')"
@click="openParentFolder"
/>
实体使用
html中展示一些如,&等字符时,使用字符实体代替
<div>
> 1 & < 12
div>
<div>
> 1 & < 12
div>
CSS部分样式穿透
在开发中修改第三方组件样式是很常见,但由于scoped属性的样式隔离,可能需要去除scoped或是另起一个style。这些做法都会带来副作用(组件样式污染、不够优雅),样式穿透在css预处理器中使用才生效。
空格
适当的空格可以提升代码的阅读体验,显得更为优雅和美观
选择器后、属性值
.custom-style { // 选择器和{ 之间空格
margin: 0; // 属性值前
transform: scale(1.5, 2.2); // 逗号之后增加空格
}
换行
和html类型,当某行的属性很多,适当的换行可以提高阅读和美观
.custom-style{
// 可以在一次声明中定义一个或多个属性
background: background-clip
background-color
background-image
background-origin
background-position
background-repeat
background-size;
}
当一个规则包含多个选择器时,每个选择器声明必须独占一行,过长导致需要横向滚动阅读剩余的内容,应该尽量使得阅读顺序纵向化
.custom .header .title,
.other .header .title {
color: #f0f;
}
嵌套层级
浏览器在解析css时,是按照从右到左递归匹配的,过深的层级嵌套不仅影响性能,而且还会导致样式阅读性和代码维护性降低,一般层架控制在5层之内
双引号
属性选择器中的值必须用双引号包围,不允许使用单引号,也不允许不使用引号,html的属性值也是推荐使用双引号,js中使用单引号
.custom-style{
font-family: "PingFang SC", "STHeitiSC-Light";
}
属性顺序
同一 规则下的属性在书写时,应按功能进行分组。并以 Formatting Model(布局方式、位置) > Box Model(尺寸) > Typographic(文本相关) > Visual(视觉效果) 的顺序书写,以提高代码的可读性。
解释:
另外,为增加可读性,如果包含 content 属性,应放在属性的最前面。
Java使用JDBC或MyBatis框架向Oracle中插入XMLType数据
先来了解一下什么是XMLType类型。
XMLType是Oracle从9i开始特有的数据类型,是一个继承了Blob的强大存在,可以用来存储xml并提供了相当多的操作函数。理论上可以保存2G大小的数据。
那怎么样通过java来插入XMLType类型的数据呢?项目当中采用的是Mybatis,总是出现莫名的异常,都搞不清楚到底是Mybatis的问题还是jdbc本身的问题,所以打算一步步来,先搞定jdbc,再解决Mybatis。
JDBC
在折腾了半天之后,发现jdbc操作主要有3种方法:
一、在Java中把XMLType当作字符串String来用,具体创建XMLType的任务完全交给数据库:
String sql = "insert into xmltable (XML) values(sys.xmlType.createXML(?))"; String xmldata = ""; ps.setString(1, xmldata); ps.executeUpdate();
此方法会使数据库的压力偏大,因为此方法简单又不需要额外的依赖,在一开始采用此方法,但在实际使用过程中发现,在内容的长度超过4000左右的时候,会抛出:ORA-01461: can bind a LONG value only for insert into a LONG column 异常。一开始以为使用mybatis的原因,使用jdbc测试依然如此,使用诸多方法尝试无解。在项目中使用该大字段不可能只保存长度在4000以内的数据,这样使用varchar2足矣,所以该方法淘汰。
二、使用CLOB类型来操作。XMLType是继承了CLOB的存在,所以是可以通过CLOB来操作的。方法是在客户端创建好CLOB数据后传入数据库通过Oracle的XMLTYPE()函数来构造XMLType的值:
String sql = "insert into xmltable (XML) values(XMLType(?))"; String xmldata = ""; //通过conn创建CLOB CLOB tempClob = CLOB.createTemporary(connection, false, CLOB.DURATION_SESSION); //打开CLOB tempClob.open(CLOB.MODE_READWRITE); //获得writer Writer clobWriter = tempClob.setCharacterStream(100); //写入数据 clobWriter.write(xmldata); //刷新 clobWriter.flush(); //关闭writer clobWriter.close(); //关闭CLOB tempClob.close(); pst.setObject(1, tempClob);
此方法客户端和数据库同时承担了创建XMLType的任务,因此压力较平均,也没有超过长度的问题。但是在实际使用过程中又发现,xml的内容头部不能包含以下信息:
否则会抛出异常:
PI names starting with XML are reserved
先不说少了这个在以后处理xml内容包含中文时会不会遇到蛋疼的乱码问题,光是看着就让人感觉不爽,且需求上也要求保存,没办法,这个方法又行不通了。
三、使用Oracle提供的oracle.xdb.XMLType类,客户端创建XMLType后直接把对象传给数据库:
Connection conn = ... ;//获得Connection PreparedStatement ps = ...;//获得PreparedSatement String sql = "insert into xmltable (XML) values(?)"; String xmldata = ""; //创建一个XMLType对象 XMLType xmltype = XMLType.createXML(conn, xmldata); ps.setObject(1, xmltype); ps.executeUpdate();
此方法将创建XMLType的任务完全交给了客户端,因此客户端的压力大,数据库压力小。在实测过程中,需要添加两个jar包,不然会报找不到类的错误:
xdb.jar xmlparserv2.jar
需要注意这jar包又没版本标注,很容易弄错,一开始我下载了个xdb.jar,怎么弄都不对提示找不到某个类,查看之后发现是属于oracle更早期版本,重新下载了一个xdb.jar后正常。
以上三种方法通过插入20万条数据测试比较发现:
第一种方法:耗时最短,服务器cpu消耗最大;
第二种方法:耗时最长,服务器cpu消耗居中;
第三种方法:耗时居中,服务器cpu消耗最小.
至此,jdbc操作XMLType类型数据终于算是小小的搞定了,不用说采用了第三种方案,但是项目中基本都不会直接用jdbc来操作,像当前项目中就采用了Mybatis,上面也讲到了使用Mybatis总是出现异常,查看了下Mybatis也没有对XMLType的实现,看来还有的折腾,不过jdbc已经搞定,思路已经清晰了不是?
Mybatis
使用Mybatis操作XMLType,我们同样在Java端映射为String类型,当直接操作不做任何处理时,和jdbc大体一样,传输的内容长度小于4000时一切正常,当传输的内容长度超过4000左右时,同样抛出异常:
ORA-01461: can bind a LONG value only for insert into a LONG column
可见,Mybatis的操作其实和jdbc是一样的,只不过它在jdbc的外面又封装了一层,使得我们可以采用配置文件等映射的方式来更方便的访问数据库,我们要做的,就是在原有Mybatis便捷性的基础上实现对XMLType类型数据的插入,这种情况下,实现一个XMLType类型的自定义TypeHandler处理器是最好的选择。
这里,我们仍然采用前面提到的方案三,自然那两个jar包:xdb.jar,xmlparserv2.jar也是要加入的。
添加一个XmltypeTypeHandler,实现TypeHandler接口,由于插入数据主要用到setParameter方法,所以这里只列出该方法,其它方法代码略:
/** * oracle SYS.XMLTYPE 类型自定义处理器 */ public class XmltypeTypeHandler implements TypeHandler{ @Override public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { } ... }
这个setParameter方法就是Mybatis在把数据插入到数据库时用来设置参数的,至于这个方法的参数相信你看代码也已经明白了,我们按照前面jdbc的实现方式,在这里插入如下代码:
public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { XMLType xmltype = XMLType.createXML(ps.getConnection(), parameter); ps.setObject(i,xmltype); }
并在mapper-config.xml中注册转换器,因为Mybatis定义的枚举org.apache.ibatis.type.JdbcType中,没有我们需要的XMLType类型,在这里我们定义为UNDEFINED:
在配置文件参数中,使用我们的定义的转换器,这样Mybatis就能找到了:
#{xmlFile,jdbcType=UNDEFINED},
当然你也可以更规范一点,完整的写出它的类型和使用的转换器:
#{xmlFile,javaType=string,jdbcType=UNDEFINED,typeHandler=com.tyyd.dw.context.XmltypeTypeHandler},
完成上面的步骤,照理说一切都大功告成了,我们来运行一下。
结果抛出了异常:java.lang.ClassCastException: org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper cannot be cast to oracle.jdbc.OracleConnection
不能转换为Oracle的连接对象OracleConnection,查看一下,发现我们数据源使用的是apache的dbcp,应该是两者不兼容吧。网上查了一下,有位仁兄说是给了个完美解决文案,就是在setParameter方法内再独自加载一个Oracle的驱动类来创建一个connection,如下:
Class.forName("oracle.jdbc.OracleDriver"); Connection connection = DriverManager.getConnection(url, username, password);
这个确实能100%解决连接对象不能转换的问题,但是实现方式上,呵呵,还是不做评论了。还有网上在传来传去的,说是可以转换成PoolableConnection 对象,再使用getDelegate方法可以获得原始代理链接,这个貌似可行,我们来试试:
PoolableConnection connection = (PoolableConnection )ps.getConnection(); XMLType xmltype = XMLType.createXML(connection.getDelegate(), parameter); ps.setObject(i,xmltype);
结果又抛出了异常:
org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper cannot be cast to org.apache.commons.dbcp.PoolableConnection,不能转换。
没办法,看来网上传来传去的文章不怎么可靠,没捷径了还是自己看看源代码吧。
通过查看源代码,我们发现PoolableConnection继承了DelegatingConnection类,而DelegatingConnection类实现了Connection接口,我们把它转换成DelegatingConnection试试:
DelegatingConnection connection = (DelegatingConnection )ps.getConnection(); XMLType xmltype = XMLType.createXML(connection.getDelegate(), parameter); ps.setObject(i,xmltype);
结果又抛出异常:无法构造描述符: Invalid arguments; nested exception is java.sql.SQLException: 无法构造描述符: Invalid arguments,通过断点调试,发现connection对象居然是null,怎么会是null呢,网上人家都用的好好的,到我这里就都不行了,真是蛋疼,这不会无解吧,难道真要像上面那位仁兄说的独自加载一个驱动类?没办法,再研究研究吧。
最后发现,通过getMetaData方法可以获取它的原始代理连接,柳暗花明啊,赶紧写上测试,终于正常了,不容易啊,最终代码如下:
@Override public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { DelegatingConnection connection = (DelegatingConnection) ps.getConnection().getMetaData() .getConnection(); XMLType xmltype = XMLType.createXML(connection.getDelegate(), parameter); ps.setObject(i, xmltype); }
至此,使用Mybatis操作XMLType类型终于是搞定了,过程是一波三折啊。数据有插入当然要有查询,接下来就要实现XMLType类型的查询操作了。