javascript - @typescript-eslint/no-unsafe-assignment: Osäker tilldelning av ett värde av typen any
Överväg följande kod:
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 }
}
Vilket genererar ESLint-varningen:
@typescript-eslint/no-unsafe-assignment: Osäker tilldelning av ett any-värde.
Detta betyder att egenskaperna i response.data kanske inte matchar de i profile. Returvärdet av getGraphProfile är Promise<AxiosResponse<any>>. Självklart är det enkelt att bli av med denna ESLint-varning genom att helt enkelt ignorera den:
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
state.profile = { ...defaultState().profile, ...response.data }
Frågor:
- Hur är det möjligt att forma datan i Promise
getGraphProfileså att den matchar? Eftersom man kan skapa ett TSinterfacemen det skulle helt enkelt skapa duplicerad kod med objektetdefaultState().profile - Varför har TypeScript inget problem med den här koden men linter gör det? Behöver inte båda vara i linje?
Implementeringarna:
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}`)
}
}
Svar #1
TypeScript genererar inte varningar, bara fel. När det gäller TS är den any-tilldelningen giltig. Det är här lintverktyget kommer in för att erbjuda ytterligare stöd.
Som tur är behöver du inte duplicera ditt gränssnitt. Använd TypeScripts ReturnType för att få typen av profile-objektet i din defaultState-metod:
type IProfile = ReturnType<typeof defaultState>["profile"]
Ovanstående rad använder 3 fantastiska TypeScript-funktioner:
ReturnTypeför att härleda typen som en funktion returnerartypeofför att härleda gränssnittet från en objektinstans["profile"]för att få typen av en viss egenskap hos ett gränssnitt
Gör nu din callGraph-funktion generisk:
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 })
}
Och uppdatera callGraph-anropet i din getGraphDetails-funktion:
...
if (response && response.accessToken) {
return callGraph<IProfile>(uri, response.accessToken, axiosConfig)
}
...
Nu är dina grafanrop korrekt typade, och du behövde inte duplicera din profildefinition; istället använde du TypeScripts fantastiska typinferenzteknik för att "läsa din interface" från returtypen av din funktion.
Svar #2
Kommer att svara på dina frågor i omvänd ordning:
Varför har TypeScript inget problem med den här koden men linter gör det? Behöver inte båda vara i linje?
I Typescript kan något med typen any tilldelas vad som helst. Att använda any tar i princip bort typ-säkerhet från den delen av koden. Till exempel:
const foo: number = 'hello' as any // Typescript is fine with this
Jag antar att poängen med den där eslint-regeln är att fånga ställen där du kanske inte faktiskt vill tilldela något med typen any till något annat. Ärligt talat är jag inte riktigt säker på varför man skulle använda den lint-regeln med tanke på att kompilatoralternativet noImplicitAny finns.
Hur är det möjligt att forma datan i Promisen getGraphProfile så att den matchar? För man kan skapa ett TS-interface men det skulle helt enkelt skapa dubbel kod med objektet defaultState().profile
Det finns några sätt du kan lösa detta på. Det enklaste sättet skulle förmodligen vara att skriva ut returvärdet av getGraphDetails:
type GraphDetailsPayload = {
id: string,
displayName: string,
givenName: string,
}
export const getGraphProfile = async (): Promise<GraphDetailsPayload> => {
...
}
Men vanligtvis är det bättre att skriva in datan på så låg nivå som möjligt, vilket i det här fallet betyder callGraph-funktionen:
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 })
}
Genom att göra det på det sättet är nu callGraph:s returvärde typat, och TS kommer därför att veta att getGraphDetails och getGraphProfile båda returnerar samma typ, eftersom de i slutändan bara vidarebefordrar API-svaret.
Sista alternativet: Jag använder inte Axios, men jag gissar att dess Typescript-definition skulle låta dig göra detta:
const callGraph = (
url: string,
token: string,
axiosConfig?: AxiosRequestConfig
) => {
const params: AxiosRequestConfig = {
method: 'GET',
url: url,
headers: { Authorization: `Bearer ${token}` },
}
return axios<GraphDetailsPayload>({ ...params, ...axiosConfig })
}
Jag har tagit bort Promise<GraphDetailsPayload>-returtypen och har istället bara "passerat in" GraphDetailsPayload-typen via vinkelparenteserna till axios-funktionsanropet. Detta använder något som kallas "generics", vilket är den roligaste och mest komplexa delen av typsystem som TS. Du kommer att stöta på dem mycket i bibliotek du använder, och du kommer så småningom att börja skriva funktioner som också tar generics.
