在TypeScript中,可选参数和未定义参数之间是否有区别?

我想知道这两段代码之间是否有区别:

function sayHello(name?: string) {
  if (name) { return 'Hello ' + name; }
  return 'Hello!';
}

function sayHello(name: string | undefined) {
  if (name) { return 'Hello ' + name; }
  return 'Hello!';
}

(我知道我不能在“名称”之后添加第二个不是可选的参数,因为它必须是最后一个或最后一个)

我今天早些时候在考虑这个问题,我觉得对我来说主要的区别首先是你所说的函​​数的使用者。

第一个更暗示可选性,比如“你不需要把这个传给我,但如果你愿意,你可以”第二个说“给我一个字符串,我不在乎它是否未定义,我可以处理”。

类似的事情也可以出现在接口和类型中。

interface Foo {
   thing?: string;
}

对比

interface Foo {
   thing: string | undefined;
}

我在正确的轨道上吗?还有什么我应该知道的吗?

回答

你走在正确的轨道上,而且几乎是正确的。


在下文中,我将假设你正在使用的--strict或至少是--strictNullChecks编译器选项,以便undefinednull并不总是含蓄地允许:

let oops: string = undefined; // error! 
// Type 'undefined' is not assignable to type 'string'

在 TypeScript 中,用修饰符标记为可选的函数/方法参数或对象类型的字段?意味着它可能会丢失

function opt(x?: string) { }

interface Opt {
    x?: string;
}

const optObj: Opt = {}; // okay
opt(); // okay

但是也允许存在undefined此类可选参数/字段,但是

const optObj2: Opt = { x: undefined } // okay
opt(undefined); // okay

事实上,如果您使用 IntelliSense 检查此类可选参数/字段的类型,您会发现编译器会自动添加undefined以下可能性:

function opt(x?: string) { }
// function opt(x?: string | undefined): void

interface Opt {
    x?: string;
}
type AlsoOpt = Pick<Opt, "x">;
/* type AlsoOpt = {
    x?: string | undefined;
} */

从函数的实现者或对象类型的使用者的角度来看,可选元素可以被视为它始终存在,但可能undefined

function opt(x?: string) {
    // (parameter) x: string | undefined
    console.log(typeof x !== "undefined" ? x.toUpperCase() : "undefined");
}

function takeOpt(v: Opt) {
    const x = v.x;
    // const x: string | undefined
    console.log(typeof x !== "undefined" ? x.toUpperCase() : "undefined");
}

将其与包含以下内容的必需(非可选)字段或参数进行比较和对比| undefined

function req(x: string | undefined) { }

interface Req {
    x: string | undefined
}

与可选版本一样,必需的 with| undefined接受一个显式的undefined. 但与可选版本不同的是,不能在完全缺少值的情况下调用或创建必需的版本:

req(); // error, Expected 1 arguments, but got 0!
req(undefined); // okay
const reqObj: Req = {}; // error, property x is missing!
const reqObj2: Req = { x: undefined } // okay

并且,与可选版本一样,函数的实现者或对象类型的使用者将看到可选的东西确实存在,但可能undefined

function req(x: string | undefined) {
    // (parameter) x: string | undefined
    console.log(typeof x !== "undefined" ? x.toUpperCase() : "undefined");
}

function takeReq(v: Req) {
    const x = v.x;
    // const x: string | undefined
    console.log(typeof x !== "undefined" ? x.toUpperCase() : "undefined");
}

其他注意事项:


元组类型中还有一些可选元素,它们的工作方式相同。它们类似于可选对象字段,但它们与参数具有相同的限制:如果任何元组元素是可选的,则所有后续元素也必须是可选的:

type OptTuple = [string, number?];
const oT: OptTuple = ["a"]; // okay
const oT2: OptTuple = ["a", undefined]; // okay

type ReqTuple = [string, number | undefined];
const rT: ReqTuple = ["a"]; // error! Source has 1 element(s) but target requires 2
const rT2: ReqTuple = ["a", undefined]; // okay

对于函数参数,您有时也可以使用类型void来表示“缺失”,因此| void表示“可选”,如microsoft/TypeScript#27522 中实现的那样。所以x?: stringx: string | void被类似地对待:

function orVoid(x: string | void) {
    console.log((typeof x !== "undefined" ? x.toUpperCase() : "undefined"));
}
orVoid(); // okay

对象字段还不是这种情况。它已在microsoft/TypeScript#40823 中实现,但尚未进入语言(我不确定它是否会这样做):

interface OrVoid {
    x: string | void;
}
const o: OrVoid = {} // error! x is missing

最后,我将向您指出microsoft/TypeScript#13195,该问题讨论了 TypeScript 中“失踪”和“存在但undefined”之间有趣的历史关系。有时它们被同等对待,有时它们被区别对待。

人们想要更多区别的一个主要地方是,当某些东西是可选的时,开发人员并不总是对显式传入undefined的可能性感到高兴。也就是说,人们想说这interface Opt {x?: string}应该意味着它x要么是 a string,要么完全丢失了。他们认为如果你有一个otype的值Obj,那么o.x === undefined应该只在"x" in ois时发生false

但这不是 TypeScript 中默认发生的情况。在大多数编译器配置中,o.x === undefined不会让您知道该属性是否存在-但是-undefined或缺失。所以"x" in o并不能最终告诉你o.x是 astring还是undefined

有一个新的--exactOptionalPropertyTypes编译器标志将与 TypeScript 4.4 一起发布,当启用时,它不会添加undefined到可选属性的域中,因此o.x === undefined意味着x缺少该属性。但是,默认情况下不会启用此编译器选项,它仅适用于属性而不适用于函数参数。

无论如何,我建议避免丢失和undefined重要之间的区别的情况。


Playground 链接到代码


以上是在TypeScript中,可选参数和未定义参数之间是否有区别?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>