Typescript: 如何根据请求成功的情况在前端正确类型化 response.data?

huangapple go评论69阅读模式
英文:

Typescript: How to correctly type response.data on front-end side based on success of the request?

问题

考虑到这个上下文:一个API授权路由,我无法查看或编辑其代码,如果电子邮件和密码正确,它会在响应数据中返回以下内容:

interface AuthSuccess {
  token: string
  user: object
}

但如果它们不正确或未传递在请求体中,它会返回以下内容:

interface AuthError {
  error: string
}

在我的前端应用中,使用NextJS构建,使用axios进行请求,我创建了以下函数来调用这个路由:

const auth = ({ email, password }) =>
  api // api是axios.create()的返回值
    .post(url, { email, password })
    .then((result) => ({
      success: true,
      data: response.data // 这里是AuthSuccess
    }))
    .catch((error) => ({
      success: false,
      data: error.response?.data // 这里是AuthError
    }))

然而,当我像这样调用这个函数时:

const res = await auth(data)

if (res.success) {
  // 这里我期望数据是AuthSuccess类型
} else {
  // 这里我期望数据是AuthError类型
}

即使我在验证res.success,在if块内部,TypeScript会认为数据可以具有两种类型,这显然是不对的。

到目前为止,我尝试了两种解决方案:

1)将auth的类型设置为(arg: AuthProps): Promise<AuthSuccess | AuthError>,这会导致if块的问题。

2)创建另一种类型,有条件地:

interface AuthResult<T extends boolean> {
  success: T
  data: T extends true ? AuthSuccess : AuthError
}

并将auth设置为(arg: AuthProps): Promise<AuthResult>,但TypeScript会投诉没有传递泛型,而在if块中一切都变成了any

那么,如何正确为这个函数定义类型?或者如何重构它以解决这个问题?提前感谢您。

英文:

Consider this context: An API auth route which I don't have access to view or edit the code returns

interface AuthSuccess {
  token: string
  user: object
}

on response.data if email and password are correct, but returns

interface AuthError {
  error: string
}

on error.response.data if they aren't correct or aren't passed in the body.

On my front-end, built in NextJS with axios to make requests, I've created the following function to call this route:

const auth = ({email, password}) =&gt;
  api // api is the return of axios.create()
    .post(url, {email, password})
    .then((result) =&gt; ({
      success: true,
      data: response.data // here it it AuthSuccess
    }))
    .catch((error) =&gt; ({
      success: false,
      data: error.response?.data // here it is AuthError
    }))

However, when I call this function like this

const res = await auth(data)

if (res.success) {
  // here I expect data to be AuthSuccess type
} else {
  // here I expect data to be AuthError type
}

Even if I'm verifying res.success, inside the if block Typescript acts like data can have both types, which it can't, obviously.

I've tried 2 solutions so far:

  1. Setting auth type as (arg: AuthProps): Promise&lt;AuthSuccess | AuthError&gt;, which creates the problem with the if block

  2. Creating another type, conditionally:

interface AuthResult&lt;T extends boolean&gt; {
  success: T
  data: T extends true ? AuthSuccess : AuthError
}

And setting auth to be (arg: AuthProps): Promise&lt;AuthResult&gt;, to which TS complains about not passing the generic, and at the if block everything becomes any.

So, how can I type this function correctly? Or how can I refactor it to stop the problem? Ty in advance

答案1

得分: 2

你可以使用联合类型

interface SuccessResponse {
  success: true;
  data: {
    token: string;
    user: object;
  };
}

interface ErrorResponse {
  success: false;
  data: {
    error: string;
  };
}

type AuthResponse = SuccessResponse | ErrorResponse;

const test = (res: AuthResponse) => {
  if (res.success) {
    return res.data; // { token: string; user: object; }
  } else {
    return res.data; // { error: string; }
  }
}

或者使用返回类型谓词的函数

interface SuccessResponse {
  success: true;
  data: {
    token: string;
    user: object;
  };
}

interface ErrorResponse {
  success: false;
  data: {
    error: string;
  };
}

const isSuccessResponse = (res: SuccessResponse | ErrorResponse): res is SuccessResponse => {
  return res.success;
}

const test = (res: SuccessResponse | ErrorResponse) => {
  if (isSuccessResponse(res)) {
    return res.data; // { token: string; user: object; }
  } else {
    return res.data; // { error: string; }
  }
}

Typescript playground

英文:

You can use a union type:

interface SuccessResponse {
  success: true;
  data: {
    token: string;
    user: object;
  };
}

interface ErrorResponse {
  success: false;
  data: {
    error: string;
  };
}

type AuthResponse = SuccessResponse | ErrorResponse;

const test = (res: AuthResponse) =&gt; {
  if (res.success) {
    return res.data; // { token: string; user: object; }
  } else {
    return res.data; // { error: string; }
  }
}

Typescript playground


Or a function that returns a type predicate:

interface SuccessResponse {
  success: true;
  data: {
    token: string;
    user: object;
  };
}

interface ErrorResponse {
  success: false;
  data: {
    error: string;
  };
}

const isSuccessResponse = (res: SuccessResponse | ErrorResponse): res is SuccessResponse =&gt; {
  return res.success;
}

const test = (res: SuccessResponse | ErrorResponse) =&gt; {
  if (isSuccessResponse(res)) {
    return res.data; // { token: string; user: object; }
  } else {
    return res.data; // { error: string; }
  }
}

Typescript playground

huangapple
  • 本文由 发表于 2023年2月18日 03:07:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/75488366.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定