TypeScript中的类型'string | string[]'不能赋值给类型'string',那么什么是'string | string[]'类型?如何将它们转换为字符串?

26

当我使用TypeScript时:

let token = req.headers['x-access-token'] || req.headers['authorization'] as string;

我遇到了以下错误:

Argument of type 'string | string[]' is not assignable to parameter of type 'string'

有人知道什么是 string | string[] 类型吗?我的意思是,如果我想在 TypeScript 中使用两个字符串的逻辑“或”,该怎么做?
还有,如何将 string | string[] 强制转换为 string 类型?
4个回答

24
尝试
let token = (req.headers['x-access-token'] || req.headers['authorization']) as string;
编译器认为req.headers ['some string']是字符串数组,当你将or运算符的一侧强制转换为字符串或字符串数组时,你会得到一个字符串或字符串数组类型。所以对它们两个都执行or运算,然后将结果强制转换为字符串。

15

我猜你正在使用node.js。在这种情况下,req.headers 的类型为 IncomingHttpHeaders,它具有索引签名:[header: string]: string | string[] | undefined;
这意味着,req.headers['whatever'] 可以是 stringstring[](字符串数组)或者 undefined

  • 你逻辑或的第一部分 req.headers ['x-access-token'] 的类型为 string | string[] | undefined
  • 由于强制类型转换,第二部分 req.headers ['authorization'] as string 的类型为 string
  • token 的类型为 string | string[],因为:
    • 当第一部分被定义时,它可以是 string | string[]
    • 当第一部分未被定义时,or 将使用第二部分,其类型为 string

提示
你可以使用 req.headers.authorization 替代 req.headers['authorization'],它的类型为 string | undefined

interface IncomingHttpHeaders {
    .. 
    'authorization'?: string;
    ..
    [header: string]: string | string[] | undefined;
}

详细信息
注意: Adrian Brand的回答已经很好了,您可以直接使用。为了完整起见,我将展示一种详细的方法来处理所有情况并解释这些类型:

const tokenValue= req.headers['x-access-token'] || req.headers['authorization'];

tokenValue 的类型是 string | string[] | undefined
需要注意的是,当这些标头都不存在时,它也可能是undefined
我们可以处理这种情况:

if (!tokenValue) throw Error('missing header')

经过这个检查,TypeScript就足够聪明了,知道tokenValue现在的类型是string | string[]

if (Array.isArray(tokenValue)) {
  // In this if branch the type of `tokenValue` is `string[]`
} else {
  // In this if branch, the type of `tokenValue` is `string`
}

1
@Ernesto 将'user-agent'转换为字符串是错误的。IncomingHttpHeaders['user-agent']的类型是string | undefined,根据RFC723,发送用户代理是可选的。因此,您应该使用默认值而不是转换为字符串:const userAgent = req.headers["user-agent"] || ''; - TmTron
1
@Ernesto,这个问题不是关于“user-agent”的。OP还想获取x-access-token的值(它在IncomingHttpHeaders中没有明确类型,因此是string | string[] | undefined类型)。也就是说,在运行时,您可能会得到一个字符串数组。将数组强制转换为字符串很可能会导致运行时错误,因此最好明确处理该情况。 - TmTron
1
@Ernesto,我猜你没有看问题中的代码示例:req.headers['x-access-token'] || req.headers['authorization'] - TmTron
1
@Ernesto:这里有一个简短的示例,可以帮助您了解为什么将 string[] 强制转换为 string 是一个不好的想法(点击“运行”按钮):typescript playground - TmTron
1
@Ernesto,在这种情况下你是错误的。我强烈建议你不要把这变成一个关于自尊心等问题的争吵,而是去对这个主题做更多的研究。将数组转换为字符串在这种情况下对用户没有任何帮助,因为底层类型可以是数组、字符串或未定义。 - Alexis Tyler
显示剩余5条评论

6

澄清

有人知道什么是 'string | string[]' 类型吗?我的意思是如果我想在 TypeScript 中使用两个字符串的逻辑“或”。怎么做呢?

string | string[] 是一种 类型联合 (TS文档),这意味着相关值可以是 string 或者 string[](或者 Array<string> 或者是一个字符串数组)。

当两个变量之间使用逻辑或运算符 || 时,如果左操作数包含一个 falsish 类型 (undefined, null, number, stringboolean),则实际上产生了两个变量类型的联合类型;否则产生第一个变量的类型。 falsish 类型实际上取决于配置(请参见下面的注释以获取真正的解决方案)。例如:

type NonFalsishType = { prop: number };
let var1: number | undefined = 42;
let var2: string = 'var2'
let var3: NonFalsishType  = { prop: 42 };

let test1: number | string = var1 || var2;
let test2: number | string = var2 || var1;
let test3: string | NonFalsishType = var2 || var3;

// strictNullCheck = true
// string type can be omitted because NonFalsishType
// is defenitely not falsy
let test4: NonFalsishType = var3 || var2;

// strictNullCheck = false
// string not omitted because null can be assigned to var3
let test4: NonFalsishType | string/* | null*/ = var3 || var2;

