可以將資料做分層化的處理,也就是單一職責原則。
export interface IBase<T> {
findAll(): Promise<T[] | null>;
findOne(id: any): Promise<T | null>;
create(date: Omit<T, 'id'>): Promise<T | null>;
update(id: any, date: Partial<Omit<T, 'id'>>): Promise<T | null>;
delete(id: any): Promise<void>;
}
export interface
就是把一個物件結構的描述從一個模組拿出來,讓其他模組也能使用這個結構定義。IBase
前綴 "I"明確地表示這是一個接口,所以IBase
表示這是一個基礎接口。T
使這個接口可以適用於各種類型的資料,不限於特定資料結構。Omit
和 Partial
來靈活地定義資料結構,避免在創建和更新時強制要求提供所有欄位。findAll(): Promise<T[] | null>
:它會取得資料庫中所有的記錄。findOne(id: any): Promise<T | null>
:根據 ID 查找特定的資料。create(data: Omit<T, 'id'>): Promise<T | null>
:接受一個不包含 id
的資料物件(Omit<T, 'id'>
),用於在資料庫中建立新記錄。update(id: any, data: Partial<Omit<T, 'id'>>): Promise<T | null>
:接受一個 ID 和一個部分更新的資料物件(Partial<Omit<T, 'id'>>
),用於根據 ID 更新現有記錄。
delete(id: any): Promise<void>
:
根據所給的 ID 刪除資料記錄,void
表示操作完成且無返回值,於刪除資料庫中的記錄。
export interface IBase<T> {
findAll(trx?: Knex.Transaction): Promise<T[] | null>;
findOne(id: any, trx?: Knex.Transaction): Promise<T | null>;
create(data: Omit<T, 'id'>, trx?: Knex.Transaction): Promise<T | null>;
update(id: any, data: Partial<Omit<T, 'id'>>, trx?: Knex.Transaction): Promise<T | null>;
delete(id: any, trx?: Knex.Transaction): Promise<void>;
}
created_at
和 updated_at
)、唯一標識碼(UUID)、系統內部狀態。T
表示單一的一個資料元素,而 T[]
則表示一個包含多個資料元素的陣列。也就是說,查詢所有資料用 T[]
表示資料陣列,查詢單一資料或創建新資料用 T
表示單個資料。void
是 TypeScript 和 JavaScript 中用來表示「沒有返回值」的函數。export abstract class Base<T> implements IBase<T> {
constructor({ knexSql, tableName }: { knexSql: Knex, tableName?: string }) {
}
}
constructor
方法:export abstract class Base<T> implements IBase<T> {
//定義屬性:
protected knexSql: Knex;
protected tableName: string = '';
protected schema = {};
//在建構函數中初始化和設定這些屬性,以確保類的實例具有正確的初始狀態。
constructor({ knexSql, tableName }: { knexSql: Knex, tableName?: string }) {
//設定值:
this.knexSql = knexSql;
if (tableName) this.tableName = tableName;
}
Lodash 是一個很實用的function集合包,包含了所有的函數和功能。目的是為了簡化重複性的代碼,減少開發時間和錯誤。
npm install lodash
這個是 TypeScript 的型別定義包,用於 Lodash 函式庫。當你在用 TypeScript 開發時,它提供 Lodash 函數的型別定義,這樣編譯器在使用 Lodash 時得到自動完成功能和型別檢查。
npm install --save-dev @types/lodash
private DBData2DataObject = (data) => {
const transform = mapValues(data, (value, key) => {
if (['updatedAt', 'createdAt'].includes(key)) return new Date(value);
// check if a string is json
if (typeof value === "object") return JSON.stringify(value);
return value;
})
}
這段程式碼的目的是將輸入資料中的 updatedAt
和 createdAt
字段轉換為 Date
對象,並將所有物件型別的值轉換為 JSON 字符串。
export const isJson = (value: string) => {
try {
return Boolean(JSON.parse(value));
} catch (e) {
return false;
}
}
export const isJson = (value: string) => {}
try
區塊:try {
return Boolean(JSON.parse(value));
}
catch
區塊:catch (e) {
return false;
}
private DBData2DataObject = (data: any) => {
const transform = mapValues(data, (value, key) => {
if (['updatedAt', 'createdAt'].includes(key)) return new Date(value);
if (isJson(value)) return JSON.parse(value);
return value;
});
return mapKeys(transform, (_value, key) => camelCase(key));
}
private DBData2DataObject = (data: any) => {}
private DataObject2DBData = (data: any) => {
const transform = mapValues(data, (value, key) => {
if (['updatedAt', 'createdAt'].includes(key)) return new Date(value);
if (isJson(value)) return JSON.parse(value);
return value;
});
return mapKeys(transform, (_value, key) => snakeCase(key));
}
Date
物件。public findAll = async (trx?: Knex.Transaction) => {
//select col1, col2, ...from tableName
let sqlBuilder = this.knexSql(this.tableName).select(this.schema);
if (trx) sqlBuilder = sqlBuilder.transacting(trx);
const result = await sqlBuilder;
if (isEmpty(result)) return null;
return result.map(this.DBData2DataObject) as T[];
}
public findAll = async (trx?: Knex.Transaction) => {}
let sqlBuilder = this.knexSql(this.tableName).select(this.schema);
if (trx) sqlBuilder = sqlBuilder.transacting(trx);
如果有提供 trx 交易物件,則將查詢納入該交易,以確保資料的一致性和原子性。
const result = await sqlBuilder;
if (isEmpty(result)) return null;
return result.map(this.DBData2DataObject) as T[];
public findOne = async (id: any, trx?: Knex.Transaction) => {
let sqlBuilder = this.knexSql(this.tableName).select(this.schema).where(id);
if (trx) sqlBuilder = sqlBuilder.transacting(trx);
const result = await sqlBuilder;
if (isEmpty(result)) return null;
return this.DBData2DataObject(result[0]) as T;
}
1. 定義方法:
public findOne = async (id: any, trx?: Knex.Transaction) => { ... }
let sqlBuilder = this.knexSql(this.tableName).select(this.schema).where(id);
if (trx) sqlBuilder = sqlBuilder.transacting(trx);
knexSql
方法來構建 SQL 查詢,選擇 schema
中的字段,並根據 id
進行查詢。trx
,則使用 transacting
方法將查詢包含在交易中。3. 執行查詢:
const result = await sqlBuilder;
執行查詢並等待結果,將查詢結果存儲在 result
變量中。
4. 檢查結果是否為空:
if (isEmpty(result)) return null;
isEmpty
函數檢查結果是否為空。null
。5. 轉換並返回結果:
return this.DBData2DataObject(result[0]) as T;
DBData2DataObject
方法來完成的。T
返回,這意味著返回的物件具有型別 T
。public create = async (data: Omit<T, 'id'>, trx?: Knex.Transaction) => {
let sqlBuilder = this.knexSql(this.tableName).insert(this.DataObject2DBData(data));
if (trx) sqlBuilder = sqlBuilder.transacting(trx);
const result = await sqlBuilder;
if (isEmpty(result)) return null;
const id = result[0];
return await this.findOne(id, trx);
}
public create = async (data: Omit<T, 'id'>, trx?: Knex.Transaction) => {
let sqlBuilder = this.knexSql(this.tableName).insert(this.DataObject2DBData(data));
if (isEmpty(result)) return null;
const id = result[0];
return await this.findOne(id, trx);
public update = async (id: any, data: Partial<Omit<T, 'id'>>, trx?: Knex.Transaction) => {
let sqlBuilder = this.knexSql(this.tableName).update(this.DataObject2DBData(data).where({ id }));
if (trx) sqlBuilder = sqlBuilder.transacting(trx);
await sqlBuilder;
return await this.findOne(id, trx);}
let sqlBuilder = this.knexSql(this.tableName).update(this.DataObject2DBData(data)).where({ id });
knexSql
方法來構建更新查詢,指定更新的表 this.tableName
。this.DataObject2DBData(data)
方法將 data
轉換為適合資料庫的格式。where
方法指定更新條件,即 id
。if (trx) sqlBuilder = sqlBuilder.transacting(trx);如果提供了交易
trx
,則使用 transacting
方法將查詢包含在交易中。3. 執行更新查詢:await sqlBuilder;執行更新查詢,等待更新操作完成。4. 查詢並返回更新後的記錄:
return await this.findOne(id, trx);使用
findOne
方法根據 id
查詢並返回更新後的記錄,並傳遞可選的交易 trx
。public delete = async (id: any, trx?: Knex.Transaction) => {程式碼解說:
let sqlBuilder = this.knexSql(this.tableName).where({ id }).del();
if (trx) sqlBuilder = sqlBuilder.transacting(trx);
await sqlBuilder;
return
}
return;
Production
的 Schema:export interface Product {
//符合 TypeScript 的語法和類型系統
id: number;
name: string;
amount: number;
description: string;
pre_order: number;
price: number;
}
Production
的 型別:export interface IProductModel extends IBase<Product> {}
簡單來說,IProductModel
介面是 IBase
介面的具體化,專門用於操作 Product
類型的資料。這樣定義能讓代碼更具模組化和可重用性。
export class ProductModel extends Base<Product> implements IProductModel {}
tableName = 'products';
schema
屬性:schema = {
id: 'id',
name: 'name',
amount: 'amount',
description: 'description',
pre_order: 'pre_order',
price: 'price'
};
constructor({ knexSql, tableName }: { knexSql: Knex; tableName?: string }) {
super({ knexSql, tableName });
}
super
),將這些參數傳遞給父類別進行初始化。5.將資料表的名稱設定為 products:
tableName = "products";
createModel
:專門用來生成模型(model)static createModel = ({
knexSql,
tableName,
}: {
knexSql: Knex;
tableName?: string;
}) => {
return new ProductModel({ knexSql, tableName })
}
import { IProductModel, ProductModel } from "@/model/products";
import { Knex } from "knex";
2.使用 models 進行資料庫操作:
export const modelManager = ({knexSql}: {knexSql: Knex}) => {
const productModel = ProductModel.createModel({knexSql});
return {productModel}
}
modelManagerFile
文件中導入 modelManager
,並用配置好的 knex
物件初始化它,然後可以使用 models
進行各種資料庫操作。3.定義一個模型相關的環境資訊:
export interface ModelContext {
productModel: IProductModel;
}
export
關鍵字允許這個接口在其他文件中被導入使用。productModel
必須符合 IProductModel
介面所定義的結構和型別。import { ModelContext, modelManager } from './manager/modelManager';//新增
class App {
private modelCtx: ModelContext;//新增
constructor() {
this.modelCtx = modelManager({ knexSql: this.knexSql });//新增
}
import { ModelContext, modelManager } from './manager/modelManager';
目的是從指定的路徑(./manager/modelManager)匯入兩個模組:ModelContext 和 modelManager。
2. 類別定義:
class App {
private modelCtx: ModelContext;
目的是在 App
類別中定義一個私有屬性 modelCtx
,它的類型為 ModelContext
。主要用途:
3.建構函數:
constructor() {
this.modelCtx = modelManager({ knexSql: this.knexSql });
}
目的是在 App
類別的建構函數中初始化資料模型上下文。
export const modelManager = ({ knexSql }: { knexSql: Knex }): ModelContext => {
}
ModelContext 是為了指定 modelManager 函數的返回值類型。