团队规范系列之工程规范

bg

最近一周的工作重心就是在梳理团队规范,在写的过程也查缺补漏了不少知识,剔除掉关于公司场景的部分就有了这一系列的文章,预计写四部分:

  1. git 规范
  2. 工程规范
  3. 用户体验规范
  4. 命名规范

工程规范

项目目录

项目目录定义的名称应当做到清晰易读,对于每个文件夹可以放置一个README.md的文件,对重要部分和职责进行描述,下面给一份常用的示例:

├── public                          // 静态页面
├── scripts                         // 相关脚本配置
├── src                             // 主目录
    ├── assets                      // 静态资源
    ├── components                  // 全局组件
    ├── lib                         // 全局插件
    ├── router                      // 路由配置
    ├── store                       // vuex 配置
    ├── styles                      // 样式
    ├── utils                       // 工具方法(axios封装,全局方法等)
    ├── views                       // 页面
    ├── App.vue                     // 页面主入口
    ├── main.js                     // 脚本主入口

下面再说几条建议:

  • 如果存在嵌套路由的页面,可以在 src 定义一个 layout 当做基础的视图组件使用
  • components 文件内的组件请保持通用性
  • 如果 views 存在业务组件,可以在当前目录下新建 components 使用,或者基于全局 components 进行二次封装
  • views 下的页面使用文件夹的形式来定义,例如有一个统计页面,如果只写一个.vue文件会导致文件内容过多,而如果直接在 views 下进行抽离多个.vue会导致结构不统一

utils

对于 utils 下的文件,请保持按照文件类型进行划分,下面截取ant-design-pro的 utils 文件为例

