Ben*_* Sx 15 device-detection reactjs server-side-rendering next.js
在 Web 应用程序上,我想显示两种不同的菜单,一种用于移动设备,一种用于桌面浏览器。我将 Next.js 应用程序与服务器端渲染和库react-device-detect 一起使用。
import Link from "next/link";
import { BrowserView, MobileView } from "react-device-detect";
export default () => (
<div>
Hello World.{" "}
<Link href="/about">
<a>About</a>
</Link>
<BrowserView>
<h1> This is rendered only in browser </h1>
</BrowserView>
<MobileView>
<h1> This is rendered only on mobile </h1>
</MobileView>
</div>
);
Run Code Online (Sandbox Code Playgroud)
如果您在浏览器中打开它并切换到移动视图并查看控制台,您会收到此错误:
警告:文本内容不匹配。服务器:“仅在浏览器中呈现”客户端:“仅在移动设备上呈现”
发生这种情况是因为服务器的渲染检测到浏览器,而在客户端,他是一个移动设备。我发现的唯一解决方法是生成两者并使用如下 CSS:
.activeOnMobile {
@media screen and (min-width: 800px) {
display: none;
}
}
.activeOnDesktop {
@media screen and (max-width: 800px) {
display: none;
}
}
Run Code Online (Sandbox Code Playgroud)
而不是图书馆,但我真的不喜欢这种方法。有人知道直接在反应代码中处理 SSR 应用程序上的设备类型的好做法吗?
Pau*_*Dyk 19
最新更新:
因此,如果您不介意在客户端执行此操作,则可以按照以下几个人的建议使用动态导入。这将适用于您使用静态页面生成的用例。
我创建了一个组件,它将所有react-device-detect导出作为道具传递(明智的做法是仅过滤掉所需的导出,因为这样就不会进行 treeshake)
// Device/Device.tsx
import { ReactNode } from 'react'
import * as rdd from 'react-device-detect'
interface DeviceProps {
children: (props: typeof rdd) => ReactNode
}
export default function Device(props: DeviceProps) {
return <div className="device-layout-component">{props.children(rdd)}</div>
}
Run Code Online (Sandbox Code Playgroud)
// Device/index.ts
import dynamic from 'next/dynamic'
const Device = dynamic(() => import('./Device'), { ssr: false })
export default Device
Run Code Online (Sandbox Code Playgroud)
然后当你想使用组件时,你可以这样做
const Example = () => {
return (
<Device>
{({ isMobile }) => {
if (isMobile) return <div>My Mobile View</div>
return <div>My Desktop View</div>
}}
</Device>
)
}
Run Code Online (Sandbox Code Playgroud)
我个人只是使用一个钩子来做到这一点,虽然初始 props 方法更好。
const Example = () => {
return (
<Device>
{({ isMobile }) => {
if (isMobile) return <div>My Mobile View</div>
return <div>My Desktop View</div>
}}
</Device>
)
}
Run Code Online (Sandbox Code Playgroud)
我遇到了滚动动画在移动设备上很烦人的问题,所以我制作了一个基于设备的滚动动画组件;
import React, { ReactNode } from 'react'
import ScrollAnimation, { ScrollAnimationProps } from 'react-animate-on-scroll'
import useMobileDetect from 'src/utils/useMobileDetect'
interface DeviceScrollAnimation extends ScrollAnimationProps {
device: 'mobile' | 'desktop'
children: ReactNode
}
export default function DeviceScrollAnimation({ device, animateIn, animateOut, initiallyVisible, ...props }: DeviceScrollAnimation) {
const currentDevice = useMobileDetect()
const flag = device === 'mobile' ? currentDevice.isMobile() : device === 'desktop' ? currentDevice.isDesktop() : true
return (
<ScrollAnimation
animateIn={flag ? animateIn : 'none'}
animateOut={flag ? animateOut : 'none'}
initiallyVisible={flag ? initiallyVisible : true}
{...props}
/>
)
}
Run Code Online (Sandbox Code Playgroud)
因此,在进一步深入兔子洞之后,我想出的最佳解决方案是在 useEffect 中使用 react-device-detect,如果您进一步检查设备检测,您会注意到它导出通过ua-parser-jslib设置的 const
import { useEffect } from 'react'
const getMobileDetect = (userAgent: NavigatorID['userAgent']) => {
const isAndroid = () => Boolean(userAgent.match(/Android/i))
const isIos = () => Boolean(userAgent.match(/iPhone|iPad|iPod/i))
const isOpera = () => Boolean(userAgent.match(/Opera Mini/i))
const isWindows = () => Boolean(userAgent.match(/IEMobile/i))
const isSSR = () => Boolean(userAgent.match(/SSR/i))
const isMobile = () => Boolean(isAndroid() || isIos() || isOpera() || isWindows())
const isDesktop = () => Boolean(!isMobile() && !isSSR())
return {
isMobile,
isDesktop,
isAndroid,
isIos,
isSSR,
}
}
const useMobileDetect = () => {
useEffect(() => {}, [])
const userAgent = typeof navigator === 'undefined' ? 'SSR' : navigator.userAgent
return getMobileDetect(userAgent)
}
export default useMobileDetect
Run Code Online (Sandbox Code Playgroud)
这导致初始设备成为导致错误检测的服务器。
我分叉了 repo 并创建并添加了一个ssr-selector,它需要你传入一个用户代理。这可以使用初始道具完成
由于 Ipad 没有提供正确或足够好的定义用户代理,请参阅此问题,我决定创建一个钩子来更好地检测设备
import { useEffect, useState } from 'react'
function isTouchDevice() {
if (typeof window === 'undefined') return false
const prefixes = ' -webkit- -moz- -o- -ms- '.split(' ')
function mq(query) {
return typeof window !== 'undefined' && window.matchMedia(query).matches
}
// @ts-ignore
if ('ontouchstart' in window || (window?.DocumentTouch && document instanceof DocumentTouch)) return true
const query = ['(', prefixes.join('touch-enabled),('), 'heartz', ')'].join('') // include the 'heartz' - https://git.io/vznFH
return mq(query)
}
export default function useIsTouchDevice() {
const [isTouch, setIsTouch] = useState(false)
useEffect(() => {
const { isAndroid, isIPad13, isIPhone13, isWinPhone, isMobileSafari, isTablet } = require('react-device-detect')
setIsTouch(isTouch || isAndroid || isIPad13 || isIPhone13 || isWinPhone || isMobileSafari || isTablet || isTouchDevice())
}, [])
return isTouch
Run Code Online (Sandbox Code Playgroud)
因为我每次调用该钩子时都需要该包,所以更新了 UA 信息,它还修复了 SSR 不同步警告。
Dyl*_*211 16
我认为您应该通过在您的页面中使用 getInitialProps 来完成它,因为它同时在服务器和客户端上运行,并通过首先检测您是否只是收到对网页的请求来获取设备类型(因此您仍然在服务器),或者如果您正在重新渲染(因此您在客户端上)。
// index.js
IndexPage.getInitialProps = ({ req }) => {
let userAgent;
if (req) { // if you are on the server and you get a 'req' property from your context
userAgent = req.headers['user-agent'] // get the user-agent from the headers
} else {
userAgent = navigator.userAgent // if you are on the client you can access the navigator from the window object
}
}
Run Code Online (Sandbox Code Playgroud)
现在您可以使用正则表达式查看设备是移动设备还是台式机。
// still in getInitialProps
let isMobile = Boolean(userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
))
return { isMobile }
Run Code Online (Sandbox Code Playgroud)
现在您可以访问将返回 true 或 false 的 isMobile 道具
const IndexPage = ({ isMobile }) => {
return (
<div>
{isMobile ? (<h1>I am on mobile!</h1>) : (<h1>I am on desktop! </h1>)}
</div>
)
}
Run Code Online (Sandbox Code Playgroud)
我从这篇文章中得到了这个答案, 希望对你有帮助
更新
从 Next 9.5.0 开始,getInitialProps将被替换为getStaticProps和getServerSideProps。虽然getStaticProps是用于获取静态数据,这将被用于创建在构建时一个HTML页面,getServerSideProps动态地生成对每个请求的页面,并且接收context与所述对象req支柱一样getInitialProps。不同的getServerSideProps是不会知道navigator,因为它只是服务器端。用法也有点不同,因为您必须导出异步函数,而不是在组件上声明方法。它会这样工作:
const HomePage = ({ deviceType }) => {
let componentToRender
if (deviceType === 'mobile') {
componentToRender = <MobileComponent />
} else {
componentToRender = <DesktopComponent />
}
return componentToRender
}
export async function getServerSideProps(context) {
const UA = context.req.headers['user-agent'];
const isMobile = Boolean(UA.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
))
return {
props: {
deviceType: isMobile ? 'mobile' : 'desktop'
}
}
}
export default HomePage
Run Code Online (Sandbox Code Playgroud)
请注意,由于getServerSideProps和getStaticProps是互斥的,因此您需要放弃由 提供的 SSG 优势getStaticProps才能了解用户的设备类型。如果您只需要处理一些样式细节,我建议不要为此目的使用 getServerSideProps。如果页面结构因设备类型而异,则可能值得
小智 7
react-device-detect现在提供了一个getSelectorsByUserAgent可以使用服务器端的方法,并且您需要将用户代理作为参数传递。如果您在 next.js 中使用应用程序路由器,则可以从标头中获取用户代理。
// index.tsx
import { headers } from "next/headers"
import { getSelectorsByUserAgent } from "react-device-detect"
import MobileVersion from "./mobile"
import BrowserVersion from "./browser"
export default function SomeComponent() {
// you can get more than just isMobile from this.
const { isMobile } = getSelectorsByUserAgent(
headers().get("user-agent") ?? ""
)
return isMobile ? <Mobile /> : <Browser />
}
Run Code Online (Sandbox Code Playgroud)
//mobile.tsx
export default () => <div>this is the mobile version</div>
Run Code Online (Sandbox Code Playgroud)
//browser.tsx
export default () => <div>this is the browser version</div>
Run Code Online (Sandbox Code Playgroud)
您可以使用 next/dynamic 动态加载组件,并且只会加载适当的组件。
在我的例子中,您可以使用react-detect-device或is-mobile。在这种情况下,我为移动设备和桌面设备创建了单独的布局,并根据设备加载适当的组件。
import dynamic from 'next/dynamic';
const mobile = require('is-mobile');
const ShowMobile = dynamic(() => mobile() ? import('./ShowMobile.mobile') : import('./ShowMobile'), { ssr: false })
const TestPage = () => {
return <ShowMobile />
}
export default TestPage
Run Code Online (Sandbox Code Playgroud)
您可以查看codesandbox。只会加载所需的 component.JS。
编辑:
上述与条件加载组件有什么不同?例如
isMobile ? <MobileComponent /> : <NonMobileComponent />
Run Code Online (Sandbox Code Playgroud)
第一个解决方案不会加载 JS 文件,而在第二个解决方案中,两个 JS 文件都会被加载。这样您就可以节省一次往返路程。
| 归档时间: |
|
| 查看次数: |
25478 次 |
| 最近记录: |