开发者问题收集

Typescript 检查未定义并分配给变量

2019-07-05
1022

我不确定为什么 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

谢谢!

3个回答

TS4.4 更新:

TypeScript 将很快允许 别名条件的控制流分析 ,这意味着将 isShareable 之类的条件保存为 const 可以允许它稍后用作类型保护。不幸的是,这里的代码似乎有点恶化,因为 shareNavigator 的已知属性,您不能再 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 。无论如何,这样做可能没有坏处。这取决于你。)

好的,希望有所帮助。祝你好运!

代码链接

jcalz
2019-07-05

您可以使用类型转换来确保导航器具有 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);
      }
    }
  };
River H
2019-07-05

您可以使用 类型保护 来帮助 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);
    }
  }
};
Justin Howard
2019-07-05