简体   繁体   English

@typescript-eslint/no-unsafe-assignment:任何值的不安全赋值

[英]@typescript-eslint/no-unsafe-assignment: Unsafe assignment of an any value

Consider the following code:考虑以下代码:

const defaultState = () => {
  return {
    profile: {
      id: '',
      displayName: '',
      givenName: '',
    },
    photo: '',
  }
}

const state = reactive(defaultState())

export const setGraphProfile = async () => {
  const response = await getGraphProfile()
  state.profile = { ...defaultState().profile, ...response.data }
}

Which generates the ESLint warning:这会生成 ESLint 警告:

@typescript-eslint/no-unsafe-assignment: Unsafe assignment of an any value. @typescript-eslint/no-unsafe-assignment:任何值的不安全赋值。

This means that the properties in response.data might not match the ones of the profile .这意味着response.data中的属性可能与profile中的属性不匹配。 The return of getGraphProfile is Promise<AxiosResponse<any>> . getGraphProfile的返回是Promise<AxiosResponse<any>> Of course it's easy to get rid of this ESLint warning by simply ignoring it:当然,通过简单地忽略它很容易摆脱这个 ESLint 警告:

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
state.profile = { ...defaultState().profile, ...response.data }

Questions:问题:

  • How is it possible to shape the data in the Promise getGraphProfile so it does match?如何塑造 Promise getGraphProfile中的数据使其匹配? Because one can create a TS interface but that would simply create duplicate code with the object defaultState().profile因为可以创建一个 TS interface ,但这只会使用 object defaultState().profile创建重复的代码
  • Why is TypeScript not having an issue with this code but the linter does?为什么 TypeScript 对此代码没有问题,但 linter 有问题? Do both not need to be alligned?两者都不需要对齐吗?

The implementations:实现:

const callGraph = (
  url: string,
  token: string,
  axiosConfig?: AxiosRequestConfig
) => {
  const params: AxiosRequestConfig = {
    method: 'GET',
    url: url,
    headers: { Authorization: `Bearer ${token}` },
  }
  return axios({ ...params, ...axiosConfig })
}

const getGraphDetails = async (
  uri: string,
  scopes: string[],
  axiosConfig?: AxiosRequestConfig
) => {
  try {
    const response = await getToken(scopes)
    if (response && response.accessToken) {
      return callGraph(uri, response.accessToken, axiosConfig)
    } else {
      throw new Error('We could not get a token because of page redirect')
    }
  } catch (error) {
    throw new Error(`We could not get a token: ${error}`)
  }
}

export const getGraphProfile = async () => {
  try {
    return await getGraphDetails(
      config.resources.msGraphProfile.uri,
      config.resources.msGraphProfile.scopes
    )
  } catch (error) {
    throw new Error(`Failed retrieving the graph profile: ${error}`)
  }
}

export const getGraphPhoto = async () => {
  try {
    const response = await getGraphDetails(
      config.resources.msGraphPhoto.uri,
      config.resources.msGraphPhoto.scopes,
      { responseType: 'arraybuffer' }
    )
    if (!(response && response.data)) {
      return ''
    }
    const imageBase64 = new Buffer(response.data, 'binary').toString('base64')
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
    return `data:${response.headers['content-type']};base64, ${imageBase64}`
  } catch (error) {
    throw new Error(`Failed retrieving the graph photo: ${error}`)
  }
}

TypeScript doesn't generate warnings, only errors. TypeScript 不会产生警告,只会产生错误。 As far as TS is concerned, that any assignment is valid.就 TS 而言, any分配都是有效的。 This is where the linter comes in to offer additional support.这就是 linter 提供额外支持的地方。

Luckily you don't need to duplicate your interface.幸运的是,您不需要复制您的界面。 Use TypeScript's ReturnType to get the type of the profile object in your defaultState method:使用 TypeScript 的 ReturnType 在defaultState方法中获取profile ReturnType的类型:

type IProfile = ReturnType<typeof defaultState>["profile"]

The above line utilizes 3 great TypeScript features:上述行利用了 3 个出色的 TypeScript 特性:

  • ReturnType to infer the type that a function returns ReturnType推断 function 返回的类型
  • typeof to infer the interface from an object instance typeof从 object 实例推断接口
  • ["profile"] to get the type of a certain property of an interface ["profile"]获取接口某个属性的类型

Now, make your callGraph function generic:现在,让您的callGraph function 通用:

function callGraph<T>(url: string, token: string, axiosConfig?: AxiosRequestConfig) {
  const params: AxiosRequestConfig = {
    method: 'GET',
    url: url,
    headers: { Authorization: `Bearer ${token}` },
  }
  return axios.request<T>({ ...params, ...axiosConfig })
}

And update the callGraph call in your getGraphDetails function:并在您的getGraphDetails function 中更新callGraph调用:

...
  if (response && response.accessToken) {
    return callGraph<IProfile>(uri, response.accessToken, axiosConfig)
  }
...

Now your graph calls are properly typed, and you didn't have to duplicate your profile definition;现在您的图形调用已正确键入,您不必复制配置文件定义; rather you used TypeScript's awesome type inference technique to "read your interface" from the return type of your function.相反,您使用 TypeScript 出色的类型推断技术从 function 的返回类型中“读取您的接口”。

