前端代码审查(Code Review)
针对目录结构、SCSS规范、JS规范、Vue规范
可参照官方给出的风格指南(Code Review)
具体实践规范
1、POST/PUT/DELETE 请求按钮需要添加 loading 状态,防止重复提交。
建议使用 Element UI 提供的button 组件的loading属性,或者自己封装一个 loading 状态的按钮组件。
<el-buttontype="primary":loading="loading"@click="handleSubmit"> 提交 </el-button>
2、模板上超过两个的判断条件,写成方法或者computed
<!--bad--><template><t-tablev-if="satus==1&&orderStatus==2&&isShowTable"/></template><!--good--><template><t-tablev-if="isChangeAvailiable"/></template><script>computed:{isChangeAvailiable(){return(this.satus==1&&this.orderStatus==2&&this.isShowTable
);},},</script>
3、可选链访问数组/对象元素
//bad
cosnt obj ={}
cosnt b = obj.a && obj.a.b
console.log(b)// undefined//good
cosnt obj ={}
cosnt b = obj?.a?.b
console.log(b)// undefined
4、定时器及时清理
mounted(){this.timer =setInterval(()=>{...},1000)}destroyed(){if(this.timer){clearInterval(this.timer)}}
5、window/body上的监听事件–需要解绑
mounted(){
window.addEventListener('resize',this.fun)}beforeDestroy(){
window.removeEventListener('resize',this.fun);}
6、async await 结合使用(调用接口)
exportdefault{created(){this.getOrderNo()},methods:{asyncgetOrderNo(){const res =awaitthis.$api.getOrderNo()if(res.success){// 成功处理}}}}
7、使用try…catch…时–错误代码需要提示
try{// 成功处理}catch(error){// 处理异常的代码this.$message.error(error.message)}
8、函数有很多参数,需要封装成一个对象
// bad--->这个方式参数就必须按顺序传递constgetUserInfo=(name,age,sex,mobile,hobby)=>{// 函数逻辑}// goodconstgetUserInfo=(userInfo)=>{// 函数逻辑const{name,age,sex,mobile,hobby}= userInfo
}
9、简化switch case判断
// badconstcounter=(state=0,action)=>{switch(action.type){case'ADD':return state +1case'MINUS':return state -1default:return state
}}// goodconstcounter=(state=0,action)=>{const step={'ADD':1,'MINUS':-1}return state +(step[action.type]??0)}
10、判断条件过多需要提取出来
// badconstcheckGameStatus=()=>{if(status===0||(satuas===1&&isEnd===1)||(isEnd===2)){// 调用}}// goodconstisGaneOver=()=>{return(status===0||(satuas===1&&isEnd===1)||(isEnd===2))}constcheckGameStatus=()=>{if(isGameOver()){// 调用}}
11、if 判断嵌套—>错误前置
// badconstpublishPost=(post)=>{if(isLoggenIn){if(post){if(isPostValid()){doPublishPost(post)}else{thrownewError('文章不合法')}}else{thrownewError('文章不能为空')}}else{thrownewError('用户未登录')}}// goodconstpublishPost=(post)=>{if(!isLoggenIn){thrownewError('用户未登录')}if(!post){thrownewError('文章不能为空')}if(!isPostValid()){thrownewError('文章不合法')}doPublishPost(post)}// badconstcreateElement=(item)=>{if(item.type==='ball'){
cosnt div = document.createElement('div')
div.className ='ball'
div.style.backgroundColor = item.color
return div
}elseif(item.type==='block'){const div = document.createElement('div')
div.className ='block'
div.style.backgroundColor = item.color
return div
}elseif(item.type==='square'){const div = document.createElement('div')
div.className ='square'
div.style.backgroundColor = item.color
return div
}else{thrownewError('未知元素类型')}}// good
cosnt createElement=(item)=>{const validTypes =['ball','block','image']if(!validTypes.includes(item.type)){thrownewError('未知元素类型')}
cosnt div = document.createElement('div')
div.className = item.type
div.style.backgroundColor = item.color
return div
}// badlet commodity ={phone:'手机',computer:'电脑',television:'电视',gameBoy:'游戏机',}functionprice(name){if(name === commodity.phone){
console.log(1999)}elseif(name === commodity.computer){
console.log(9999)}elseif(name === commodity.television){
console.log(2999)}elseif(name === commodity.gameBoy){
console.log(3999)}}price('手机')// 1999// goodconst commodity =newMap([['phone',1999],['computer',9999],['television',2999],['gameBoy',3999],])constprice=(name)=>{return commodity.get(name)}price('phone')// 1999
12、判断非空(使用空值合并操作符——??)
// badif(value !==null&& value !==undefined&& value !==''){....}// goodif((value??'')!==''){...}
补充常规的—>目录结构规范:
项目根目录下创建 src 目录,src 目录下创建 api 目录、assets 目录、components 目录、directive 目录、router 目录、store 目录、utils 目录、views 目录。
1、api 目录存放所有页面API。
建议将每个页面的API封装成一个单独的js文件,文件名与页面名称相同(防止增删查改接口命名重复),并且都放在api下的modules目录下。
import request from'@/utils/request'exportfunctionafterSaleApplyRefund(data){returnrequest({url:`/web/refundApplyOrder/applyRefund`,method:'put',
data
})}exportfunctiongetStoreList(params){returnrequest({url:`/webWaterStore/getMarkStoreTree`,method:'get',
params
})}....
建议API目录下新建index.js文件,用于统一导出所有API,在main.js引入并将api挂载到vue的原型上
Vue.prototype.$api = api
;在页面直接使用
this.$api.xxx
调用接口。
WebPack自动加载配置API(使用require.context)
// 自动加载apiconst commonApiObj ={}const finalObj ={}const modulesApi = require.context('./modules',true,/\.js$/)
modulesApi.keys().forEach(key=>{const newKey = key.replace(/(\.\/|\.js)/g,'')
commonApiObj[newKey]=require(`./modules/${newKey}`)})
Object.values(commonApiObj).map(x=> Object.assign(finalObj, x))// console.log('所有业务接口--', finalObj)exportdefault{...finalObj
}
Vite自动加载配置API(使用import.meta.globEager)
(注册全局api方法 )instance.config.globalProperties.$api = api;
// 自动导入modulesconstfiles: any =import.meta.globEager("./modules/*.ts");letmodules: any ={};// eslint-disable-next-line @typescript-eslint/no-unused-vars
Object.entries(files).forEach(([k, v])=>{
Object.assign(modules, v);});exportdefault{...modules
};
// useApiimport{ ComponentInternalInstance, getCurrentInstance }from"vue";exportdefaultfunctionuseApi(){const{ appContext }=getCurrentInstance()as ComponentInternalInstance;const proxy = appContext.config.globalProperties;return{
proxy
};}
//页面使用<script setup lang="ts">import useApi from"@/hooks/useApi";const{ proxy }=useApi();constgetData=async()=>{const res =await proxy.$api.xxx(接口名);if(res.success){...}}</script>
2、assets 目录存放静态资源,如图片、字体、公共scss等。
3、components 目录存放公共组件(store也可以如下方式自动导入)。
建议将公共组件拆分为基础组件(baseComponents)和业务组件(pageComponents),基础组件存放一些通用的组件,如按钮、输入框、表格等,业务组件存放与具体业务相关的组件,如用户管理组件、权限管理组件等。
基础组件命名方式大驼峰,如:TTable;业务组件命名方式是小驼峰,如:importExcel。
组件文件夹下必须包含index.vue文件,index.vue文件中必须包含组件的name属性,name属性值必须与组件文件夹名一致。
基础组件复用性高,通常情况都是全局注册
components 目录下的index.js–全局导入
import Vue from'vue'// 全局自动注册baseComponents下的基础组件const requireComponent = require.context('./baseComponents',true,/\.vue$/)// 找到组件文件夹下以.vue命名的文件,如果文件名为index,那么取组件中的name作为注册的组件名
requireComponent.keys().forEach(filePath=>{const componentConfig =requireComponent(filePath)const fileName =validateFileName(filePath)const componentName = fileName.toLowerCase()==='index'?capitalizeFirstLetter(componentConfig.default.name): fileName
Vue.component(componentName, componentConfig.default || componentConfig)})//首字母大写functioncapitalizeFirstLetter(str){return str && str.charAt(0).toUpperCase()+ str.slice(1)}// 对符合'xx/xx.vue'组件格式的组件取组件名functionvalidateFileName(str){return/^\S+\.vue$/.test(str)&&
str.replace(/^\S+\/(\w+)\.vue$/,(rs, $1)=>capitalizeFirstLetter($1))}
全局注册main.js
import'@/components/index.js'// 全局基础组件注入
页面组件使用
<template><div><t-table></t-table></div></template>
4、utils 目录存放公共方法,如全局loading,axios封装,正则校验等。
axios封装(request.js)
import axios from'axios'import{ Notification, MessageBox, Message }from'element-ui'import store from'@/store'import{ getToken }from'@/utils/auth'exportdefaultfunction(config){// 创建axios实例const service = axios.create({// baseURL: process.env.VUE_APP_BASE_API,baseURL: process.env.VUE_APP_BASE_API,// 超时 btimeout:50000})// request拦截器
service.interceptors.request.use(config=>{getToken()&&(config.headers['Authorization']=getToken())
localStorage.getItem('store_id')&&(config.headers['Store-Id']= localStorage.getItem('store_id'))
config.headers['Content-Type']= config.headers['Content-Type']||'application/json'// 8080if(config.type =='file'){
config.headers['content-type']='application/multipart/form-data'}elseif(config.type =='form'){
config.headers['Content-type']='application/x-www-form-urlencoded'}if(config.method.toLowerCase()==='get'){
config.data =true}return config
},error=>{
console.log(error)
Promise.reject(error)})// 响应拦截器
service.interceptors.response.use(res=>{const code = res.data.code
if(code ===401){
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录','系统提示',{confirmButtonText:'重新登录',cancelButtonText:'取消',type:'warning'}).then(()=>{
store.dispatch('FedLogOut').then(()=>{if(!window.__POWERED_BY_QIANKUN__){// 为了重新实例化vue-router对象 避免bug
location.reload()}else{
window.location.href ='/'}})})}elseif(code !==200){
Notification.error({title: res.data.msg
})return Promise.reject('error')}else{return res.data
}},error=>{
console.log('err'+ error)Message({message: error.message,type:'error',duration:5*1000})return Promise.reject(error)})returnservice(config)}
相关文章
基于ElementUi再次封装基础组件文档
基于ant-design-vue再次封装基础组件文档
vue3+ts基于Element-plus再次封装基础组件文档
版权归原作者 wocwin 所有, 如有侵权,请联系我们删除。