TypeScript:在 redux-saga 的 call() 上使用类型
TypeScript: Use types on call() from redux-saga
如何使用 call()
设置函数的类型?
我有这个功能:
export function apiFetch<T>(url: string): Promise<T> {
return fetch(url).then(response =>
{
if (!response.ok) throw new Error(response.statusText)
return response.json().then(data => data as T);
}
)
}
这个函数可以像这样使用:
let resp = await apiFetch<ServerResponse>("http://localhost:51317/Task");
通过使用您在上面代码中看到的函数,resp
是正确的字符串类型。所以 intellisense 为我提供了 ServerResponse
接口的所有属性。
但是,此函数必须在工作人员内部从 redux-saga
调用,这是不允许的,异步函数:
function* refreshTaskSaga():any {
yield takeEvery("TASK_REFRESH", workerRefreshTaskSaga);
}
function* workerRefreshTaskSaga() {
//I need to call the function here
}
我尝试使用 yield + call 来调用它,正如 redux-saga
文档所说:
a) let resp = yield call(apiFetch, "http://localhost:51317/Task");
b) let resp = yield call(apiFetch<ServerResponse>, "http://localhost:51317/Task");
第一个选项,按预期执行函数,但是 resp
具有 any
类型。
第二个选项抛出异常。
No overload matches this call.
The last overload gave the following error.
Argument of type 'boolean' is not assignable to parameter of type '{ context: unknown; fn: (this: unknown, ...args: any[]) => any; }'.ts(2769)
effects.d.ts(499, 17): The last overload is declared here.
知道调用它并且不丢失类型的正确语法吗?
不幸的是,yield
的左侧总是类型为any
。这是因为生成器函数原则上可以用任何值恢复。当 运行 生成器时,Redux saga 以可预测的方式运行,但是没有什么可以阻止某人编写其他代码来遍历您的 saga 并为您提供与您产生的内容无关的值,如:
const iterator = workerRefreshTaskSaga();
iterator.next();
// You might have been expecting a ServerResponse, but too bad, you're getting a string.
iterator.next('hamburger');
只有你可以假设 redux saga 是你的生成器 运行 你才能对类型进行预测,而打字稿没有办法说 "assume this generator will be run by redux saga (and all the implications that includes)".
因此您需要自己添加类型。例如:
const resp: ServerResponse = yield call(apiFetch, 'url');
这确实意味着您有责任确保类型正确。由于打字稿只能告诉它它是一个 any
,它会相信你说的任何类型。因此 typescript 可以验证此后的代码是否与 ServerResponse
正确交互,但如果它实际上不是 ServerResponse
,则 typescript 无法向您指出这一点。
为了获得更多类型安全,我经常做的一件事是使用 ReturnType
,如:
const output: ReturnType<typeof someFunction> = yield call(someFunction);
我仍然要知道 ReturnType<typeof someFunction>
是否正确,但假设我这样做了,那么如果有人更改 someFunction 的实现以使其变为 return 不同的东西,输出的类型将更新以匹配。
阅读:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html,我意识到我们可以将yield类型设置为Generator Type
中的第三个参数
import { AnyAction } from "redux";
import { call, put, fork, takeLatest, StrictEffect } from "redux-saga/effects";
import { apiRequest } from "api/requests";
import { setAuthenticationLoader, setLoginError, setToken } from "./actions";
import { sagaTypes } from "./types";
import { LoginResponse } from "api/requests/authentication";
export function* requestLogin(
action: AnyAction
): Generator<StrictEffect, any, LoginResponse> {
const setError = (err?: any) => put(setLoginError(err));
yield put(setAuthenticationLoader(true));
yield setError();
try {
const data = yield call(apiRequest.authentication.login, action.payload);
if (!data.token) setError(data);
else yield put(setToken(data.token));
} catch (err) {
yield setError(err);
} finally {
yield put(setAuthenticationLoader(false));
}
}
function* watchLoginRequest() {
yield takeLatest(sagaTypes.REQUEST_LOGIN, requestLogin);
}
export const authenticationSagas = [fork(watchLoginRequest)];
如何使用 call()
设置函数的类型?
我有这个功能:
export function apiFetch<T>(url: string): Promise<T> {
return fetch(url).then(response =>
{
if (!response.ok) throw new Error(response.statusText)
return response.json().then(data => data as T);
}
)
}
这个函数可以像这样使用:
let resp = await apiFetch<ServerResponse>("http://localhost:51317/Task");
通过使用您在上面代码中看到的函数,resp
是正确的字符串类型。所以 intellisense 为我提供了 ServerResponse
接口的所有属性。
但是,此函数必须在工作人员内部从 redux-saga
调用,这是不允许的,异步函数:
function* refreshTaskSaga():any {
yield takeEvery("TASK_REFRESH", workerRefreshTaskSaga);
}
function* workerRefreshTaskSaga() {
//I need to call the function here
}
我尝试使用 yield + call 来调用它,正如 redux-saga
文档所说:
a) let resp = yield call(apiFetch, "http://localhost:51317/Task");
b) let resp = yield call(apiFetch<ServerResponse>, "http://localhost:51317/Task");
第一个选项,按预期执行函数,但是 resp
具有 any
类型。
第二个选项抛出异常。
No overload matches this call.
The last overload gave the following error.
Argument of type 'boolean' is not assignable to parameter of type '{ context: unknown; fn: (this: unknown, ...args: any[]) => any; }'.ts(2769)
effects.d.ts(499, 17): The last overload is declared here.
知道调用它并且不丢失类型的正确语法吗?
不幸的是,yield
的左侧总是类型为any
。这是因为生成器函数原则上可以用任何值恢复。当 运行 生成器时,Redux saga 以可预测的方式运行,但是没有什么可以阻止某人编写其他代码来遍历您的 saga 并为您提供与您产生的内容无关的值,如:
const iterator = workerRefreshTaskSaga();
iterator.next();
// You might have been expecting a ServerResponse, but too bad, you're getting a string.
iterator.next('hamburger');
只有你可以假设 redux saga 是你的生成器 运行 你才能对类型进行预测,而打字稿没有办法说 "assume this generator will be run by redux saga (and all the implications that includes)".
因此您需要自己添加类型。例如:
const resp: ServerResponse = yield call(apiFetch, 'url');
这确实意味着您有责任确保类型正确。由于打字稿只能告诉它它是一个 any
,它会相信你说的任何类型。因此 typescript 可以验证此后的代码是否与 ServerResponse
正确交互,但如果它实际上不是 ServerResponse
,则 typescript 无法向您指出这一点。
为了获得更多类型安全,我经常做的一件事是使用 ReturnType
,如:
const output: ReturnType<typeof someFunction> = yield call(someFunction);
我仍然要知道 ReturnType<typeof someFunction>
是否正确,但假设我这样做了,那么如果有人更改 someFunction 的实现以使其变为 return 不同的东西,输出的类型将更新以匹配。
阅读:https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-6.html,我意识到我们可以将yield类型设置为Generator Type
中的第三个参数import { AnyAction } from "redux";
import { call, put, fork, takeLatest, StrictEffect } from "redux-saga/effects";
import { apiRequest } from "api/requests";
import { setAuthenticationLoader, setLoginError, setToken } from "./actions";
import { sagaTypes } from "./types";
import { LoginResponse } from "api/requests/authentication";
export function* requestLogin(
action: AnyAction
): Generator<StrictEffect, any, LoginResponse> {
const setError = (err?: any) => put(setLoginError(err));
yield put(setAuthenticationLoader(true));
yield setError();
try {
const data = yield call(apiRequest.authentication.login, action.payload);
if (!data.token) setError(data);
else yield put(setToken(data.token));
} catch (err) {
yield setError(err);
} finally {
yield put(setAuthenticationLoader(false));
}
}
function* watchLoginRequest() {
yield takeLatest(sagaTypes.REQUEST_LOGIN, requestLogin);
}
export const authenticationSagas = [fork(watchLoginRequest)];