[英]@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 }
getGraphProfile
so it does match?getGraphProfile
中的数据使其匹配? Because one can create a TS interface
but that would simply create duplicate code with the object defaultState().profile
interface
,但这只会使用 object defaultState().profile
创建重复的代码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 将知道getGraphDetails
和getGraphProfile
都返回相同的类型,因为它们最终只是通过 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.