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