Typescript 检查未定义并分配给变量
我不确定为什么 Typescript 会为以下代码给出类型错误:
type ShareData = {
title?: string;
text?: string;
url?: string;
};
// extending window.navigator, which has a type of Navigator
interface Navigator {
share?: (data?: ShareData) => Promise<void>;
}
const isShareable =
window.navigator && window.navigator.share;
const handleShare = async () => {
if (isShareable) {
try {
await window.navigator.share({
text: 'Checkout Verishop!',
title: 'Share Verishop',
url: 'referralUrl',
});
} catch (e) {
window.alert(e);
}
}
};
Typescript 对此行
await window.navigator.share({
它说
share
可能未定义。但是,我正在使用
isShareable
来确保
window.navigator.share
未未定义。
我也尝试过定义
isShareable
,如下所示:
const isShareable = window.navigator && typeof window.navigator.share !== 'undefined';
,但我会得到相同的错误。
理想情况下,我可以创建一个函数来检查
window.navigator.share
是否已定义,并且类型检查将流经该函数。
以上所有代码都可以粘贴到 https://www.typescriptlang.org/play
谢谢!
TS4.4 更新:
TypeScript 将很快允许
别名条件的控制流分析
,这意味着将
isShareable
之类的条件保存为
const
可以允许它稍后用作类型保护。不幸的是,这里的代码似乎有点恶化,因为
share
是
Navigator
的已知属性,您不能再
merge
了。
像
isShareable
这样的别名条件只有在您测试的属性是
readonly
时才会起作用,因为就编译器所知,有人可能会在您设置
isShareable
之后但在调用
window.navigator.share
之前的某个时间点设置
window.navigator.share = undefined
。如果您无法控制事物是否为
readonly
,这将是一个相当大的绊脚石。尽管如此,如果它看起来像这样:
interface Navigator extends Omit<typeof window.navigator, "share"> {
readonly share?: (data?: ShareData) => Promise<void>;
}
declare const _window: Omit<typeof window, 'navigator'> &
{ readonly navigator?: Navigator }
那么在 TypeScript 4.4 中,这不会是一个错误:
const isShareable =
(_window.navigator && _window.navigator.share);
const handleShareStored = async () => {
if (isShareable) {
try {
await _window.navigator.share({ // no error in TS4.4+
text: "Checkout Verishop!",
title: "Share Verishop",
url: "referralUrl"
});
} catch (e) {
_window.alert(e);
}
}
};
TS4.4 之前的解答:
TypeScript 目前不允许您将类型保护结果保存在变量中,以便稍后像您尝试使用
isShareable
那样使用。 TypeScript 的 GitHub 问题中有两个开放的建议:
microsoft/TypeScript#12184
和
microsoft/TypeScript#24865
。这两个问题似乎都没有太大的吸引力,但我想你可以去这些问题上点个赞,或者描述一下你的用例,如果它很有说服力的话。问题很可能在于,实现它并保持编译器性能并不是一件容易的事。
您现在能做什么?显而易见且不令人满意的答案是不要尝试提前进行类型保护,而是在您想要使用受保护的
window.navigator.share
函数之前进行类型保护:
// just do the check inline
const handleShareExplicit = async () => {
if (window.navigator && window.navigator.share) {
try {
await window.navigator.share({ // no error
text: "Checkout Verishop!",
title: "Share Verishop",
url: "referralUrl"
});
} catch (e) {
window.alert(e);
}
}
};
这其实并不是那么糟糕,因为它仍然相当简洁……
(window.navigator && window.navigator.share)
仍然适合一行。但有些人可能会遇到类似的问题,其中类型保护是一项更复杂的检查,或者您可能在哲学上反对多次进行检查。在这种情况下,您可以尝试不同的方法:
// store a reference to the function if it exists
const share =
window.navigator && window.navigator.share
? window.navigator.share // .bind(window.navigator) ?? see note below
: undefined;
const handleShare = async () => {
if (share) {
try {
await share({ // no error
text: "Checkout Verishop!",
title: "Share Verishop",
url: "referralUrl"
});
} catch (e) {
window.alert(e);
}
}
};
不要存储类似
boolean
的变量
isShareable
,而是存储等效变量
share
,其类型为
((data?: ShareData) => Promise<void>) | undefined
。然后检查
if (share) { share(...)
具有与您正在执行的操作相同的语义,只是在这种情况下,编译器可以轻松地看到您正在使用刚刚检查的内容。
(请注意,根据
window.navigator.share()
的实现方式,它可能需要
this
上下文为
window.navigator
。如果是这样,那么您可能希望将
share
保存为
window.navigator.share.bind(window.navigator)
,而不仅仅是
window.navigator.share
。无论如何,这样做可能没有坏处。这取决于你。)
好的,希望有所帮助。祝你好运!
您可以使用类型转换来确保导航器具有 share 方法,并且您需要将
share
设为必需的 prop,因为按照您的方式,它可能未定义。playpen 中的此代码没有错误。
type ShareData = {
title?: string;
text?: string;
url?: string;
};
// extending window.navigator, which has a type of Navigator
interface Navigator {
share: (data?: ShareData) => Promise<void>;
}
const nav = window.navigator as Navigator;
const handleShare = async () => {
try {
await nav.share({
text: 'Checkout Verishop!',
title: 'Share Verishop',
url: 'referralUrl',
});
} catch (e) {
window.alert(e);
}
}
};
您可以使用 类型保护 来帮助 TypeScript 安全地确定变量的接口。例如,
type ShareData = {
title?: string;
text?: string;
url?: string;
};
// extending window.navigator, which has a type of Navigator
interface SharableNavigator extends Navigator {
share: (data?: ShareData) => Promise<void>;
}
function isSharableNavigator(nav: Navigator): nav is SharableNavigator {
return typeof (nav as SharableNavigator).share === 'function';
}
const handleShare = async () => {
if (isSharableNavigator(window.navigator)) {
try {
await window.navigator.share({
text: 'Checkout Verishop!',
title: 'Share Verishop',
url: 'referralUrl',
});
} catch (e) {
window.alert(e);
}
}
};