vue项目规范

222 阅读5分钟

参考文献

github.com/PanJiaChen/…

规范目的

提高团队协作效率 便于后期优化维护 输出高质量的产品

目录文件夹及子文件规范
src                          源码目录
|-- api                      接口目录
|   |-- xxx1.js              以业务大模块命名的js
|   |-- xxx2.js              以业务大模块命名的js

|-- assets                   静态资源目录
|   |-- imgs                 放置图片的文件目录
|   |-- css                  放置样式或字体图标的文件目录
|   |-- |-- common.scss      一些通用的样式
|   |-- fonts                放置字体图标的文件目录

|-- components               公用组件目录
|   |-- Table                公用Table文件目录(PascalCase)
|   |-- |-- index.vue        公用Table入口文件
|   |-- Pagination           公用Pagination文件目录
|   |-- |-- index.vue        公用Pagination入口文件
|   |-- index.js             注册全局公用组件的入口js文件

|-- constant                 常量目录
|   |-- index.js

|-- enums                    枚举目录
|   |-- index.js             枚举入口
|   |-- enums.js

|-- directives               指令目录
|   |-- permission           权限指令相关目录
|   |-- |-- index.js         注册指令
|   |-- |-- permission.js
|   |-- debounce             防抖指令相关目录
|   |-- |-- index.js         注册指令
|   |-- |-- debounce.js

|-- filters                  过滤器目录(模板插值和v-bind表达式)(vue3中config.globalProperties和provide)
|   |-- index.js

|-- mixins                   混入目录(vue3中更推荐使用组合式函数composables)
|   |-- xxxMixin.js

|-- request                  对axios或fetch封装目录
|   |-- index.js

|-- utils                    工具集合目录
|   |-- index.js             引入并输出各个工具
|   |-- validate.js
|   |-- format.js

|-- mock                     模拟接口,临时存放

|-- router                   路由目录
|   |-- index.js             路由入口文件
|   |-- modules              各个业务模块目录
|   |-- |-- xxxa.js
|   |-- |-- xxxb.js

|-- store                    vuex目录(pina的话store目录建平铺xxx.js即可,命名usexxxStore)
|   |-- index.js             入口文件
|   |-- modules              各个业务模块目录
|   |-- |-- xxxa.js
|   |-- |-- xxxb.js

|-- views                    视图目录
|   |-- staffWorkbench       视图模块名目录
|   |-- |-- index.vue        模块入口页面
|   |-- |-- components       模块页面级组件文件夹
|   |-- |-- |-- AaBb.vue       模块页面级组件文件夹

|-- App.vue                  根组件

|-- main.js                  应用入口
  • src/api/xxx1.js 代码示例 (每个导出的 api 以 Request 结尾)
import request from "@/request";

/**
 * @desc 登录  [接口地址]
 */
export function loginRequest(data) {
  return request({
    url: "/vue-element-admin/user/login",
    method: "post",
    data,
  });
}
  • src/components/index.js 代码示例 (待测试)
// webpack项目中 require.context(需要读取模块的文件的所在目录,  是否遍历子目录, 匹配的规则(正则表达式))
import Vue from "vue";

const components = require.context("./components", true, /\/index.vue$/);
// const model = require.context('./components', true, /\.vue$/)
components.keys().forEach((componentKey) => {
  const component = components(componentKey).default;
  Vue.component(component.name, component);
});
for (const key in components) {
  const component = components[key].default;
  Vue.component(component.name, component);
}

// vite项目中
import Vue from "vue";
const components = import.meta.globEager("./components/*.vue");
for (const key in components) {
  const component = components[key].default;
  Vue.component(component.name, component);
}
  • src/store/modules/user.js 代码示例
import { loginRequest } from "@/api/user";
import { getToken } from "@/utils/auth";

/**
 * @desc 使用示例 computed: {...mapState(['token']),  ...mapState('moduleName', ['aa', 'bb']),}
 */
const state = {
  token: getToken(),
  roles: [],
};

/**
 * @desc 使用示例 computed: {...mapGetters(['getRolesByType']),  ...mapGetters('moduleName', ['aaGetter', 'bbGetter']),}
 */
const getters = {
  getRolesByType: (state) => (type) => {
    return state.roles?.filter((role) => role.type === type);
  },
};

/**
 * @desc 使用示例 methods: {...mapMutations(['SET_TOKEN']),  ...mapMutations('moduleName', ['setAa', 'setBb']),}
 */
const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token;
  },
  SET_ROLES: (state, roles) => {
    state.roles = roles;
  },
};

