Gatsby/React 导航器
Gatsby/React navigator
你好,我目前遇到一个问题,我想在我的 React 组件中使用 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 时的预期输出。
每当我加载页面时都会提示。
I'm encountering an error when building my gatsby
总结和简化,gatsby develop
由客户端(浏览器)使用网络套接字直接解释(这就是为什么你有即时刷新)并且有一个 window
或 navigator
对象,而 gatsby build
由节点服务器处理,显然 there's no window
, document
or other global objects (like navigator
) 因为它们甚至还没有定义。
这是 Gatsby 中一个非常常见且直接的问题,可以通过添加以下条件轻松绕过:
typeof window !== `undefined`
如果定义了 window 对象,意味着您不在构建时进程中,因此您可以访问它。
useEffect(() => {
if(typeof window !== 'undefined') loadScript()
}, [])
或者在每个导航器语句中添加它:
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'
) 适用于对 window
、document
、navigator
的所有引用,以及您代码中所有 SSR 中不可用的其他全局对象。
超出问题范围。极不建议避免像你正在做的那样(使用 jQuery)指向 DOM:
$('body').addClass('is-not-ios')
真的,别这样。
使用 React,您正在创建和操纵虚拟 DOM (vDOM),以避免像操纵真实 DOM 那样的高性能操作。使用 React,您可以使用挂钩 (useRef
) 或其他变通方法来指向 React 范围和环境中的那些元素。
您的方法将导致您遇到 hydration 问题,因为您正在执行 React 范围之外的操作。这意味着您可能会遇到某些元素的渲染问题,尤其是在前后移动或触发某些显示操作时。
Do you know what can be my best approach in this kind of problem?
是的,避免使用 jQuery。
您可以通过创建一个 useState
挂钩来实现相同的行为,该挂钩根据您的 userAgent
逻辑更改其值以添加 class.
例如:
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
中添加一个函数调用到 return 和 userAgent
验证,以避免像 className={someFunctionThatReturnsTheClassname()}
这样的三元条件。当然,您可以将所有这些逻辑扩展并隔离到单独的函数中,即使在获取和 returns userAgent
值的自定义挂钩中也是如此。
你好,我目前遇到一个问题,我想在我的 React 组件中使用 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 时的预期输出。
每当我加载页面时都会提示。
I'm encountering an error when building my gatsby
总结和简化,gatsby develop
由客户端(浏览器)使用网络套接字直接解释(这就是为什么你有即时刷新)并且有一个 window
或 navigator
对象,而 gatsby build
由节点服务器处理,显然 there's no window
, document
or other global objects (like navigator
) 因为它们甚至还没有定义。
这是 Gatsby 中一个非常常见且直接的问题,可以通过添加以下条件轻松绕过:
typeof window !== `undefined`
如果定义了 window 对象,意味着您不在构建时进程中,因此您可以访问它。
useEffect(() => {
if(typeof window !== 'undefined') loadScript()
}, [])
或者在每个导航器语句中添加它:
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'
) 适用于对 window
、document
、navigator
的所有引用,以及您代码中所有 SSR 中不可用的其他全局对象。
超出问题范围。极不建议避免像你正在做的那样(使用 jQuery)指向 DOM:
$('body').addClass('is-not-ios')
真的,别这样。
使用 React,您正在创建和操纵虚拟 DOM (vDOM),以避免像操纵真实 DOM 那样的高性能操作。使用 React,您可以使用挂钩 (useRef
) 或其他变通方法来指向 React 范围和环境中的那些元素。
您的方法将导致您遇到 hydration 问题,因为您正在执行 React 范围之外的操作。这意味着您可能会遇到某些元素的渲染问题,尤其是在前后移动或触发某些显示操作时。
Do you know what can be my best approach in this kind of problem?
是的,避免使用 jQuery。
您可以通过创建一个 useState
挂钩来实现相同的行为,该挂钩根据您的 userAgent
逻辑更改其值以添加 class.
例如:
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
中添加一个函数调用到 return 和 userAgent
验证,以避免像 className={someFunctionThatReturnsTheClassname()}
这样的三元条件。当然,您可以将所有这些逻辑扩展并隔离到单独的函数中,即使在获取和 returns userAgent
值的自定义挂钩中也是如此。