首页 > 解决方案 > 盖茨比/反应导航器

问题描述

您好,我目前遇到一个问题,我想在我的反应组件中使用 JS 显示一个弹出窗口,但是在构建我的 gatsby 时遇到错误。WebpackError:ReferenceError:未定义导航器。这是我将在我的 React 组件上使用的 JS 代码。

JS

var isMobile = {
Android: function () {
    return navigator.userAgent.match(/Android/i)
  },
  iOS: function () {
    return navigator.userAgent.match(/iPhone|iPad|iPod/i)
  },
  Windows: function () {
    return navigator.userAgent.match(/IEMobile/i)
  },
  any: function () {
    return isMobile.Android() || isMobile.iOS() || isMobile.Windows()
  },
}

if (!isMobile.any()) {
  $('body').addClass('is-not-ios')
  $('.show-ios, .show-android').addClass('disabled')
  $('.show-no-device').removeClass('disabled')
}

if (isMobile.Android()) {
  $('body').addClass('is-not-ios')
  $('head').append('<meta name="theme-color" content="#FFFFFF"> />')
  $('.show-android').removeClass('disabled')
  $(
    '.show-ios, .show-no-device, .simulate-android, .simulate-iphones'
  ).addClass('disabled')
}

if (isMobile.iOS()) {
  $('body').addClass('is-ios')
  $('.show-ios').removeClass('disabled')
  $(
    '.show-android, .show-no-device, .simulate-android, .simulate-iphones'
  ).addClass('disabled')
}

if (pwaEnabled === true) {
  //Setting Timeout Before Prompt Shows Again if Dismissed
  var now = new Date()
  var start = new Date(now.getFullYear(), 0, 0)
  var diff = now - start
  var oneDay = 1000 * 60 * 60 * 24
  var day = Math.floor(diff / oneDay)
  var dismissDate = localStorage.getItem('Appkit-PWA-Timeout-Value')

  if (day - dismissDate > pwaRemind) {
    localStorage.removeItem('Appkit-PWA-Prompt')
  }

  //Dismiss Prompt Button
  $('.pwa-dismiss').on('click', function () {
    console.log('User Closed Add to Home / PWA Prompt')
    localStorage.setItem('Appkit-PWA-Prompt', 'install-rejected')
    $('body')
      .find('#menu-install-pwa-android, #menu-install-pwa-ios, .menu-hider')
      .removeClass('menu-active')
    localStorage.setItem('Appkit-PWA-Timeout-Value', day)
  })

  //Detecting Mobile Operating Systems
  var isMobile = {
    Android: function () {
      return navigator.userAgent.match(/Android/i)
    },
    iOS: function () {
      return navigator.userAgent.match(/iPhone|iPad|iPod/i)
    },
    any: function () {
      return isMobile.Android() || isMobile.iOS() || isMobile.Windows()
    },
  }
  var isInWebAppiOS = window.navigator.standalone == true
  var isInWebAppChrome = window.matchMedia('(display-mode: standalone)').matches

  //Trigger Install Prompt for Android
  if (isMobile.Android()) {
    function showInstallPrompt() {
      if ($('#menu-install-pwa-android, .add-to-home').length) {
        if (localStorage.getItem('Appkit-PWA-Prompt') != 'install-rejected') {
          setTimeout(function () {
            $('.add-to-home').addClass(
              'add-to-home-visible add-to-home-android'
            )
            $('#menu-install-pwa-android, .menu-hider').addClass('menu-active')
          }, 4500)
          console.log('Triggering PWA Window for Android')
        } else {
          console.log(
            'PWA Install Rejected. Will Show Again in ' +
              (dismissDate - day + pwaRemind) +
              ' Days'
          )
        }
      } else {
        console.log(
          'The div #menu-install-pwa-android was not found. Please add this div to show the install window'
        )
      }
    }
    let deferredPrompt
    window.addEventListener('beforeinstallprompt', (e) => {
      e.preventDefault()
      deferredPrompt = e
      showInstallPrompt()
    })
    $('.pwa-install').on('click', function (e) {
      deferredPrompt.prompt()
      deferredPrompt.userChoice.then((choiceResult) => {
        if (choiceResult.outcome === 'accepted') {
          //console.log('User accepted the A2HS prompt');
        } else {
          //console.log('User dismissed the A2HS prompt');
        }
        deferredPrompt = null
      })
    })
    window.addEventListener('appinstalled', (evt) => {
      $('#menu-install-pwa-android, .menu-hider').removeClass('menu-active')
    })
  }

  //Trigger Install Guide iOS
  if (isMobile.iOS()) {
    if (!isInWebAppiOS) {
      if ($('#menu-install-pwa-ios, .add-to-home').length) {
        if (localStorage.getItem('Appkit-PWA-Prompt') != 'install-rejected') {
          console.log('Triggering PWA Window for iOS')
          setTimeout(function () {
            $('.add-to-home').addClass('add-to-home-visible add-to-home-ios')
            $('#menu-install-pwa-ios, .menu-hider').addClass('menu-active')
          }, 4500)
        } else {
          console.log(
            'PWA Install Rejected. Will Show Again in ' +
              (dismissDate - day + pwaRemind) +
              ' Days'
          )
        }
      } else {
        console.log(
          'The div #menu-install-pwa-ios was not found. Please add this div to show the install window'
        )
      }
    }
  }
}

