【问题标题】:Typescript Check for undefined and assign to variable打字稿检查未定义并分配给变量
【发布时间】:2019-11-16 07:24:58
【问题描述】:

我不确定为什么 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 &amp;&amp; typeof window.navigator.share !== 'undefined'; 但我会得到同样的错误。

理想情况下,我可以创建一个函数来检查 window.navigator.share 是否已定义以及是否会进行类型检查。

以上所有代码都可以粘贴到https://www.typescriptlang.org/play

谢谢!

【问题讨论】:

    标签: typescript


    【解决方案1】:

    您可以使用类型转换来确保导航器具有共享方法,并且您需要将share 设置为必需的道具,因为您拥有它的方式可能是未定义的。游戏围栏中的这段代码没有错误。

    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);
          }
        }
      };
    

    【讨论】:

      【解决方案2】:

      您可以使用type guard 来帮助 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);
          }
        }
      };
      

      【讨论】:

        【解决方案3】:

        TS4.4 更新:

        TypeScript 很快将允许使用control flow analysis of aliased conditions,这意味着将isShareable 之类的条件保存为const 可能允许稍后将其用作类型保护。不幸的是,这里的代码似乎有点恶化,因为shareNavigator 的一个已知属性,你不能再只是merge 它了。

        isShareable 这样的别名条件只有在你测试的属性是readonly 时才有效,因为编译器都知道,有人可能会在你设置isShareable 之后但在你调用之前设置window.navigator.share = undefined window.navigator.share。如果您无法控制事物是否为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);
              }
            }
          };
        

        Playground link to code


        TS4.4 之前的答案:

        TypeScript 目前不允许您将类型保护结果保存在一个变量中,以便以后使用,就像您尝试使用 isShareable 所做的那样。在 TypeScript 的 GitHub 问题中有两个公开的建议:microsoft/TypeScript#12184microsoft/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 &amp;&amp; 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,而是存储类型为((data?: ShareData) =&gt; Promise&lt;void&gt;) | undefined 的等效变量share。然后检查 if (share) { share(...) } 与您所做的具有相同的语义,除了在这种情况下编译器可以很容易地看到您正在使用您刚刚检查的东西。

        (请注意,根据window.navigator.share() 的实现方式,它可能需要this 上下文为window.navigator。如果是这样,那么您可能希望将share 保存为window.navigator.share.bind(window.navigator) 而不仅仅是@987654354 @. 无论如何,这样做可能没有什么坏处。这取决于你。)

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

        Link to code

        【讨论】:

        • 感谢您的详细回复。我实际上曾尝试存储share 变量,但正如您提到的,它需要访问this。我没想过将它绑定到 window.navigator。这对我来说可能是最好的解决方案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-04-30
        • 2022-07-15
        • 2020-09-28
        • 1970-01-01
        • 2018-05-06
        • 2019-02-01
        相关资源
        最近更新 更多