名称提交信息
Authorized.tsuse @umijs/fabric@2.5.0
authority.ts🌎 localization: docs translated to english (#7938)
request.ts🌎 localization: docs translated to english (#7938)
utils.lessclean: remove unuse less (#6214)
utils.test.tsfeat: @umijs/route-utils replace getAuthorityFromRouter (#7319)
utils.ts🌎 localization: docs translated to english (#7938)

在开发项目中,请优先使用 lodash 这样的工具库,并在此基础上封装自己的方法,如果真的不存再在考虑手动实现从 0 到 1 实现

VueX

在大型项目中,对 VueX 的拆分通常根据业务,请不要直接使用 VueX 下的StateGetterMutation等,而是改用Module将相关依赖聚合在一起,最后通过import整合在一起,下面以 home 文件夹为例:

├── home                            // 主目录
    ├── index.js                    // VueX state getters mutations action 管理
    ├── ...                         // 可能存在的其它文件
└── index.js                        // VueX 主入口

home/index.js

export default {
  namespaced: true,
  state: () => ({ ... }),
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

index.js

import { createStore } from 'vuex';
import home from './home';

const store = createStore({
  modules: {
    home,
  },
});

export default store;

Router

路由层级

路由层级十分重要,在 Router4 之前,会根据路由定义的顺序来决定路由的先后顺序,如果层级很不清晰会导致两个问题:

  • 维护或者定义新路由需要查找半天
  • 遍历 routes 信息十分困难,做权限之类的的很麻烦

1.png

举个例子,对于上面的一个设计图,包含:head、content、footer三个部分,观察他们剩余设计图顶部和底部基本相同,只是 content 区域的内容有所不同。

而对这样一个设计图进行开发,可以定义一个layout组件当做基础公共结构,之后书写routers的具体信息

├── routes                            // routes的定义目录
    ├── home.js                       // 首页
    ├── type.js                       // 分类
    ├── food.js                       // 东家菜
    ├── brand.js                       // 大牌
└── index.js                          // vue router 主入口
└── routes.js                         // 将routers目录下的文件分发成最终vue router使用结构

routes.js

import layout from '@/layout';
import home from './routes/home';
import type from './routes/type';
import food from './routes/food';
import brand from './routes/brand';

const routes = [
  {
    path: '/',
    component: layout,
    children: home,
  },
  {
    path: '/',
    component: layout,
    children: type,
  },
  {
    path: '/',
    component: layout,
    children: food,
  },
  {
    path: '/',
    component: layout,
    children: brand,
  },
];

export default routes;

index.js

import { createRouter, createWebHistory, createWebHashHistory } from 'vue-router';

import routes from './routes';

const router = createRouter({
  routes,
});

// 全局导航守卫
router.beforeEach(async (to) => {
  // ...
});

export default router;

捕获未知路由

无论在做中台还是移动端的产品,都需要正确处理未知的路由,一般而言有二种处理方式,具体参考项目的类型进行选择:

  1. 直接给出 404 之类的提示,提示页面不存在等信息;
  2. 帮助其重定向到首页或者其它页面;
// vue router4
{ path: '/:pathMatch(._)_', name: 'NotFound', redirect: { name: 'home' } };

// vue router3
{ path: '*', name: 'NotFound', redirect: { name: 'home' } };

路由懒加载

使用懒加载可以节省白屏时间,懒加载的机制利用了 webpack 代码分割 + import(),只在进入当前路由的时候加载所依赖的 js 文件。

  { path: '/', name: 'home', component: () => import(/* webpackChunkName: "home" */ 'views/home/index.vue') }

除了需要children的父级页面,所有其它页面统一使用懒加载

/* webpackChunkName: "home" */这样的注释语法,是 webpack 独有的,它负责 build 打包之后的文件名称

组件拆分建议

2.png

一个大型项目由无数子组件组合而成,有点像乐高积木一样,直接开发一个大型项目肯定很难,但是如果给无数子组件让你拼接则相对简单。

组件的拆分,应该遵循两个原则:

颗粒度细分

在学习设计模式中有很重要的一句话就是单一职责,在组件开发也是同理,原则上让一个组件只负责一件事情,可以最大程度的复用组件,方便测试和定位问题。

但是过度的单一职责组件也会导致一个问题就是颗粒度太细造成组件的碎片化,举一个例子来说,自动完成组件,它是搜索框 + select 组合而成,因此我们可以直接复用,因为前两个组件的颗粒度刚刚好。

3.png

还有一个例子就是徽章数组件(Badge),它的右上角会有红点提示,可能是数字也可能是 icon,它的职责当然也很单一,这个红点提示也理所当然也可以被单独抽象为一个独立组件,但是我们通常不会将它作为独立组件,因为在其它场景中这个组件是无法被复用的,因为没有类似的场景再需要小红点这个小组件了

4.png

通用性考虑

组件分为基础组件和业务组件,当存在业务常见重复使用的时候通常会对通用组件进行二次封装,例如有一个搜索部门的组件,这个组件在很多 view 都有复用而且也对这个组件集成了自动搜索 + 初始 loading + 自动加载下一页,最后使用效果用起来也很方便。

但是现在突然来一个需求,说对搜索的组件进行底部提示,那难道直接更改源码吗?但是如果直接更改组件,这样也违背了开放、关闭原则

其实说这个例子就是希望思考组件广的适配性,在使用第三方的组件,会看到它们暴露了很多插槽、jsx 的渲染函数,最终的目的也就是应对各种各样的场景

组件的形态(DOM 结构)永远是千变万化的,但是其行为(逻辑)是固定的,因此通用组件的秘诀之一就是将 DOM 结构的控制权交给开发者,组件只负责行为和最基本的 DOM 结构

工程优化

优化是一个很庞大的命题,根据项目的不同所采取的方式也不尽相同,下面只总结一些常用的方式

项目优化

  • CND
  • gzip,让后端支持 gzip 压缩,前端也生成 gzip 文件
  • 使用 webp 格式,生产环境下压缩图片
  • 路由懒加载
  • 第三方组件按需加载
  • 抽离第三方库,避免和业务组件耦合一起打包
  • ...

code 优化

  • 减少对 dom 操作,如果操作将所有 dom 聚合在一起修改
  • 不直接修改 style 改用 class 修改;
  • 尽量不适用 table,绑定事件采用事件委托形式
  • 对动画元素,让其脱离文档流,减少重绘和重排
  • ...

构建过程优化

  • 删除 eslint-loader,改用编辑器自带的 eslint
  • 使用 catch-loader,对资源进行加速
  • 生产环境下删除.map文件,缩短import查找后缀
  • ...