const loadScript = () => window.addEventListener('load', () => isMobile())

export default loadScript

这是我将使用的组件loadScript

const MasterIndexPage = () => {

useEffect(() => {
loadScript()
  }, [])

<div
      id="menu-video"
      className="menu menu-box-bottom rounded-m"
      data-menu-height="410"
      data-menu-effect="menu-over"
    >
      <div class="responsive-iframe max-iframe">
        <iframe
          src="https://www.youtube.com/embed/qCSBMbUa9jg"
          frameborder="0"
          allowfullscreen
        ></iframe>
      </div>
      <div className="menu-title">
        <p className="color-highlight">Learn</p>
        <h1>How to install</h1>
        <a href="#" className="close-menu">
          <i className="fa fa-times-circle"></i>
        </a>
      </div>
      <div className="content mt-n2">
        <p>Install Sparkle</p>
        <a
          href="#"
          className="close-menu btn btn-full btn-m shadow-l rounded-s text-uppercase font-600 bg-green-dark mt-n2"
        >
          Done
        </a>
      </div>
    </div>
}

谁能帮我解决问题?这是我只使用完整 JS 时的预期输出。

在此处输入图像描述

每当我加载页面时都会提示。

标签: javascriptjqueryreactjsreact-hooksgatsby

解决方案


构建 gatsby 时遇到错误

总结和简化,gatsby develop由客户端(浏览器)使用 web 套接字直接解释(这就是你有即时刷新的原因)并且有一个windowornavigator对象,而gatsby build由节点服务器处理,显然没有window,document或其他全局对象(如navigator)因为它们甚至还没有定义。

这是 Gatsby 中一个非常常见且直接的问题,可以通过添加以下条件轻松绕过:

typeof window !== `undefined`

如果定义了窗口对象,则意味着您不在构建时进程中,因此您可以访问它。

useEffect(() => {
  if(typeof window !== 'undefined') loadScript()
  }, [])

或者通过在每个 navigator 语句中添加它:

Android: function () {
    return typeof window !== 'undefined' && navigator.userAgent.match(/Android/i)
  },
  iOS: function () {
    return typeof window !== 'undefined' && 
 navigator.userAgent.match(/iPhone|iPad|iPod/i)
  },
  Windows: function () {
    return typeof window !== 'undefined' && 
 navigator.userAgent.match(/IEMobile/i)
  },
  any: function () {
    return typeof window !== 'undefined' &&  (isMobile.Android() || isMobile.iOS() || isMobile.Windows())
  },
}

当然,根据需要调整它。

此解决方法 ( typeof window !== 'undefined') 适用于所有对windowdocumentnavigator和其他在 SSR 中不可用的全局对象的所有引用。


超出了问题的范围。极不建议避免像您正在做的那样(使用 jQuery)将 DOM 指向:

$('body').addClass('is-not-ios')

真的,不要这样做。

使用 React,您正在创建和操作虚拟 DOM (vDOM),以避免像操作真实 DOM 那样的高性能操作。使用 React,您可以使用钩子 ( useRef) 或其他变通方法来指向 React 范围和环境中的那些元素。

你的方法会导致水合问题,因为你正在执行 React 范围之外的操作。这意味着您可能会遇到某些元素的渲染问题,尤其是在前后移动或触发某些显示动作时。


你知道在这类问题上我最好的方法是什么吗?

是的,避免使用 jQuery。

您可以通过创建一个useState根据您userAgent添加类的逻辑更改其值的钩子来实现相同的行为。

例如:

const MasterIndexPage = props =>{
   const [userAgent, setUserAgent]=useState("");

   const detectUserAgent = ()=>{
    if(navigator.userAgent.match(/Android/i)) setUserAgent("isAndroid");
   // and so on for the rest
   
   }

   useEffect(() => {
     detectUserAgent()
  }, [])
 
   return <main className={`${userAgent === "isAndroid" ? "someClassName" : ``}`}>
     <div
      id="menu-video"
      className="menu menu-box-bottom rounded-m"
      data-menu-height="410"
      data-menu-effect="menu-over"
    >
      <div class="responsive-iframe max-iframe">
        <iframe
          src="https://www.youtube.com/embed/qCSBMbUa9jg"
          frameborder="0"
          allowfullscreen
        ></iframe>
      </div>
      <div className="menu-title">
        <p className="color-highlight">Learn</p>
        <h1>How to install</h1>
        <a href="#" className="close-menu">
          <i className="fa fa-times-circle"></i>
        </a>
      </div>
      <div className="content mt-n2">
        <p>Install Sparkle</p>
        <a
          href="#"
          className="close-menu btn btn-full btn-m shadow-l rounded-s text-uppercase font-600 bg-green-dark mt-n2"
        >
          Done
        </a>
      </div>
    </div>
    <main>
}

为了避免超出实际的答案,我只添加了 Android 解决方法,但相同的逻辑适用于其余部分。您甚至可以在 中添加一个函数调用className来返回userAgent验证以避免像className={someFunctionThatReturnsTheClassname()}. 当然,即使在获取和返回userAgent值的自定义挂钩中,您也可以将所有这些逻辑扩展和隔离到单独的函数中。


推荐阅读