Going to answer your questions in reverse order:以相反的顺序回答您的问题:

Why is TypeScript not having an issue with this code but the linter does?为什么 TypeScript 对此代码没有问题,但 linter 有问题? Do both not need to be alligned?两者都不需要对齐吗?

In Typescript, something with type any can be assigned to anything .在 Typescript 中,类型为any的东西可以分配给任何东西 Using any essentially removes typesafety from that part of the code.使用any本质上会从该部分代码中删除类型安全性。 For example:例如:

const foo: number = 'hello' as any // Typescript is fine with this

I guess the point of that eslint rule is to catch places where you might not be wanting to actually assign something with type any to something else.我想该 eslint 规则的重点是捕捉您可能不想将any类型的东西实际分配给其他东西的地方。 To be honest, I'm not quite sure why one would use that linting rule given that the compiler option noImplicitAny exists.老实说,鉴于存在编译器选项noImplicitAny ,我不太确定为什么要使用该 linting 规则。

How is it possible to shape the data in the Promise getGraphProfile so it does match?如何塑造 Promise getGraphProfile 中的数据使其匹配? Because one can create a TS interface but that would simply create duplicate code with the object defaultState().profile因为可以创建一个 TS 接口,但这只会使用 object defaultState().profile 创建重复的代码

There are a few ways you could solve this.有几种方法可以解决这个问题。 The simplest approach would probably be to type the return value of getGraphDetails :最简单的方法可能是输入getGraphDetails的返回值:

type GraphDetailsPayload = {
  id: string,
  displayName: string,
  givenName: string,
}

export const getGraphProfile = async (): Promise<GraphDetailsPayload> => {
  ...
}

But usually it's better to type the data at as low a level as possible, which in this case means the callGraph function:但通常最好在尽可能低的级别键入数据,在这种情况下,这意味着callGraph function:

const callGraph = (
  url: string,
  token: string,
  axiosConfig?: AxiosRequestConfig
): Promise<GraphDetailsPayload> => {
  const params: AxiosRequestConfig = {
    method: 'GET',
    url: url,
    headers: { Authorization: `Bearer ${token}` },
  }
  return axios({ ...params, ...axiosConfig })
}

By doing it that way, now callGraph 's return value is typed, and TS will therefore know that getGraphDetails and getGraphProfile both return that same type, since they ultimately just pass through the API response.通过这样做,现在callGraph的返回值是类型化的,因此 TS 将知道getGraphDetailsgetGraphProfile都返回相同的类型,因为它们最终只是通过 API 响应。

Last option: I don't use Axios, but I bet its Typescript definition would let you do this:最后一个选项:我不使用 Axios,但我敢打赌它的 Typescript 定义会让你这样做:

const callGraph = (
  url: string,
  token: string,
  axiosConfig?: AxiosRequestConfig
) => {
  const params: AxiosRequestConfig = {
    method: 'GET',
    url: url,
    headers: { Authorization: `Bearer ${token}` },
  }
  return axios<GraphDetailsPayload>({ ...params, ...axiosConfig })
}

I have removed the Promise<GraphDetailsPayload> return type, and have instead just "passed in" the GraphDetailsPayload type via the angle brackets to the axios function call.我已经删除了Promise<GraphDetailsPayload>返回类型,而是通过尖括号将GraphDetailsPayload类型“传入”到axios function 调用。 This is making use of something called "generics", which are the most fun and complex part of typesystems like TS.这是利用了一种叫做“泛型”的东西,它是像 TS 这样的类型系统中最有趣和最复杂的部分。 You'll encounter them a lot in libraries you use, and you'll eventually start writing functions that take generics as well.你会在你使用的库中经常遇到它们,并且你最终会开始编写使用 generics 的函数。

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

相关问题 ESLint:“any”的不安全分配和“any”类型值的不安全调用 - ESLint: Unsafe assignment of an `any` and Unsafe call of an `any` typed value Typescript:ESLint:任何类型值的不安全返回 @typescript-eslint/no-unsafe-return - Typescript : ESLint : Unsafe return of an any typed value @typescript-eslint/no-unsafe-return @typescript-eslint/no-unsafe-* 规则的新 eslint 错误 - New eslint errors with the @typescript-eslint/no-unsafe-* rules 如何处理 catch(err) 块上的 @typescript-eslint/no-unsafe-member-access 规则? - How to handle the @typescript-eslint/no-unsafe-member-access rule on catch(err) blocks? 任何值上的 eslint 错误不安全成员访问 ['content-type'] - eslint error Unsafe member access ['content-type'] on an any value 收到此打字稿错误:对任何值不安全的成员访问 [key] - Getting this typescript error: Unsafe member access [key] on an any value eslint 缩进和@typescript-eslint/indent 冲突 - eslint indent and @typescript-eslint/indent conflict UIComponent.getRouterFor 显示 TypeScript 错误:“任何”类型值的不安全返回 - UIComponent.getRouterFor shows TypeScript error: Unsafe return of an 'any' typed value 对 Typescript function 中的 return 语句不安全地使用“any”类型的表达式 - Unsafe use of expression of type 'any' for return statements in Typescript function 在打字稿中赋值后未定义的值 - Undefined value after assignment in Typescript
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM