繁体   English   中英

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

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

考虑以下代码:

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 }
}

这会生成 ESLint 警告:

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

这意味着response.data中的属性可能与profile中的属性不匹配。 getGraphProfile的返回是Promise<AxiosResponse<any>> 当然,通过简单地忽略它很容易摆脱这个 ESLint 警告:

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

问题:

  • 如何塑造 Promise getGraphProfile中的数据使其匹配? 因为可以创建一个 TS interface ,但这只会使用 object defaultState().profile创建重复的代码
  • 为什么 TypeScript 对此代码没有问题,但 linter 有问题? 两者都不需要对齐吗?

实现:

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 不会产生警告,只会产生错误。 就 TS 而言, any分配都是有效的。 这就是 linter 提供额外支持的地方。

幸运的是,您不需要复制您的界面。 使用 TypeScript 的 ReturnType 在defaultState方法中获取profile ReturnType的类型:

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

上述行利用了 3 个出色的 TypeScript 特性:

  • ReturnType推断 function 返回的类型
  • typeof从 object 实例推断接口
  • ["profile"]获取接口某个属性的类型

现在,让您的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 })
}

并在您的getGraphDetails function 中更新callGraph调用:

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

现在您的图形调用已正确键入,您不必复制配置文件定义; 相反,您使用 TypeScript 出色的类型推断技术从 function 的返回类型中“读取您的接口”。

以相反的顺序回答您的问题:

为什么 TypeScript 对此代码没有问题,但 linter 有问题? 两者都不需要对齐吗?

在 Typescript 中,类型为any的东西可以分配给任何东西 使用any本质上会从该部分代码中删除类型安全性。 例如:

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

我想该 eslint 规则的重点是捕捉您可能不想将any类型的东西实际分配给其他东西的地方。 老实说,鉴于存在编译器选项noImplicitAny ,我不太确定为什么要使用该 linting 规则。

如何塑造 Promise getGraphProfile 中的数据使其匹配? 因为可以创建一个 TS 接口,但这只会使用 object defaultState().profile 创建重复的代码

有几种方法可以解决这个问题。 最简单的方法可能是输入getGraphDetails的返回值:

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

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

但通常最好在尽可能低的级别键入数据,在这种情况下,这意味着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 })
}

通过这样做,现在callGraph的返回值是类型化的,因此 TS 将知道getGraphDetailsgetGraphProfile都返回相同的类型,因为它们最终只是通过 API 响应。

最后一个选项:我不使用 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 })
}

我已经删除了Promise<GraphDetailsPayload>返回类型,而是通过尖括号将GraphDetailsPayload类型“传入”到axios function 调用。 这是利用了一种叫做“泛型”的东西,它是像 TS 这样的类型系统中最有趣和最复杂的部分。 你会在你使用的库中经常遇到它们,并且你最终会开始编写使用 generics 的函数。

暂无
暂无

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

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