如何结合整洁架构和MVP模式提升前端开发体验(三) - 项目工程化配置、规范篇

工程化配置 【如何结合整洁架构和MVP模式提升前端开发体验(三) - 项目工程化配置、规范篇】还是开发体验的问题,跟开发体验有关的项目配置无非就是使用 eslint、prettier、stylelint 统一代码风格。
formatting and lint
eslint、prettier、stylelint 怎么配这里就不说了,网上文章太多了。想说的是eslint rule 'prettier/prettier': 'error'一定要开启,以及 stylelint rule 'prettier/prettier': true 也一定要开启。
虽然配置了eslint、prettier、stylelint,但是可能你队友的编辑器并没有装相应的插件,格式化用的也不是 prettier,然后他修改一行代码顺便把整个文件格式化了一遍。所以还得配置 husky + lint-staged,提交代码的时候按规范格式化回去,不符合规范的代码不允许提交。
如果公司的电脑配置还行的话,可以开发阶段就做相应的 lint, 把错误抛出来,中断编译。webpack 可以使用 eslint-loader,stylelint-webpack-plugin;vite 可以使用 vite-plugin-eslint,vite-plugin-stylelint;vue-cli 配置几个参数就可以开启,具体看文档。
ts-check
什么是 ts-check?举个例子,有一个后端接口的某个字段名称变了,由 user_name 改为了 userName,如果没有配置开发阶段进行 ts-check 并把错误抛出来,那么只能全局查找调用接口的地方去修改,如果改漏了,那就喜提一个 BUG。
ts-check 可以开发阶段就做,也可以提交代码的时候做。开发阶段 webpack 安装 fork-ts-checker-webpack-plugin ,vite 也是找相应的插件(暂时没找到用的比较多的)。提交代码的时候,结合 husky 做一次全量的 check (比较耗时),react 项目执行 tsc --noEmit --skipLibCheck,vue 项目执行 vue-tsc --noEmit --skipLibCheck
ts-check 能好用的前提是你的项目是 TS 写的,接口返回值有具体的类型定义,而不是 any。
代码规范 主要讲讲 model,service,presenter,view 这几层的代码规范,之前的文章也有简单提到过,这里做个归纳。
model

import { reactive, ref } from "vue"; import { IFetchUserListResult } from "./api"; export const useModel = () => { const userList = reactive<{ value: IFetchUserListResult["result"]["rows"] }>({ value: [], }); return { userList, }; }; export type Model = ReturnType;

  1. 每一个字段都要声明类型,不要因为字段多就用 Object[k: string]: string | number | booleanRecord 之类的来偷懒。
  2. 可以包含一些简单逻辑的方法,比如重置 state。
  3. vue 中字段声明可以移到 useModel 外面,达到状态共享的作用,在 useModel 中 return 出去使用。
service
  1. react 技术栈,presenter 层调用的时候使用单例方法,避免每次re-render 都生成新的实例。
  2. service 要尽量保持“整洁”,不要直接调用特定环境,端的 API,尽量遵循 依赖倒置原则。比如 fetch,WebSocket,cookie,localStorage 等 web 端原生 API 以及 APP 端 JSbridge,不建议直接调用,而是抽象,封装成单独的库或者工具函数,保证是可替换,容易 mock 的。Taro,uni-app 等框架的 API 也不要直接调用,可以放到 presenter 层。组件库提供的命令式调用的组件,也不要使用。
  3. service 方法的入参要合理,不要为了适配组件库而声明不合理的参数,比如某个组件返回 string[] 类型的数据,实际只需要数组第一个元素,参数声明为 string 类型即可。2个以上参数改为使用对象。
  4. 业务不复杂可以省略 service 层。
service 保证足够的“整洁”,model 和 service 是可以直接进行单元测试的,不需要去关心是 web 环境还是小程序环境。
import { Model } from './model'; export default class Service { private static _indstance: Service | null = null; private model: Model; static single(model: Model) { if (!Service._indstance) { Service._indstance = new Service(model); } return Service._indstance; }constructor(model: Model) { this.model = model; } }

presenter
import { message, Modal } from 'antd'; import { useModel } from './model'; import Service from './service'; const usePresenter = () => { const model = useModel(); const service = Service.single(model); const handlePageChange = (page: number, pageSize: number) => { service.changePage(page, pageSize); }; return { model, handlePageChange, }; }; export default usePresenter;

  1. 处理 view 事件的方法以 handle 或 on 开头。
  2. 不要出现过多的逻辑。
  3. 生成 jsx 片段的方法以 render 开头,比如 renderXXX。
  4. 不管是 react 还是 vue 不要解构 model,直接 model.xxxx 的方式使用。
view
  1. 组件 props 写完整类型。
  2. jsx 不要出现嵌套的三元运算。
  3. 尽量所有的逻辑都放到 presenter 中。
  4. 不要解构 presenter 以及 model,以 presenter.xxx,model.xxxx 方式调用。
store
  1. 不要在外层去使用内层的 store。
接口请求方法
  1. 封装的接口请求方法支持泛型
import axios, { AxiosRequestConfig } from "axios"; import { message } from "ant-design-vue"; const instance = axios.create({ timeout: 30 * 1000, }); // 请求拦截 instance.interceptors.request.use( (config) => { return config; }, (error) => { return Promise.reject(error); }, ); // 响应拦截 instance.interceptors.response.use( (res) => { return Promise.resolve(res.data); }, (error) => { message.error(error.message || "网络异常"); return Promise.reject(error); }, ); type Request = (config: AxiosRequestConfig) => Promise; export const request = instance.request as Request;

  1. 具体接口的请求方法,入参及返回值都要声明类型,参数量最多两个,body 数据命名为 data,非 body 数据命名为 params,都是对象类型。
  2. 参数类型及返回值类型都声明放在一起,不需要用单独的文件夹去放,觉得代码太多不好看可以用 region 注释块折叠起来(vscode 支持)。
  3. 接口请求方法以 fetch,del,submit,post 等单词开头。
  4. 建议接口请求方法直接放在组件同级目录里,建一个 api.ts 的文件。很多人都习惯把接口请求统一放到一个 servcies 的文件夹里,但是复用的接口又有几个呢,维护代码的时候在编辑器上跨一大段距离来回切换文件夹真的是很糟糕的开发体验。
// #region 编辑用户 export interface IEditUserResult { code: number; msg: string; result: boolean; }export interface IEditUserParams { id: number; }export interface IEditUserData { name: string; age: number; mobile: string; address?: string; tags?: string[]; }/** * 编辑用户 * http://yapi.smart-xwork.cn/project/129987/interface/api/1796964 * @author 划水摸鱼糊屎工程师 * * @param {IEditUserParams} params * @param {IEditUserData} data * @returns */ export function editUser(params: IEditUserParams, data: IEditUserData) { return request(`${env.API_HOST}/api/user/edit`, { method: 'POST', data, params, }); }// #endregion

上面代码是工具生成的,下篇说说提升开发效率及体验的工具。

    推荐阅读