如何結合整潔架構和MVP模式提升前端開發體驗(三)

2022-09-08 06:00:22

工程化設定

還是開發體驗的問題,跟開發體驗有關的專案設定無非就是使用 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<typeof useModel>;

  1. 每一個欄位都要宣告型別,不要因為欄位多就用 Object[k: string]: string | number | booleanRecord<string, string> 之類的來偷懶。
  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 = <T = unknown>(config: AxiosRequestConfig) => Promise<T>;

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<IEditUserResult>(`${env.API_HOST}/api/user/edit`, {
    method: 'POST',
    data,
    params,
  });
}

// #endregion

上面程式碼是工具生成的,下篇說說提升開發效率及體驗的工具。