简体   繁体   English

使用 Typescript 在 Redux-Saga 中输入生成器函数

[英]Typing generator functions in Redux-Saga with Typescript

Recently I've started refactoring my React project to start using Typescript in it.最近我开始重构我的 React 项目以开始在其中使用 Typescript。 I've encountered a problem with typing which I don't know how to overcome.我遇到了一个打字问题,我不知道如何克服。

Tech-stack: Typescript, React, Redux, Redux-Saga技术栈:Typescript,React,Redux,Redux-Saga

I'm also using ESLint with AirBnB ruleset.我也将 ESLint 与 AirBnB 规则集一起使用。

I'm trying to write a saga which uses async function defined in my authorization service.我正在尝试编写一个使用授权服务中定义的异步 function 的传奇。 The code below is my 2nd attempt to write this.下面的代码是我第二次尝试编写此代码。 I've splitted the signIn saga in order not to mix the yield return types as suggested on the end of this article .我拆分了signIn saga,以免混淆本文末尾建议的yield返回类型。

export function* callSignInService(userCredentials: UserCredentialsInterface): 
    Generator<CallEffect, UserAuthDataInterface, UserAuthDataInterface> > 
{
    return yield call(signInService, userCredentials);
}

export function* signIn(action: PayloadAction<UserCredentialsInterface>): 
    Generator<StrictEffect, void, PutEffect> 
{
    try {
        const { payload } = action;
        const userData = yield* callSignInService(payload);

        yield put(signInSuccess(userData));
    } catch (error) {
        yield put(signInFailure(error.message));
    }
}

A simplified version of the signIn service:登录服务的简化版本:

const signIn = async ( userCredentials: UserCredentialsInterface ): Promise<UserAuthDataInterface>
{
    /* ... Some credential format validations ... */
    try {
        const response = await axios.post('someUrl', userCredentials);
        return {
            /* objectContent */
        }: UserAuthDataInterface
    } catch (error) {
        throw new Error(AuthorizationErrorType.SERVER_ERROR);
    }    
}

The problem is I'm still getting an error on:问题是我仍然收到以下错误:

const userData = yield* callSignInService(payload);

saying:说:

TS2766: Cannot delegate iteration to value because the 'next' method of its iterator expects type 'UserAuthDataInterface', but the containing generator will always send 'PutEffect'. TS2766:无法将迭代委托给值,因为其迭代器的“下一步”方法需要类型“UserAuthDataInterface”,但包含的生成器将始终发送“PutEffect”。 Type 'SimpleEffect<"PUT", PutEffectDescriptor>' is missing the following properties from type 'UserAuthDataInterface': user, token类型“SimpleEffect<"PUT", PutEffectDescriptor>”缺少类型“UserAuthDataInterface”中的以下属性:用户、令牌

I understand that the issue is that my signIn generator NextType is set to PutEffect and it expects expression yield signInService(userCredentials) to return PutEffect but that's not my goal.我知道问题是我的登录生成器NextType设置为PutEffect并且它期望表达式yield signInService(userCredentials)返回PutEffect但这不是我的目标。 I want to use the signInService to get UserAuthData and then put these data into signInSuccess action ( signInSuccess is created using action creator).我想使用signInService获取UserAuthData ,然后将这些数据放入signInSuccess操作( signInSuccess是使用操作创建器创建的)。

I've read several resources on how to properly type the generators but still can't get it to work with my example.我已经阅读了一些关于如何正确键入生成器的资源,但仍然无法在我的示例中使用它。 I found information that redux-saga has or had problems regarding the typing but I am not sure if it's still the case in 2021.我发现 redux-saga 在打字方面有或有问题的信息,但我不确定 2021 年是否仍然如此。

Am I missing some obvious things here?我在这里遗漏了一些明显的东西吗? Maybe there is a problem with my attempt to write such Saga.也许我尝试编写这样的 Saga 有问题。 I would be pleased if someone could take a look and give me an advice how to handle this problem.如果有人可以看一下并给我建议如何处理这个问题,我会很高兴。

The first generic type parameter is the type of the yield ed values in the saga.第一个泛型类型参数是 saga 中的yield值的类型。 It's the "steps", in other words.换句话说,这是“步骤”。

Moving callSignInService to a separate function doesn't actually help with the types because we are still yield ing that result.callSignInService移动到单独的 function 实际上对类型没有帮助,因为我们仍在yield该结果。

Your signIn saga has two steps with distinct types -- calling the API and dispatching an action.您的登录传奇有两个不同类型的步骤——调用signIn并调度一个动作。 The generic needs to be a union of the two types.泛型需要是这两种类型的联合。

export function* signIn(
  action: PayloadAction<UserCredentialsInterface>
): Generator<
  // step types
  CallEffect<UserAuthDataInterface> | PutEffect<AnyAction>,
  // return type
  void,
  // intermediate argument
  UserAuthDataInterface
> {
  try {
    const { payload } = action;
    const userData: UserAuthDataInterface = yield call(signInService, payload);

    yield put(signInSuccess(userData));
  } catch (error) {
    yield put(signInFailure(error.message));
  }
}

I'm assuming that your settings require an return type declaration (or else this would be easy.).我假设您的设置需要返回类型声明(否则这很容易。)。 It can be really helpful to remove the declaration temporarily to see what Typescript infers.暂时删除声明以查看 Typescript 推断的内容非常有帮助。 That's how I got this solution.这就是我得到这个解决方案的方式。

Getting a correct return type from yielded effects in redux-saga bothered me for a long time.从 redux-saga 中的 yielded effects 中获取正确的返回类型困扰了我很长时间。 Specifying the type manually is not good enough as that gives too much space for error.手动指定类型还不够好,因为这会给错误留下太多空间。 Since I could not find good solution anywhere, I am sharing solution that I eventually came up with.由于我在任何地方都找不到好的解决方案,所以我分享了我最终想出的解决方案。

We can't get correct return type from regular yield, as it will always be any due to nature of generators.我们无法从常规 yield 获得正确的返回类型,因为由于生成器的性质,它总是any However we can get return type of generator function to whom we delegated control to.但是,我们可以获得我们将控制权委托给的生成器 function 的返回类型。 So instead of yielding a call effect we will be delegating to another generator function that will only yield the effect and will return us the result of the yielded call effect.因此,我们将委托给另一个生成器 function 而不是生成调用效果,它只会生成效果并将返回给我们生成的调用效果的结果。 In the end, usage will be very similiar, but slightly different.最后,用法将非常相似,但略有不同。

The helper generator function is typed and implemented, as follows: (all types are native or imported from 'redux-saga/effects'). helper 生成器 function 的类型和实现如下:(所有类型都是原生的或从'redux-saga/effects'导入的)。

type SagaGenCall<T extends (...args: any[]) => any> =
    Generator<CallEffect<SagaReturnType<T>>, SagaReturnType<T>, any>

const callSaga =
    function*<Fn extends (...args: any[]) => any>(fn: Fn, ...args: Parameters<Fn>): SagaGenCall<Fn> {
        return yield call(fn, ...args);
    };

To delegate control to another generator function we will be using yield * instead of yield .要将控制委托给另一个生成器 function,我们将使用yield *而不是yield And you can see resulting types in a following example along with few examples without the helper.您可以在下面的示例中看到结果类型以及几个没有帮助程序的示例。

const asyncFnString = async () => 'test';
const asyncFnNum = async () => 5;

const testFn = function*() {
    const test1 = yield asyncFnString();
    // const test1: any

    const test2 = yield call(asyncFnString);
    // const test2: any

    // bad usage example, yielded the generator instead of delegating to it
    // in another word, used 'yield' instead of 'yield*'
    // the code would still work with redux-saga, but the types will not.
    const test3 = yield callSaga(asyncFnString);
    // const test3: any

    const test4 = yield* callSaga(asyncFnString);
    // const test4: string

    const test5 = yield* callSaga(asyncFnNum);
    // const test5: number
};

As you can see the usage is quite ergonomic, as there is no need for manual typing.如您所见,该用法非常符合人体工程学,因为无需手动输入。 It will also prevent errors caused by missing manual typing or wrong manual typing.它还将防止由于缺少手动输入或错误的手动输入而导致的错误。 This helper can also be modified for other effects too.这个助手也可以修改为其他效果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM