工程化配置
還是開(kāi)發(fā)體驗(yàn)的問(wèn)題,跟開(kāi)發(fā)體驗(yàn)有關(guān)的項(xiàng)目配置無(wú)非就是使用 eslint藤为、prettier怪与、stylelint 統(tǒng)一代碼風(fēng)格
formatting and lint
eslint、prettier缅疟、stylelint 怎么配這里就不說(shuō)了分别,網(wǎng)上文章太多了遍愿。想說(shuō)的是eslint rule 'prettier/prettier': 'error'
一定要開(kāi)啟,以及 stylelint rule 'prettier/prettier': true
也一定要開(kāi)啟耘斩。
雖然配置了eslint沼填、prettier、stylelint括授,但是可能你隊(duì)友的編輯器并沒(méi)有裝相應(yīng)的插件坞笙,格式化用的也不是 prettier,然后他修改一行代碼順便把整個(gè)文件格式化了一遍刽脖。所以還得配置 husky + lint-staged羞海,提交代碼的時(shí)候按規(guī)范格式化回去,不符合規(guī)范的代碼不允許提交曲管。
如果公司的電腦配置還行的話却邓,可以開(kāi)發(fā)階段就做相應(yīng)的 lint, 把錯(cuò)誤拋出來(lái)院水,中斷編譯腊徙。webpack 可以使用 eslint-loader,stylelint-webpack-plugin檬某;vite 可以使用 vite-plugin-eslint撬腾,vite-plugin-stylelint;vue-cli 配置幾個(gè)參數(shù)就可以開(kāi)啟恢恼,具體看文檔民傻。
ts-check
什么是 ts-check?舉個(gè)例子场斑,有一個(gè)后端接口的某個(gè)字段名稱變了漓踢,由 user_name 改為了 userName,如果沒(méi)有配置開(kāi)發(fā)階段進(jìn)行 ts-check 并把錯(cuò)誤拋出來(lái)漏隐,那么只能全局查找調(diào)用接口的地方去修改喧半,如果改漏了,那就喜提一個(gè) BUG青责。
ts-check 可以開(kāi)發(fā)階段就做挺据,也可以提交代碼的時(shí)候做。開(kāi)發(fā)階段 webpack 安裝 fork-ts-checker-webpack-plugin 脖隶,vite 也是找相應(yīng)的插件(暫時(shí)沒(méi)找到用的比較多的)扁耐。提交代碼的時(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 寫(xiě)的,接口返回值有具體的類型定義心墅,而不是 any酿矢。
代碼規(guī)范
主要講講 model榨乎,service,presenter瘫筐,view 這幾層的代碼規(guī)范蜜暑,之前的文章也有簡(jiǎn)單提到過(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>;
- 每一個(gè)字段都要聲明類型肛捍,不要因?yàn)樽侄味嗑陀?
Object
,[k: string]: string | number | boolean
之众,Record<string, string>
之類的來(lái)偷懶拙毫。 - 可以包含一些簡(jiǎn)單邏輯的方法,比如重置 state棺禾。
- vue 中字段聲明可以移到 useModel 外面缀蹄,達(dá)到狀態(tài)共享的作用,在 useModel 中 return 出去使用膘婶。
service
- react 技術(shù)棧缺前,presenter 層調(diào)用的時(shí)候使用單例方法,避免每次re-render 都生成新的實(shí)例悬襟。
- service 要盡量保持“整潔”衅码,不要直接調(diào)用特定環(huán)境,端的 API脊岳,盡量遵循 依賴倒置原則逝段。比如 fetch,WebSocket割捅,cookie惹恃,localStorage 等 web 端原生 API 以及 APP 端 JSbridge,不建議直接調(diào)用棺牧,而是抽象,封裝成單獨(dú)的庫(kù)或者工具函數(shù)朗儒,保證是可替換颊乘,容易 mock 的。Taro醉锄,uni-app 等框架的 API 也不要直接調(diào)用乏悄,可以放到 presenter 層。組件庫(kù)提供的命令式調(diào)用的組件恳不,也不要使用檩小。
- service 方法的入?yún)⒁侠恚灰獮榱诉m配組件庫(kù)而聲明不合理的參數(shù)烟勋,比如某個(gè)組件返回 string[] 類型的數(shù)據(jù)规求,實(shí)際只需要數(shù)組第一個(gè)元素筐付,參數(shù)聲明為 string 類型即可。2個(gè)以上參數(shù)改為使用對(duì)象阻肿。
- 業(yè)務(wù)不復(fù)雜可以省略 service 層瓦戚。
service 保證足夠的“整潔”,model 和 service 是可以直接進(jìn)行單元測(cè)試的丛塌,不需要去關(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;
- 處理 view 事件的方法以 handle 或 on 開(kāi)頭。
- 不要出現(xiàn)過(guò)多的邏輯赴邻。
- 生成 jsx 片段的方法以 render 開(kāi)頭印衔,比如 renderXXX。
- 不管是 react 還是 vue 不要解構(gòu) model姥敛,直接 model.xxxx 的方式使用奸焙。
view
- 組件 props 寫(xiě)完整類型。
- jsx 不要出現(xiàn)嵌套的三元運(yùn)算徒溪。
- 盡量所有的邏輯都放到 presenter 中忿偷。
- 不要解構(gòu) presenter 以及 model,以 presenter.xxx臊泌,model.xxxx 方式調(diào)用鲤桥。
store
- 不要在外層去使用內(nèi)層的 store。
接口請(qǐng)求方法
- 封裝的接口請(qǐng)求方法支持泛型
import axios, { AxiosRequestConfig } from "axios";
import { message } from "ant-design-vue";
const instance = axios.create({
timeout: 30 * 1000,
});
// 請(qǐng)求攔截
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;
- 具體接口的請(qǐng)求方法渠概,入?yún)⒓胺祷刂刀家暶黝愋筒璧剩瑓?shù)量最多兩個(gè),body 數(shù)據(jù)命名為 data播揪,非 body 數(shù)據(jù)命名為 params贮喧,都是對(duì)象類型。
- 參數(shù)類型及返回值類型都聲明放在一起猪狈,不需要用單獨(dú)的文件夾去放箱沦,覺(jué)得代碼太多不好看可以用 region 注釋塊折疊起來(lái)(vscode 支持)。
- 接口請(qǐng)求方法以 fetch雇庙,del谓形,submit,post 等單詞開(kāi)頭疆前。
- 建議接口請(qǐng)求方法直接放在組件同級(jí)目錄里寒跳,建一個(gè) api.ts 的文件。很多人都習(xí)慣把接口請(qǐng)求統(tǒng)一放到一個(gè) servcies 的文件夾里竹椒,但是復(fù)用的接口又有幾個(gè)呢童太,維護(hù)代碼的時(shí)候在編輯器上跨一大段距離來(lái)回切換文件夾真的是很糟糕的開(kāi)發(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 劃水摸魚(yú)糊屎工程師
*
* @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
上面代碼是工具生成的,下篇說(shuō)說(shuō)提升開(kāi)發(fā)效率及體驗(yàn)的工具书释。