如何結(jié)合整潔架構(gòu)和MVP模式提升前端開發(fā)體驗(yàn)(三) - 項(xiàng)目工程化配置、規(guī)范篇

工程化配置

還是開發(fā)體驗(yàn)的問題,跟開發(fā)體驗(yàn)有關(guān)的項(xiàng)目配置無非就是使用 eslint、prettier、stylelint 統(tǒng)一代碼風(fēng)格

formatting and lint

eslint、prettier、stylelint 怎么配這里就不說了,網(wǎng)上文章太多了。想說的是eslint rule 'prettier/prettier': 'error'一定要開啟,以及 stylelint rule 'prettier/prettier': true 也一定要開啟。

雖然配置了eslint、prettier、stylelint,但是可能你隊(duì)友的編輯器并沒有裝相應(yīng)的插件,格式化用的也不是 prettier,然后他修改一行代碼順便把整個(gè)文件格式化了一遍。所以還得配置 husky + lint-staged,提交代碼的時(shí)候按規(guī)范格式化回去,不符合規(guī)范的代碼不允許提交。

如果公司的電腦配置還行的話,可以開發(fā)階段就做相應(yīng)的 lint, 把錯(cuò)誤拋出來,中斷編譯。webpack 可以使用 eslint-loader,stylelint-webpack-plugin;vite 可以使用 vite-plugin-eslint,vite-plugin-stylelint;vue-cli 配置幾個(gè)參數(shù)就可以開啟,具體看文檔。

ts-check

什么是 ts-check?舉個(gè)例子,有一個(gè)后端接口的某個(gè)字段名稱變了,由 user_name 改為了 userName,如果沒有配置開發(fā)階段進(jìn)行 ts-check 并把錯(cuò)誤拋出來,那么只能全局查找調(diào)用接口的地方去修改,如果改漏了,那就喜提一個(gè) BUG。

ts-check 可以開發(fā)階段就做,也可以提交代碼的時(shí)候做。開發(fā)階段 webpack 安裝 fork-ts-checker-webpack-plugin ,vite 也是找相應(yīng)的插件(暫時(shí)沒找到用的比較多的)。提交代碼的時(shí)候,結(jié)合 husky 做一次全量的 check (比較耗時(shí)),react 項(xiàng)目執(zhí)行 tsc --noEmit --skipLibCheck,vue 項(xiàng)目執(zhí)行 vue-tsc --noEmit --skipLibCheck

ts-check 能好用的前提是你的項(xiàng)目是 TS 寫的,接口返回值有具體的類型定義,而不是 any。

代碼規(guī)范

主要講講 model,service,presenter,view 這幾層的代碼規(guī)范,之前的文章也有簡單提到過,這里做個(gè)歸納。

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<typeof useModel>;

  1. 每一個(gè)字段都要聲明類型,不要因?yàn)樽侄味嗑陀?Object,[k: string]: string | number | boolean,Record<string, string> 之類的來偷懶。
  2. 可以包含一些簡單邏輯的方法,比如重置 state。
  3. vue 中字段聲明可以移到 useModel 外面,達(dá)到狀態(tài)共享的作用,在 useModel 中 return 出去使用。

service

  1. react 技術(shù)棧,presenter 層調(diào)用的時(shí)候使用單例方法,避免每次re-render 都生成新的實(shí)例。
  2. service 要盡量保持“整潔”,不要直接調(diào)用特定環(huán)境,端的 API,盡量遵循 依賴倒置原則。比如 fetch,WebSocket,cookie,localStorage 等 web 端原生 API 以及 APP 端 JSbridge,不建議直接調(diào)用,而是抽象,封裝成單獨(dú)的庫或者工具函數(shù),保證是可替換,容易 mock 的。Taro,uni-app 等框架的 API 也不要直接調(diào)用,可以放到 presenter 層。組件庫提供的命令式調(diào)用的組件,也不要使用。
  3. service 方法的入?yún)⒁侠?,不要為了適配組件庫而聲明不合理的參數(shù),比如某個(gè)組件返回 string[] 類型的數(shù)據(jù),實(shí)際只需要數(shù)組第一個(gè)元素,參數(shù)聲明為 string 類型即可。2個(gè)以上參數(shù)改為使用對象。
  4. 業(yè)務(wù)不復(fù)雜可以省略 service 層。

service 保證足夠的“整潔”,model 和 service 是可以直接進(jìn)行單元測試的,不需要去關(guān)心是 web 環(huán)境還是小程序環(huán)境。

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. 不要出現(xiàn)過多的邏輯。
  3. 生成 jsx 片段的方法以 render 開頭,比如 renderXXX。
  4. 不管是 react 還是 vue 不要解構(gòu) model,直接 model.xxxx 的方式使用。

view

  1. 組件 props 寫完整類型。
  2. jsx 不要出現(xiàn)嵌套的三元運(yùn)算。
  3. 盡量所有的邏輯都放到 presenter 中。
  4. 不要解構(gòu) presenter 以及 model,以 presenter.xxx,model.xxxx 方式調(diào)用。

store

  1. 不要在外層去使用內(nèi)層的 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);
  },
);

// 響應(yīng)攔截
instance.interceptors.response.use(
  (res) => {
    return Promise.resolve(res.data);
  },
  (error) => {
    message.error(error.message || "網(wǎng)絡(luò)異常");
    return Promise.reject(error);
  },
);

type Request = <T = unknown>(config: AxiosRequestConfig) => Promise<T>;

export const request = instance.request as Request;

  1. 具體接口的請求方法,入?yún)⒓胺祷刂刀家暶黝愋?,參?shù)量最多兩個(gè),body 數(shù)據(jù)命名為 data,非 body 數(shù)據(jù)命名為 params,都是對象類型。
  2. 參數(shù)類型及返回值類型都聲明放在一起,不需要用單獨(dú)的文件夾去放,覺得代碼太多不好看可以用 region 注釋塊折疊起來(vscode 支持)。
  3. 接口請求方法以 fetch,del,submit,post 等單詞開頭。
  4. 建議接口請求方法直接放在組件同級(jí)目錄里,建一個(gè) api.ts 的文件。很多人都習(xí)慣把接口請求統(tǒng)一放到一個(gè) servcies 的文件夾里,但是復(fù)用的接口又有幾個(gè)呢,維護(hù)代碼的時(shí)候在編輯器上跨一大段距離來回切換文件夾真的是很糟糕的開發(fā)體驗(yàn)。
// #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<IEditUserResult>(`${env.API_HOST}/api/user/edit`, {
    method: 'POST',
    data,
    params,
  });
}

// #endregion

上面代碼是工具生成的,下篇說說提升開發(fā)效率及體驗(yàn)的工具。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容