/**
 * @desc 使用示例 methods: {...mapActions(['login']),  ...mapActions('moduleName', ['setAaAsync', 'setBbAsync']),}
 */
const actions = {
  async login({ commit }, userInfo) {
    const { token, roles } = await loginRequest({ ...userInfo });
    commit("SET_TOKEN", token);
    commit("SET_ROLES", roles);
  },
};

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions,
};
  • src/store/index.js 代码示例
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const modulesFiles = require.context("./modules", true, /\.js$/);

const modules = modulesFiles.keys().reduce((modules, modulePath) => {
  // set './user.js' => 'user'
  const moduleName = modulePath.replace(/^\.\/(.*)\.\w+$/, "$1");
  const value = modulesFiles(modulePath);
  modules[moduleName] = value.default;
  return modules;
}, {});

const store = new Vuex.Store({
  modules,
});

export default store;
  • src/enums/index.js 代码示例
class EnumFactory {
    constructor(configs) {
        this.configs = Object.freeze(configs)
        const options = []
        Object.keys(configs).forEach(key => {
            options.push(configs[key])
            Object.defineProperty(this, key, {
                get: () => this.configs[key].value,
            });
        });
        this.options = Object.freeze(options)
    }
    getLabelByVal(val) {
        return this.options.find(item => item.value === val).label
    }
    getAllVals() {
        return this.options.map(item => item.value);
    }
}

export function createEnumFactory(configs) {
    return new EnumFactory(configs)
}
  • src/enums/enums.js 代码示例
import { createEnumFactory } from "./index.js";
const TodoEnum = createEnumFactory({
    PENDING: { label: '进行中', value: 'pending' },
    DONE: { label: '已完成', value: 'done' },
    CANCELED: { label: '已取消', value: 'cancel' }
})
const StausEnum = createEnumFactory({
    PENDING: { label: '审批中', value: 'pending' },
    DONE: { label: '审批通过', value: 'approve' },
    CANCELED: { label: '审批拒绝', value: 'refuse' }
})
console.log('todoEnum', TodoEnum.options, TodoEnum.PENDING, TodoEnum.getLabelByVal('done'), TodoEnum.getAllVals())
    
命名规范(让团队当中其他人看你的代码能一目了然)
  • 常量文件命名规范 A_B
const MAX_COUNT = 10;
const URL = "https://www.baidu.com/";
  • 增强代码可读性,推荐使用枚举或英文代替数字等类型
  • 变量命名规范 小驼峰 aaBbCc
// 数字类型 count total amount 等跟数量相关的
// 布尔类型 isxxx、canxx、hasxxx、disabledxxx、needxxx、showxxx、
// 数组类型 xxxs、xxxList、
// api方法  xxxRequest
// 获取列表 getList getxxxList
// 设置     setxxx
// 获取     getxxx  getxxxByxxx
// mixin   xxxMixin
// ref     xxxRef
// 动作相关的 add delete update search view save reset  change
  • class 命名 aa-bb
//  容器 xxx-container  sidebar-container  main-container breadcrumb-container  app-container
//  导航:nav  子导航:subnav  菜单:menu  面包屑:crumb  选项卡:tab	 标题区:header/title	 列表:list 表格: table  表单: form
//  提示:tips  新闻:news  下载:download
日志规范
console.error("xxx---error:", error);
console.log("xxx---start");
console.log("xxx---end");
console.log("data:", data);
编码规范(使用 ES6 风格编码)
  • 能使用 const 的地方使用 const
  • 静态字符串一律使用单引号或反引号,动态字符串使用反引号 ``
  • 使用数组成员对变量赋值时,优先使用解构赋值。
  • 使用扩展运算符(...)拷贝数组。
  • 优先使用 Map 结构
  • 使用===!==
  • 字符串 includes(), startsWith(), endsWith()
  • 函数参数的默认值 + 箭头函数
  • 数组 includes()
  • 对象 Object.is() Object.hasOwn()
  • 运算符 ?. ??
  • Reflect.ownKeys(target)
  • for...of 循环
  • async await 取代 then
vue 项目规范
  • 组件命名规范 <my-component-name><MyComponentName> 都是可接受的 (个人推荐<MyComponentName>)
  • 事件
// onXxx
<button @click="onLogin">click me</button>

// 自定义事件key  @kebab-case
<MyComponent @some-event="onXxx" />

// 触发 camelCase
<button @click="$emit('some-event')">click me</button>
  • 引号
// template中使用双引号(属性等)
// js中推荐使用单引号
  • props 最好带有类型 默认值 和 校验器, .vue文件中使用的是字符串模板,可以统一命名prop方式为小驼峰