如何将 'string | string[]' 类型转换为字符串类型?

"转换"(正确的名称是type assertion (TS Docs),这是一个语义上不同的概念)可以通过不同的方式实现,最常见的方法是使用 as 关键字,但也有角括号标记的方法:

// as
let myHeader: string = req.headers['authorization'] as string

// angle brackets
let myHeader: string = <string>req.headers['authorization']

注意:类型断言在运行时不起任何作用,它在JS代码中也不会被编译:

// TS
let myHeader: string = req.headers['authorization'] as string

// JS
var myHeader = req.headers['authorization'];

类型断言仅用于指示TS类型检查器在编译阶段仅将类型限制为另一种类型。这就像对编译器说:“我不关心变量实际上是哪种(联合)类型,只需将其视为指定的类型”。

可能的解决方案

现在最简单的解决方案是为您的变量断言string类型:

// parenthesis emphasize where the assertion is applied

let token: string = (req.headers['x-access-token'] as string) ||
                    (req.headers['authorization'] as string);

let token: string = (
    req.headers['x-access-token'] ||
    req.headers['authorization']
) as string;

// no runtime differences, just saying to the TS type checker
// two different way to see the same variables

这种解决方案会导致不同的问题:如果客户端向服务器发送多个x-access-token/authorization标头怎么办?

您最终将在token变量中得到一个数组,这意味着您生成的代码可能会出现错误(例如,token.substr(10)会产生运行时错误,因为数组没有substr属性,而字符串有)。

更糟糕的是,如果客户端根本不发送x-access-token/authorization标头,任何访问器都会中断执行(undefined属性将会中断执行)。

真正的解决方案

您需要考虑您需要实现什么。 TypeScript类型注释不仅仅是为了使您的代码更美观,而且通过类型和模式检查实际上可以生成具有显著质量的代码。您不应忽略变量可能具有多种类型的事实,否则在生产环境中会出现漏洞和安全问题。

如果您的真正目的是验证访问令牌,您应确保令牌非空且唯一,以便标识用户:

// usually is a bad practice to authorize with multiple headers
// but it does not produce significant runtime error doing this
let token: string | string[] | undefined = req.headers['x-access-token'] || req.headers['authorization'];

if (typeof(token) === 'undefined') {
    // no token passed in the header
    // token variable is of type 'undefined' in this scope
   
    // probably here, if the page require access, you should
    // respond with a 401 unauth code

    // you can skip this check by appending a check at 
    // the end of token variable initialization like this:
    // let token: string | string[] = ... || '';
}
else if (Array.isArray(token)) {
    // multiple tokens passed in the header
    // token variable is of type 'string[]' in this scope
   
    // for this special case see multiple tokens notes (at the end)
}
else if (!token) {
    // the header contains the token but is actually an empty string
    // token variable is of type 'string' in this scope

    // same as undefined token, if the page require access, you should
    // respond with a 401 unauth code
}
else {
    // the header contains a non-empty string token
    // token variable is of type 'string' also in this scope

    // validate your token and respond by consequence (200 OK / 401 unath)
}

注意:

req.headers[key],正如@TmTron的回答所述,其类型为string | string[] | undefined,但是错误信息中的联合类型中没有提到undefined。这是因为可以配置TypeScript(在tsconfig.json或通过传递正确的命令行参数)在类型检查期间忽略假值(如falsenullundefined)。该选项是strictNullCheck (TS Docs),默认设置为false(意味着TS在类型检查时会忽略假值)。如果将该选项设置为true,则错误将变为:

Argument of type 'string | string[] | undefined' is not assignable to parameter of type 'string'

强制你考虑"undefined"的情况(我的经验是这通常可以防止很多bug)。
多令牌注意事项
多个令牌的情况比较模糊,你需要根据你的意图达成一致:
- 总是拒绝多个令牌 - 最好、建议和通用做法(401未授权)。 - 如果多个令牌指向不同的用户,则拒绝它们 - 如果有可能,如忽略和删除过期的令牌,但要检查它们是否指向同一个用户(验证令牌-200 OK / 401未授权)。 - 只接受第一个令牌作为有效令牌:只需使用token = token[0] || ''并删除后续的else,变成if(!token)... - 这仍然可行,但不是非常干净的解决方案。
实际上,有些身份验证技术利用扩展令牌(逗号分隔的令牌),但在安全实现的日常使用中非常少见。
此外,请注意,理论上客户端不应发送具有相同名称的多个头文件,但实际上,恶意用户可以模拟对您的服务器的调用,并重复发送头文件,以利用应用程序的某些漏洞。这就是为什么您还应该验证数组情况的原因。

4
这是因为可能会有多个相同的标题。
在这里,我返回标题或者如果它是一个数组,则返回该标题的第一个实例。
const getHeader = (name) => Array.isArray(req.headers[name]) ? req.headers[name][0] : req.headers[name];
let token = getHeader('x-access-token') ?? getHeader('authorization');

很高兴看到这个被踩了,但是当前是唯一正确的答案。 - Alexis Tyler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接