問題來源
爲了簡化 ctx.body
賦值操作,想要在 ctx
擴展兩個自定義方法, success
及 error
使用起來如下
// 響應成功狀態請求
ctx.success({
username: 'test'
});
// 等價於
ctx.body = {
code: 1,
data: {
username: 'test'
}
};
// 響應失敗狀態請求
ctx.error("參數不正確");
// 等價於
ctx.body = {
code: 0,
data: null,
msg: '參數不正確'
};
success
、error
這兩個方法的擴展是基於 koa
中間件的套路來做的
其核心代碼如下
const koaResponse = async (ctx: Koa.Context, next: Koa.Next) => {
ctx.success = (data = null, status = Types.EResponseStatus.SUCCESS) => {
ctx.status = status;
ctx.body = {
code: Types.EResponseCode.SUCCESS,
data
};
};
ctx.error = (
msg = Types.EResponseMsg.DEFAULT_ERROR,
data = null,
status = Types.EResponseStatus.SUCCESS
) => {
ctx.status = status;
ctx.body = {
code: Types.EResponseCode.ERROR,
data,
msg
};
};
await next();
};
具體使用時便會遇到問題
// 給路由添加了一個 請求參數 校驗的中間件 和 一個 請求核心邏輯處理的中間件
router.get('/', Validator.validLogin, UserController.login);
請求參數校驗中間件
// 這裏使用假數據做測試
class Validator {
static async validLogin(ctx: Koa.Context, next: Koa.Next) {
const result = loginModel.check({
username: 'test',
email: '[email protected]',
age: 20
});
if (
(Object.keys(result) as ['username', 'email', 'age']).filter((name) => result[name].hasError)
.length > 0
) {
ctx.error(Types.EResponseMsg.INVALID_PARAMS); // error 類型丟失,沒有代碼提示
} else {
await next();
}
}
}
請求核心邏輯處理中間件
class UserController {
static async login(ctx: Koa.Context, next: Koa.Next) {
ctx.success({ // success 類型丟失,沒有代碼提示
username: 'test'
});
await next();
}
}
問題解決過程
試驗一
app.ts做如下修改
// 實例化 app 時,傳入自定義屬性作爲 defaultContext
const app = new Koa<{}, {
success: Function;
error: Function;
}>();
// logger 中做測試
app.use(async (ctx, next) => {
// ctx 類型爲
/* (parameter) ctx: Koa.ParameterizedContext<{}, {
success: Function;
error: Function;
}> */
const start = Date.now();
app.context.success // 具有正確類型提示 (property) success: Function
ctx.success // 具有正確類型提示 (property) success: Function
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
爲上面👆 logger 中 ctx 指定類型聲明
app.use(async (ctx: Koa.context, next: Koa.Next) => {
// ctx 類型爲 (parameter) ctx: Koa.Context
const start = Date.now();
app.context.success // 具有正確類型提示 (property) success: Function
ctx.success // 類型丟失
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
發生上面的問題原因在於,app.use
中具有類型推斷,當不手動設置 ctx
類型時,其推斷正是我們想要的
// 就是這個東東
Koa.ParameterizedContext<{}, {
success: Function;
error: Function;
}>
當手動設置後變爲
Koa.context // 其類型聲明中是不具備 success、error 這兩個類型的
那麼解決方案來了,ctx
的類型如果都是下面這個,是不是就對了
Koa.ParameterizedContext<{}, {
success: Function;
error: Function;
}>
// logger
app.use(async (ctx: Koa.ParameterizedContext<{}, {
success: Function;
error: Function;
}>, next) => {
const start = Date.now();
app.context.success // 具有正確類型提示 (property) success: Function
ctx.success // 具有正確類型提示 (property) success: Function
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
這樣可能引出另一個問題
ctx
有很多地方在使用,那麼每個 ctx
的類型每次都要 這麼聲明一遍 或者 定義一個全局的類型來導入使用(每次導入也難受)
那能不能通過 Koa
聲明合併的方式,爲 Context
全局添加 success
、error
類型聲明
於是有了實驗二
實驗二
打開 node_modules/@types/koa/index.d.ts
,大致瀏覽會看到這麼個東西
type DefaultStateExtends = any;
/**
* This interface can be augmented by users to add types to Koa's default state
*/
interface DefaultState extends DefaultStateExtends {}
type DefaultContextExtends = {};
/**
* This interface can be augmented by users to add types to Koa's default context
*/
interface DefaultContext extends DefaultContextExtends {
/**
* Custom properties.
*/
[key: string]: any;
}
重點
- DefaultState 可以擴展
state
- DefaultContext 可以擴展
context
來看看怎麼進行聲明合併,src/types/index.ts
declare module 'koa' {
interface DefaultState {
stateProperty: boolean;
}
interface DefaultContext {
success: TSuccess;
error: TError;
}
}
再來看看 app.ts
// logger ctx 類型寫或者不寫,結果都是正確的
app.use(async (ctx: Koa.Context, next) => {
const start = Date.now();
app.context.success // 具有正確類型提示 (property) success: Function
ctx.success // 具有正確類型提示 (property) success: Function
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
此時已經達到我買的目的了,反過來看一下我們將 類型合併聲明 放到了什麼地方,OK,here, src/types/index.ts
那爲什麼不放到 src/global.d.ts
中呢,測試過就會發現,如果放到這裏面,我們的類型合併聲明就會失敗,失敗方是 @types/koa
中提供的類型聲明。原因就在於,我們導入的 koa
的類型聲明被 src/global.d.ts
中的聲明給攔截了,導致並未讀取 @types/koa
中提供的類型聲明
問題到此就基本解決了
測試項目 koa2-ts