props: {
   propD: {
      type: Number,
      default: 100
    },
   propF: {
    validator: function (value) {
      // 这个值必须匹配下列字符串中的一个
      return ['success', 'warning', 'danger'].includes(value)
    }
  }
}
  • 模板中表达式尽可能简单
  • 组件命名 大驼峰命名(index.vue 除外)
  • 文件目录名 aaBbCc
  • 避免 v-if 和 v-for 同时用在一个元素上(性能问题),建议通过 computed 等转化
  • v-if 和 v-show 使用要有原则
  • v-for 上的 key 不要使用索引
  • keep-alive
  • 事件代理
  • 列表 Object.freeze()
  • 减少 watch 使用
  • 优先使用 computed
  • 第三方插件的按需引入
  • 组件按需加载
prettier eslint(前提:vscode 安装 ESLint 和 Prettier 插件 和 设置 Format On Save + vscode配置默认格式化工具选择prettier)
  • .prettierrc | .prettierrc.json (不要出现格式错误)
{
  "printWidth": 200,
  "semi": true,
  "singleQuote": true,
  "useTabs": false,
  "tabWidth": 2,
  "trailingComma": "es5",
  "arrowParens": "avoid",
  "proseWrap": "preserve",
  "endOfLine": "auto",
  "bracketSpacing": true,
  "stylelintIntegration": false,
  "eslintIntegration": false,
  "tslintIntegration": false,
  "disableLanguages": [
    "vue"
  ],
  "htmlWhitespaceSensitivity": "ignore",
  "ignorePath": ".prettierignore"
}
  • .prettierignore
dist/
node_modules/
package-lock.json
  • .eslintrc
  // off: 允许(关闭检测规则:0)  warn:提醒级别(1)  error:错误级别(2)
rules: {
  'no-alert': 2, // 禁止使用alert confirm prompt
  'no-debugger': 1, // 禁止使用debugger
  'no-console': 0, // 允许使用console
  'prefer-const': 1, // 建议使用 const
  'no-dupe-keys': 2, // 在创建对象字面量时不允许键重复 {a:1,a:1}
  'no-dupe-args': 2, // 函数参数不能重复
  'no-duplicate-imports': [2, { 'includeExports': true }], // 不允许重复导入
  'no-duplicate-case': 2, // switch中的case标签不能重复
  'padded-blocks': 0, // 块语句内行首行尾是否要空行
  'space-after-keywords': [0, 'always'], // 关键字后面是否要空一格
  'space-before-blocks': [0, 'always'], // 不以新行开始的块{前面要不要有空格
  'space-before-function-paren': [0, 'always'], // 函数定义时括号前面要不要有空格
  'space-in-parens': [0, 'never'], // 小括号里面要不要有空格
  'space-infix-ops': 0, // 中缀操作符周围要不要有空格
  'eqeqeq': 2, // 必须使用全等
  'no-var': 2, // 禁用var,用let和const代替
  'no-unused-vars': 1, // 提醒没有用到的变量
  'no-extra-boolean-cast': 2, // 禁止不必要的bool转换
  'valid-jsdoc': 0,
  'one-var': 0, // 允许连续声明
  'semi-spacing': [0, { before: false, after: true }], // 分号前后空格
  'no-new': 0, // 禁止在使用new构造一个实例后不赋值
  'no-extra-semi': 0, // 禁止多余的冒号
  'keyword-spacing': 0,
  'arrow-parens': 0, // 箭头函数用小括号括起来 - 关闭
  'generator-star-spacing': 0, // 生成器函数*的前后空格
  'no-mixed-operators': 0,
  'eol-last': 0, // 文件以单一的换行符结束 - 关闭
  'object-curly-spacing': 0, // 大括号内是否允许不必要的空格
  'vue/no-deprecated-v-bind-sync': 1, // 提醒 v-bind-sync
  'vue/no-deprecated-slot-attribute': 1, // 提醒 deprecated-slo
  'vue/multi-word-component-names': 1, // 提醒 multi-word-component
  'vue/no-deprecated-v-on-native-modifier': 1, // 提醒 .native
  'no-undef': 0, // 关闭 未定义的
}
  • .eslintignore
dist/
node_modules/
package-lock.json
vscode 插件
auto close tab
auto rename tag
chinese
eslint
gitlens
es6 code snippets
markdown preview enhanced
open in browser
path autocomplete
prettier
prettify json
relative path
todo highlight
vetur
volar
copilot  codewhisper codegeex
前端开发插件

(谷歌浏览器 + nvm + node(.msi) + git + postman + sourcetree + http-server)