前端代码审查(Code Review)
针对目录结构、SCSS规范、JS规范、Vue规范
可参照官方给出的风格指南(Code Review)
具体实践规范
1、POST/PUT/DELETE 请求按钮需要添加 loading 状态,防止重复提交。
建议使用 Element UI 提供的button 组件的loading属性,或者自己封装一个 loading 状态的按钮组件。
<el-button type="primary" :loading="loading" @click="handleSubmit"> 提交 </el-button>
2、模板上超过两个的判断条件,写成方法或者computed
<!--bad-->
<template>
<t-table v-if="satus==1&&orderStatus==2&&isShowTable"/>
</template>
<!--good-->
<template>
<t-table v-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 结合使用(调用接口)
export default {
created() {
this.getOrderNo()
},
methods:{
async getOrderNo() {
const res = await this.$api.getOrderNo()
if(res.success){
// 成功处理
}
}
}
}
7、使用try…catch…时–错误代码需要提示
try {
// 成功处理
} catch (error) {
// 处理异常的代码
this.$message.error(error.message)
}
8、函数有很多参数,需要封装成一个对象
// bad--->这个方式参数就必须按顺序传递
const getUserInfo =(name,age,sex,mobile,hobby)=> {
// 函数逻辑
}
// good
const getUserInfo =(userInfo)=> {
// 函数逻辑
const {name,age,sex,mobile,hobby} = userInfo
}
9、简化switch case判断
// bad
const counter =(state=0,action)=>{
switch (action.type) {
case 'ADD':
return state + 1
case 'MINUS':
return state - 1
default:
return state
}
}
// good
const counter =(state=0,action)=>{
const step={
'ADD':1,
'MINUS':-1
}
return state + (step[action.type] ?? 0)
}
10、判断条件过多需要提取出来
// bad
const checkGameStatus =()=>{
if(status===0||(satuas===1&&isEnd===1)||(isEnd===2)){
// 调用
}
}
// good
const isGaneOver =()=>{
return (status===0||(satuas===1&&isEnd===1)||(isEnd===2))
}
const checkGameStatus =()=>{
if(isGameOver()){
// 调用
}
}
11、if 判断嵌套—>错误前置
// bad
const publishPost =(post)=>{
if(isLoggenIn){
if(post){
if(isPostValid()){
doPublishPost(post)
}else{
throw new Error('文章不合法')
}
}else{
throw new Error('文章不能为空')
}
}else{
throw new Error('用户未登录')
}
}
// good
const publishPost =(post)=>{
if(!isLoggenIn){
throw new Error('用户未登录')
}
if(!post){
throw new Error('文章不能为空')
}
if(!isPostValid()){
throw new Error('文章不合法')
}
doPublishPost(post)
}
// bad
const createElement =(item)=>{
if(item.type==='ball'){
cosnt div = document.createElement('div')
div.className = 'ball'
div.style.backgroundColor = item.color
return div
}else if(item.type==='block'){
const div = document.createElement('div')
div.className = 'block'
div.style.backgroundColor = item.color
return div
}else if(item.type==='square'){
const div = document.createElement('div')
div.className = 'square'
div.style.backgroundColor = item.color
return div
}else{
throw new Error('未知元素类型')
}
}
// good
cosnt createElement =(item)=>{
const validTypes = ['ball', 'block', 'image']
if(!validTypes.includes(item.type)){
throw new Error('未知元素类型')
}
cosnt div = document.createElement('div')
div.className = item.type
div.style.backgroundColor = item.color
return div
}
// bad
let commodity = {
phone: '手机',
computer: '电脑',
television: '电视',
gameBoy: '游戏机',
}
function price(name) {
if (name === commodity.phone) {
console.log(1999)
} else if (name === commodity.computer) {
console.log(9999)
} else if (name === commodity.television) {
console.log(2999)
} else if (name === commodity.gameBoy) {
console.log(3999)
}
}
price('手机') // 1999
// good
const commodity = new Map([
['phone', 1999],
['computer', 9999],
['television', 2999],
['gameBoy', 3999],
])
const price = (name) => {
return commodity.get(name)
}
price('phone') // 1999
12、判断非空(使用空值合并操作符——??)
// bad
if(value !==null && value !==undefined && value !==''){
....
}
// good
if((value??'') !==''){
...
}
补充常规的—>目录结构规范:
项目根目录下创建 src 目录,src 目录下创建 api 目录、assets 目录、components 目录、directive 目录、router 目录、store 目录、utils 目录、views 目录。
一、api 目录存放所有页面API。
建议将每个页面的API封装成一个单独的js文件,文件名与页面名称相同(防止增删查改接口命名重复),并且都放在api下的modules目录下。
import request from '@/utils/request'
export function afterSaleApplyRefund(data) {
return request({
url: `/web/refundApplyOrder/applyRefund`,
method: 'put',
data
})
}
export function getStoreList(params) {
return request({
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)
// 自动加载api
const 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)
export default {
...finalObj
}
Vite自动加载配置API(使用import.meta.globEager)
(注册全局api方法 )instance.config.globalProperties.$api = api;
// 自动导入modules
const files: any = import.meta.globEager("./modules/*.ts");
let modules: any = {};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
Object.entries(files).forEach(([k, v]) => {
Object.assign(modules, v);
});
export default {
...modules
};
// useApi
import { ComponentInternalInstance, getCurrentInstance } from "vue";
export default function useApi() {
const { appContext } = getCurrentInstance() as ComponentInternalInstance;
const proxy = appContext.config.globalProperties;
return {
proxy
};
}
//页面使用
<script setup lang="ts">
import useApi from "@/hooks/useApi";
const { proxy } = useApi();
const getData = async () => {
const res = await proxy.$api.xxx(接口名);
if (res.success) {
...
}
}
</script>
二、assets 目录存放静态资源,如图片、字体、公共scss等。
三、components 目录存放公共组件(store也可以如下方式自动导入)。
建议将公共组件拆分为基础组件(baseComponents)和业务组件(pageComponents),基础组件存放一些通用的组件,如按钮、输入框、表格等,业务组件存放与具体业务相关的组件,如用户管理组件、权限管理组件等。
基础组件命名方式大驼峰,如:TTable;业务组件命名方式是小驼峰,如:importExcel。
组件文件夹下必须包含index.vue文件,index.vue文件中必须包含组件的name属性,name属性值必须与组件文件夹名一致。
基础组件复用性高,通常情况都是全局注册
components 基础组件全局注册---->Webpack方式
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)
})
//首字母大写
function capitalizeFirstLetter (str) {
return str && str.charAt(0).toUpperCase() + str.slice(1)
}
// 对符合'xx/xx.vue'组件格式的组件取组件名
function validateFileName (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>
components 基础组件全局注册—Vite方式
1、在每个基础组件文件夹下引入index.ts(我的命名是:install.ts),代码如下
import { App } from 'vue'
import Component from './index.vue'
export default {
install(app: App) {
app.component('TTable', Component)
}
}
2、在baseComponents文件夹下引入index.ts(我的命名是:install.ts),代码如下
/* 统一注册 baseComponents 目录下的全部组件 */
import { App } from 'vue'
export default {
install: (app: App) => {
// 引入所有组件下的安装模块
const modules:any = import.meta.globEager('./**/install.ts')
for (const path in modules) {
app.use(modules[path].default)
}
}
}
3、全局注册main.ts,代码如下
import baseComponentsInstall from '@/components/baseComponents/install'
const instance = createApp(App)
instance.use(baseComponentsInstall)
四、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'
export default function (config) {
// 创建axios实例
const service = axios.create({
// baseURL: process.env.VUE_APP_BASE_API,
baseURL: process.env.VUE_APP_BASE_API ,
// 超时 b
timeout: 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'
// 8080
if (config.type == 'file') {
config.headers['content-type'] = 'application/multipart/form-data'
} else if (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 = '/'
}
})
})
} else if (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)
}
)
return service(config)
}
相关文章
基于ElementUi再次封装基础组件文档
基于ant-design-vue再次封装基础组件文档文章来源:https://www.toymoban.com/news/detail-853892.html
vue3+ts基于Element-plus再次封装基础组件文档文章来源地址https://www.toymoban.com/news/detail-853892.html
到了这里,关于前端代码审查(Code Review)---具体实践规范会持续更新(新增Vite基础组件全局注册方式)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!