更新於 2025/01/19閱讀時間約 40 分鐘

第三方金流串接 – 產品Model(模型)

    ※ 認識MVC架構:

    • Model (模型):
      • Data Model: 負責資料庫的交互與資料的映射,通常用來定義資料結構和進行資料庫操作。
      • Business Model: 處理商業邏輯,將原始資料進行處理和轉換(在這次的設計中,這部分由 Controller 處理,省略business model的開發。)。
    • Controller (控制器):
      • 從 Model 取得資料並進行處理,根據商業邏輯整理好後傳給 View。
      • 就是負責調控和協調。
    • View (視圖):
      • 負責將資料呈現給使用者,通常是前端的畫面渲染。View 不包含任何邏輯處理,專門做畫面的渲染。

    ※ MVC架構的優點:

    可以將資料做分層化的處理,也就是單一職責原則。

    raw-image




    ※ 建立Model :model --> base.ts

    建立Model 的主要功能有:

    • 執行 CRUD(建立、讀取、更新、刪除)操作。

    ※ 定義接口(IBase) :

    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: 使用泛型 T 使這個接口可以適用於各種類型的資料,不限於特定資料結構。
    • Promise: 使用 Promise 以便在非同步操作(如資料庫查詢)完成後處理結果。
    • Omit 和 Partial: 使用 OmitPartial 來靈活地定義資料結構,避免在創建和更新時強制要求提供所有欄位。
    • 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表示操作完成且無返回值,於刪除資料庫中的記錄。

    ※ 建立database transaction,資料庫交易 :

    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>;
    }

    程式碼解說:

    • trx?表示這個參數是可選的,不傳遞也沒關係。它是 Knex.js 用於資料庫交易(transaction)的物件。
    • Omit:目的是排除某個屬性。在創建資料時,由系統自動生成或管理的屬性,有ID、時間戳(created_at 和 updated_at)、唯一標識碼(UUID)、系統內部狀態。
    • findAll(trx?: Knex.Transaction): Promise<T[] | null>; 查找所有記錄,返回一個包含所有記錄的 Promise,類型為陣列(T[]),或是空值(null)。
    • findOne(id: any, trx?: Knex.Transaction): Promise<T | null>; 根據 ID 查找單一記錄,返回一個包含該記錄的 Promise,類型為 T 或是空值(null)。
    • create(data: Omit<T, 'id'>, trx?: Knex.Transaction): Promise<T | null>; 創建一個新的記錄,傳入的資料不包含 ID(Omit<T, 'id'>),返回一個包含創建結果的 Promise,類型為 T 或是空值(null)。
    • update(id: any, data: Partial<Omit<T, 'id'>>, trx?: Knex.Transaction): Promise<T | null>: 說的是,更新指定 ID 的記錄,傳入需要修改的屬性,返回一個包含更新結果的 Promise,類型為 T 或是空值(null)。
    • delete(id: any, trx?: Knex.Transaction): Promise<void>; 根據 ID 刪除記錄,返回一個 Promise,完成後不返回數據。
    • T 表示單一的一個資料元素,而 T[] 則表示一個包含多個資料元素的陣列。也就是說,查詢所有資料用 T[] 表示資料陣列,查詢單一資料或創建新資料用 T 表示單個資料。
    • void 是 TypeScript 和 JavaScript 中用來表示「沒有返回值」的函數。

    ※ 定義抽象類別(Base):

    export abstract class Base<T> implements IBase<T> {
    constructor({ knexSql, tableName }: { knexSql: Knex, tableName?: string }) {

    }
    }

    程式碼解說:

    1. export abstract class Base<T> implements IBase<T>:
      • 定義了一個泛型抽象類別 Base這個框架,來實現 IBase<T> 這個介面。這表示 Base 類別必須包含 IBase 介面定義的方法,但因為它是抽象類別,所以不能直接實例化。
    2. constructor 方法
    • constructor({ knexSql, tableName }: { knexSql: Knex, tableName?: string }) 允許在創建 Base 類實例時傳入一個包含 knexSql 和可選的 tableName 的物件。
    1. 從 App 傳入參數
    • 在 App 中,可以根據需要傳入這些參數,當創建 Base 或其子類的實例時,提供所需的資料庫連接和表名。
    • 如果 tableName 未傳入,則可以在子類或其他地方決定其具體值。


    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;
    }

    程式碼解說:

    1. protected knexSql: Knex;
      • 定義了一個受保護的屬性 knexSql,類型為 Knex。這表示這個屬性只能在 Base 類別及其子類中使用。目的是增強程式碼的封裝性和安全性用來存儲資料庫連接實例。
    2. protected tableName: string = '';
      • 定義了一個受保護的屬性 tableName,類型為 string,並預設值為空字串。
    3. protected schema = {};
      • 定義了一個受保護的屬性 schema,預設為空物件。這可能用來存儲資料庫表格的結構定義。
    4. this.knexSql = knexSql;:將傳入的 knexSql 參數賦值給 this.knexSql,將資料庫連接實例保存到該類的屬性中。
    5. if (tableName) this.tableName = tableName;:是否提供了 tableName 參數,如果提供,則將其賦值給 this.tableName。

    ※ 將資料庫中的資料轉換為應用程式中的資料格式:

    認識lodash:

    Lodash 是一個很實用的function集合包,包含了所有的函數和功能。目的是為了簡化重複性的代碼,減少開發時間和錯誤。

    Lodash 中常用的功能:

    1. 陣列處理:_.map, _.filter, _.reduce 等函數,讓你可以方便地操作陣列。
    2. 物件處理:_.assign, _.cloneDeep, _.merge 等函數,讓物件的操作變得簡單。
    3. 數據查詢:_.find, _.findIndex, _.includes 等函數,幫助你快速查找數據。
    4. 函數處理:_.debounce, _.throttle, _.once 等函數,控制函數的調用次數。
    5. 字串處理:_.camelCase, _.kebabCase, _.startCase 等函數,方便格式化字串。

    安裝lodash:

    npm install lodash


    認識@types/lodash:

    這個是 TypeScript 的型別定義包,用於 Lodash 函式庫。當你在用 TypeScript 開發時,它提供 Lodash 函數的型別定義,這樣編譯器在使用 Lodash 時得到自動完成功能和型別檢查。

    安裝@types/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;
    })
    }

    程式碼解說:

    1. private DBData2DataObject = (data) => { ... }:這是一個箭頭函數,它接收一個 data 參數。這個函數被定義為 private,表示它只能在類別內部使用。
    2. const transform = mapValues(data, (value, key) => { ... }):這一行使用 mapValues 函數來遍歷 data 中的每個鍵值對。mapValues 是一個類似於 Array.prototype.map 的函數,但它是用來處理物件的。對於 data 中的每個鍵值對,都會執行提供的箭頭函數 (value, key) => { ... }。
    3. if (['updatedAt', 'createdAt'].includes(key)) return new Date(value);:這一行檢查當前鍵(key)是否為 updatedAt 或 createdAt,如果是,則將對應的值轉換為 Date 對象並返回。
    4. if (typeof value === "object") return JSON.stringify(value);:這一行檢查當前值(value)是否為物件,如果是,則將其轉換為 JSON 字符串並返回。
    5. return value;:如果值不是物件,則直接返回該值。
    6. 最後,transform 變數將包含轉換後的資料。

    這段程式碼的目的是將輸入資料中的 updatedAtcreatedAt 字段轉換為 Date 對象,並將所有物件型別的值轉換為 JSON 字符串。

    ※ 判斷輸入的字串是否為有效的 JSON 格式:utils --> index.ts

    export const isJson = (value: string) => {

    try {

    return Boolean(JSON.parse(value));

    } catch (e) {

    return false;

    }

    }

    程式碼解說:

    1. 函數定義
    export const isJson = (value: string) => {}
    • export:表示這個函數可以被其他模組引用。
    • const isJson:定義了一個常數函數,名稱為 isJson。
    • (value: string):函數接受一個字串類型的參數 value。
    1. try 區塊:
    try {

    return Boolean(JSON.parse(value));

    }
    • JSON.parse(value):嘗試將字串解析為 JSON。
    • Boolean():將解析結果轉換為布林值。如果解析成功,返回 true。
    1. catch 區塊
    catch (e) {

    return false;

    }
    • 如果解析失敗,捕捉錯誤並返回 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 DataObject2DBData = (data: any) => { ... }:這是一個私有的箭頭函數,僅供類的內部使用。
    • 轉換數據(mapValues):
      • 這行代碼會遍歷 data 中的每個屬性,並根據屬性名和屬性值進行處理。
      • 如果屬性名是 updatedAt 或 createdAt,就把這個屬性的值轉換成日期(Date)物件。
      • 如果屬性值是 JSON 字符串,就把這個字符串解析成 JavaScript 物件。
      • 對於其他情況,保持屬性值不變。
    • 鍵名轉換(mapKeys):
      • 這行代碼會遍歷轉換後的每個屬性,將屬性名從蛇形命名法(如 created_at)轉換成駝峰命名法(如 createdAt),使其符合 JavaScript 的命名慣例。

    ※ 導入isJson函數:

    ※ data參數增加any:

    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 物件。
    • 將 JSON 字符串解析為 JavaScript 物件。
    • 將鍵名轉換為蛇形命名法。


    ※ CRUD的function:

    1. 查詢所有記錄:

    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[];
    }

    程式碼解說:

    1. 方法定義
    public findAll = async (trx?: Knex.Transaction) => {}
    • public:表示這個方法是公開的,可以被外部訪問。
    • findAll:方法名稱。
    • async:表示這個方法是非同步的,會返回一個 Promise。
    • trx?: Knex.Transaction:可選的參數 trx,類型為 Knex 的交易物件。
    1. 建立 SQL 查詢
    let sqlBuilder = this.knexSql(this.tableName).select(this.schema);
    • 使用 Knex.js 建立一個 SQL 查詢建構物件(sqlBuilder),從 this.tableName 表中選擇 this.schema 中定義的欄位。
    1. 交易處理
    if (trx) sqlBuilder = sqlBuilder.transacting(trx);

    如果有提供 trx 交易物件,則將查詢納入該交易,以確保資料的一致性和原子性。

    1. 執行查詢
    const result = await sqlBuilder;
    • 執行查詢並等待結果。
    1. 結果處理
    if (isEmpty(result)) return null;
    return result.map(this.DBData2DataObject) as T[];
    • 將資料庫裡面的資料拉出來,如果結果為空,返回 null。
    • 如果有結果,會將結果轉換成資料物件,並以 T[] 型別返回。
    • 使用as T[]的原因:明確地指定返回的結果是一個具有型別 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) => { ... }
    • 這是一個公開的異步箭頭函數 findOne,接收兩個參數:id 和可選的交易 trx。
    • public 表示這個方法可以從類的外部訪問。
    1. 建構 SQL 查詢:
    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);

    }

    程式碼解說:

    1. 方法定義
    public create = async (data: Omit<T, 'id'>, trx?: Knex.Transaction) => {
    • data: Omit<T, 'id'>:傳入的資料不包含 id 屬性,類型為 T(除了 id)。
    1. 建立 SQL 插入查詢
    let sqlBuilder = this.knexSql(this.tableName).insert(this.DataObject2DBData(data));
    • 使用 Knex.js 建立一個插入查詢,將資料插入到 this.tableName 表中,資料經過 this.DataObject2DBData(data) 處理。
    1. 結果處理
    if (isEmpty(result)) return null;
    const id = result[0];
    return await this.findOne(id, trx);
      • 如果有結果,提取插入記錄的 id,並使用 findOne 方法根據 id 查詢並返回新創建的記錄。

      ※ 更新單筆記錄:

      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);}

      程式碼解說:

      1. 建構更新查詢:
      let sqlBuilder = this.knexSql(this.tableName).update(this.DataObject2DBData(data)).where({ id });
      • 使用 knexSql 方法來構建更新查詢,指定更新的表 this.tableName
      • 使用 this.DataObject2DBData(data) 方法將 data 轉換為適合資料庫的格式。
      • 使用 where 方法指定更新條件,即 id
      2. 檢查並處理交易:
      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;
      • 方法完成後不返回任何數據,因為這個方法的主要目的是刪除指定記錄。



    ※  建立產品檔案 :Model -->product.ts

    ※ 定義Production 的 Schema:

    export interface Product {
    //符合 TypeScript 的語法和類型系統
    id: number;
    name: string;
    amount: number;
    description: string;
    pre_order: number;
    price: number;
    }

    程式碼解說:

    • id: number;:產品的唯一標識符,類型為數字。
    • name: string;:產品名稱,類型為字串。
    • amount: number;:產品數量,類型為數字。
    • description: string;:產品描述,類型為字串。
    • pre_order: number;:預購數量,類型為數字。
    • price: number;:產品價格,類型為數字。

    ※ 定義Production 的 型別:

    export interface IProductModel extends IBase<Product> {}

    程式碼解說:

    1. export interface IProductModel extends IBase<Product> {}:
      • export:表示這個介面可以被其他模組引用。
      • interface IProductModel:定義了一個介面 IProductModel。
      • extends IBase<Product>:表示 IProductModel 繼承自 IBase<Product>。
    2. IBase<Product>:這表示 IProductModel 繼承了 IBase 介面的所有方法和屬性,而這些方法和屬性的泛型類型為 Product。這意味著 IProductModel 會擁有 IBase 定義的所有 CRUD 操作(如 findAll, findOne, create, update, delete),但這些操作針對的是 Product 類型的資料。

    簡單來說,IProductModel 介面是 IBase 介面的具體化,專門用於操作 Product 類型的資料。這樣定義能讓代碼更具模組化和可重用性。

    ※ 實作class:

    1. 類別定義
    export class ProductModel extends Base<Product> implements IProductModel {}

    程式碼解說:

    • export class ProductModel:定義並導出一個名為 ProductModel 的類別。
    • extends Base<Product>:表示這個類別繼承自 Base<Product> 類別。
    • implements IProductModel:表示這個類別實現了 IProductModel 介面。
    1. tableName 屬性:
    tableName = 'products';

    程式碼解說:

    • 定義資料表的名稱為 products。
    1. schema 屬性
    schema = {
    id: 'id',
    name: 'name',
    amount: 'amount',
    description: 'description',
    pre_order: 'pre_order',
    price: 'price'
    };

    程式碼解說:

    • 定義資料表的結構,把資料物件的屬性對應到資料庫的欄位,其中value是資料庫裡面的欄位名稱。
    1. 建構函數
    constructor({ knexSql, tableName }: { knexSql: Knex; tableName?: string }) {
    super({ knexSql, tableName });
    }
    • 接受一個包含 knexSql 和可選 tableName 的物件作為參數,並呼叫父類別的建構函數super),將這些參數傳遞給父類別進行初始化。

    5.將資料表的名稱設定為 products:

    tableName = "products";
    1. 靜態方法 createModel:專門用來生成模型(model)
    static createModel = ({
    knexSql,
    tableName,
    }: {
    knexSql: Knex;
    tableName?: string;
    }) => {
    return new ProductModel({ knexSql, tableName })
    }

    程式碼解說:

    • static:表示這是一個靜態方法,可以直接使用 ProductModel.createModel() 來調用。
    • createModel:方法名稱。
    • 參數:接受一個包含 knexSql 和可選 tableName 的物件。
    • 返回:創建並返回一個 ProductModel 的新實例,也就無需每次手動傳入參數來初始化它。

    ※ 建立一個管理者的資料夾,負責建立不同的管理 manager:


    ※ 在manager資料夾中,建立modelManager.ts:

    ※ 設定資料庫模型的管理:

    1. 匯入模組
    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 進行各種資料庫操作。
    • ProductModel.createModel:這是一個靜態方法,用於創建 ProductModel 的實例。
    • 參數 { knexSql }:將 knexSql 物件傳遞給 createModel 方法,這通常是資料庫連接的配置。

    3.定義一個模型相關的環境資訊:

    export interface ModelContext {

    productModel: IProductModel;

    }

    程式碼解說:

    • 接口定義接口用於描述物件的結構和類型。
    • 導出export 關鍵字允許這個接口在其他文件中被導入使用。
    • productModel 必須符合 IProductModel 介面所定義的結構和型別。

    ※ 調用 modelManager 函數:src --> app.ts

    在app.ts新增程式碼:

    import { ModelContext, modelManager } from './manager/modelManager';//新增

    class App {

    private modelCtx: ModelContext;//新增

    constructor() {



    this.modelCtx = modelManager({ knexSql: this.knexSql });//新增



    }
    1. 匯入模組
    import { ModelContext, modelManager } from './manager/modelManager'; 

    程式碼解說:

    目的是從指定的路徑(./manager/modelManager)匯入兩個模組:ModelContext 和 modelManager。

    • ModelContext:這是一個介面,用來描述資料模型上下文的結構和類型。
    • modelManager:這是一個函數,用來初始化和管理資料模型,並返回一個包含所有資料模型實例的物件。

    2. 類別定義:

    class App {

    private modelCtx: ModelContext;

    程式碼解說:

    目的是在 App 類別中定義一個私有屬性 modelCtx,它的類型為 ModelContext。主要用途:

    • 強制型別:透過將 modelCtx 的類型設定為 ModelContext,可以確保 modelCtx 符合特定的結構和類型要求,這有助於在編譯時期捕捉潛在的錯誤。
    • 資料模型管理:modelCtx 用來存儲資料模型的上下文,這樣可以方便地在應用程式的其他地方訪問和操作這些資料模型。
    • 封裝性:private 關鍵字表示 modelCtx 屬性只能在 App 類別內部訪問,這增加了程式碼的封裝性和安全性,防止外部未經授權的訪問和修改。
    • "Ctx" 是 "Context" 的縮寫。通常用來表示一個上下文或環境,用來存儲和共享關鍵資料和設定。

    3.建構函數

    constructor() {

    this.modelCtx = modelManager({ knexSql: this.knexSql });

    }

    程式碼解說:

    目的是在 App 類別的建構函數中初始化資料模型上下文。

    • 呼叫 modelManager 函數,並將 knexSql 資料庫連接實例傳遞給它。
    • modelManager 函數會根據傳入的 knexSql 初始化資料模型,並返回包含這些模型的上下文物件。
    • 這個上下文物件被儲存在 this.modelCtx 屬性中。

    ※ 指定 modelManager 函數的返回值類型:manager --> modelManager.ts

    export const modelManager = ({ knexSql }: { knexSql: Knex }): ModelContext => {



    }

    程式碼解說:

    ModelContext 是為了指定 modelManager 函數的返回值類型。







    分享至
    成為作者繼續創作的動力吧!
    © 2025 vocus All rights reserved.