如何使用 Next.js 在 React SSR App 上检测设备?
How to detect the device on React SSR App with Next.js?
在 Web 应用程序上,我想显示两个不同的菜单,一个用于移动浏览器,一个用于桌面浏览器。
我将 Next.js 应用程序与服务器端渲染和库 react-device-detect.
一起使用
这里是 CodeSandox link.
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>
);
如果您在浏览器中打开它并切换到移动设备查看并查看控制台,您会收到此错误:
Warning: Text content did not match. Server: " This is rendered only
in browser " Client: " This is rendered only on mobile "
发生这种情况是因为服务器渲染检测到浏览器,而在客户端,他是移动设备。我发现的唯一解决方法是生成两者并像这样使用 CSS:
.activeOnMobile {
@media screen and (min-width: 800px) {
display: none;
}
}
.activeOnDesktop {
@media screen and (max-width: 800px) {
display: none;
}
}
而不是图书馆,但我不太喜欢这种方法。有人知道直接在 React 代码中处理 SSR 应用程序上的设备类型的良好做法吗?
我认为你应该通过在你的页面中使用 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
}
}
现在您可以使用正则表达式来查看设备是移动设备还是桌面设备。
// still in getInitialProps
let isMobile = Boolean(userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
))
return { isMobile }
现在您可以访问将 return true 或 false
的 isMobile 道具
const IndexPage = ({ isMobile }) => {
return (
<div>
{isMobile ? (<h1>I am on mobile!</h1>) : (<h1>I am on desktop! </h1>)}
</div>
)
}
我从 this article here 那里得到了这个答案
希望对你有所帮助
更新
从 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
请注意,由于getServerSideProps
和getStaticProps
是互斥的,您需要放弃getStaticProps
给予的SSG优势才能知道用户的设备类型.如果您只需要处理几个样式细节,我建议不要将 getServerSideProps 用于此目的。如果页面的结构根据设备类型有很大不同,那也许是值得的
最新更新:
因此,如果您不介意在客户端执行此操作,您可以按照下面一些人的建议使用动态导入。这将适用于您使用静态页面生成的用例。
我创建了一个组件,它将所有 react-device-detect
导出作为 props 传递(明智的做法是只过滤掉需要的导出,因为这样就不会进行 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>
}
// Device/index.ts
import dynamic from 'next/dynamic'
const Device = dynamic(() => import('./Device'), { ssr: false })
export default Device
然后当你想使用该组件时,你可以这样做
const Example = () => {
return (
<Device>
{({ isMobile }) => {
if (isMobile) return <div>My Mobile View</div>
return <div>My Desktop View</div>
}}
</Device>
)
}
我个人只是用一个钩子来做这个,虽然初始道具方法更好。
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
我遇到了滚动动画在移动设备上很烦人的问题,所以我制作了一个基于设备的启用滚动动画组件;
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}
/>
)
}
更新:
所以在进一步深入兔子洞之后,我想到的最好的解决方案是在 useEffect 中使用 react-device-detect,如果你进一步检查设备检测,你会注意到它导出了已设置的常量通过 ua-parser-js
库
export const UA = new UAParser();
export const browser = UA.getBrowser();
export const cpu = UA.getCPU();
export const device = UA.getDevice();
export const engine = UA.getEngine();
export const os = UA.getOS();
export const ua = UA.getUA();
export const setUA = (uaStr) => UA.setUA(uaStr);
这导致初始设备成为导致错误检测的服务器。
我分叉了 repo 并创建并添加了一个 ssr-selector,它需要你传入一个用户代理。这可以使用初始 props
更新:
由于 Ipad 没有提供正确或定义得足够好的用户代理,请参阅此 issue,我决定创建一个挂钩以更好地检测设备
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
因为我每次调用那个钩子时都需要这个包,所以更新了 UA 信息,它还修复了 SSR 不同步警告。
当前 Next.js (v 9.5+) 我使用 next/dynamic
和 react-detect-device
.
完成了这个
例如,在我的 header
组件上:
...
import dynamic from 'next/dynamic';
...
const MobileMenuHandler = dynamic(() => import('./mobileMenuHandler'), {
ssr: false,
});
return (
...
<MobileMenuHandler
isMobileMenuOpen={isMobileMenuOpen}
setIsMobileMenuOpen={setIsMobileMenuOpen}
/>
)
...
然后在MobileMenuHandler
,只在客户端调用:
import { isMobile } from 'react-device-detect';
...
return(
{isMobile && !isMobileMenuOpen ? (
<Menu
onClick={() => setIsMobileMenuOpen(true)}
className={classes.menuIcon}
/>
) : null}
)
因此,react-detect-device
仅在客户端处于活动状态,可以提供正确的读数。
只加载动态需要的JS文件
您可以使用 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
您可以查看codesandbox。只会加载所需的 component.JS。
编辑:
以上与条件加载组件有何不同?例如
isMobile ? <MobileComponent /> : <NonMobileComponent />
第一种方案不会加载JS文件,而第二种方案会同时加载两个JS文件。所以你节省了一个往返。
如果您不介意始终渲染桌面版本并在前端计算逻辑,那么挂钩逻辑可以非常简单。
export const useDevice = () => {
const [firstLoad, setFirstLoad] = React.useState(true);
React.useEffect(() => { setFirstLoad(false); }, []);
const ssr = firstLoad || typeof navigator === "undefined";
const isAndroid = !ssr && /android/i.test(navigator.userAgent);
const isIos = !ssr && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
return {
isAndroid,
isIos,
isDesktop: !isAndroid && !isIos
};
};
import React, { useState, useEffect }
import { isMobile } from 'react-device-detect'
...
const [_isMobile, setMobile] = useState();
useEffect(() => {
setMobile(isMobile);
}, [setMobile]);
<div hidden={_isMobile}> Desktop View</div>
<div hidden={!_isMobile}> MobileView </div>
这总是有效的。 (我在尝试上述技术后使用了这个包,但它对我不起作用。)
优点:组件呈现服务器端,因此在尝试检测用户代理时客户端不会闪烁。
import { isMobile } from "mobile-device-detect";
只需导入包并创建您的布局。
import { isMobile } from "mobile-device-detect";
const Desktop = () => {
return (
<>
desktop
</>
);
};
Desktop.layout = Layout;
const Mobile = () => {
return (
<>
mobile
</>
);
};
Mobile.layout = LayoutMobile;
const Page = isMobile ? Desktop : Mobile;
export default Page;
能够通过使用 React state 来避免动态导入或组件道具。对于我的用例,我试图检测它是否是 Safari,但这也适用于其他情况。
导入代码
import { browserName } from 'react-device-detect';
组件代码
const [isSafari, setIsSafari] = useState(false);
useEffect(() => {
setIsSafari(browserName === 'Safari');
}, [browserName]);
// Then respect the state in the render
return <div data-is-safari={isSafari} />;
当我在做我的一个 next.js 项目时,我遇到了类似的情况。我从答案中得到了一些想法。我确实用下面的方法解决了它。
首先,我使用 react-device-detect
制作了自定义挂钩
//hooks/useDevice.ts
import { isDesktop, isMobile } from 'react-device-detect';
interface DeviceDetection {
isMobile: boolean;
isDesktop: boolean;
}
const useDevice = (): DeviceDetection => ({
isMobile,
isDesktop
});
export default useDevice;
其次,我制作了一个使用自定义钩子的组件
//Device/Device.tsx
import { ReactElement } from 'react';
import useDevice from '@/hooks/useDevice';
export interface DeviceProps {
desktop?: boolean;
mobile?: boolean;
children: ReactElement;
}
export const Device = ({ desktop, mobile, children }: DeviceProps): ReactElement | null => {
const { isMobile } = useDevice();
return (isMobile && mobile) || (!isMobile && desktop) ? children : null;
};
第三,我使用 next.js next/dynamic
动态导入组件
//Device/index.tsx
import dynamic from 'next/dynamic';
import type { DeviceProps } from './Device';
export const Device = dynamic<DeviceProps>(() => import('./Device').then((mod) => mod.Device), {
ssr: false
});
最后,我在页面中使用了它。
//pages/my-page.tsx
import { Device } from '@/components/Device';
<Device desktop>
<my-component>Desktop</my-component>
</Device>
<Device mobile>
<my-component>Mobile</my-component>
</Device>
在 Web 应用程序上,我想显示两个不同的菜单,一个用于移动浏览器,一个用于桌面浏览器。 我将 Next.js 应用程序与服务器端渲染和库 react-device-detect.
一起使用这里是 CodeSandox link.
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>
);
如果您在浏览器中打开它并切换到移动设备查看并查看控制台,您会收到此错误:
Warning: Text content did not match. Server: " This is rendered only in browser " Client: " This is rendered only on mobile "
发生这种情况是因为服务器渲染检测到浏览器,而在客户端,他是移动设备。我发现的唯一解决方法是生成两者并像这样使用 CSS:
.activeOnMobile {
@media screen and (min-width: 800px) {
display: none;
}
}
.activeOnDesktop {
@media screen and (max-width: 800px) {
display: none;
}
}
而不是图书馆,但我不太喜欢这种方法。有人知道直接在 React 代码中处理 SSR 应用程序上的设备类型的良好做法吗?
我认为你应该通过在你的页面中使用 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
}
}
现在您可以使用正则表达式来查看设备是移动设备还是桌面设备。
// still in getInitialProps
let isMobile = Boolean(userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
))
return { isMobile }
现在您可以访问将 return true 或 false
的 isMobile 道具const IndexPage = ({ isMobile }) => {
return (
<div>
{isMobile ? (<h1>I am on mobile!</h1>) : (<h1>I am on desktop! </h1>)}
</div>
)
}
我从 this article here 那里得到了这个答案 希望对你有所帮助
更新
从 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
请注意,由于getServerSideProps
和getStaticProps
是互斥的,您需要放弃getStaticProps
给予的SSG优势才能知道用户的设备类型.如果您只需要处理几个样式细节,我建议不要将 getServerSideProps 用于此目的。如果页面的结构根据设备类型有很大不同,那也许是值得的
最新更新:
因此,如果您不介意在客户端执行此操作,您可以按照下面一些人的建议使用动态导入。这将适用于您使用静态页面生成的用例。
我创建了一个组件,它将所有 react-device-detect
导出作为 props 传递(明智的做法是只过滤掉需要的导出,因为这样就不会进行 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>
}
// Device/index.ts
import dynamic from 'next/dynamic'
const Device = dynamic(() => import('./Device'), { ssr: false })
export default Device
然后当你想使用该组件时,你可以这样做
const Example = () => {
return (
<Device>
{({ isMobile }) => {
if (isMobile) return <div>My Mobile View</div>
return <div>My Desktop View</div>
}}
</Device>
)
}
我个人只是用一个钩子来做这个,虽然初始道具方法更好。
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
我遇到了滚动动画在移动设备上很烦人的问题,所以我制作了一个基于设备的启用滚动动画组件;
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}
/>
)
}
更新:
所以在进一步深入兔子洞之后,我想到的最好的解决方案是在 useEffect 中使用 react-device-detect,如果你进一步检查设备检测,你会注意到它导出了已设置的常量通过 ua-parser-js
库
export const UA = new UAParser();
export const browser = UA.getBrowser();
export const cpu = UA.getCPU();
export const device = UA.getDevice();
export const engine = UA.getEngine();
export const os = UA.getOS();
export const ua = UA.getUA();
export const setUA = (uaStr) => UA.setUA(uaStr);
这导致初始设备成为导致错误检测的服务器。
我分叉了 repo 并创建并添加了一个 ssr-selector,它需要你传入一个用户代理。这可以使用初始 props
更新:
由于 Ipad 没有提供正确或定义得足够好的用户代理,请参阅此 issue,我决定创建一个挂钩以更好地检测设备
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
因为我每次调用那个钩子时都需要这个包,所以更新了 UA 信息,它还修复了 SSR 不同步警告。
当前 Next.js (v 9.5+) 我使用 next/dynamic
和 react-detect-device
.
例如,在我的 header
组件上:
...
import dynamic from 'next/dynamic';
...
const MobileMenuHandler = dynamic(() => import('./mobileMenuHandler'), {
ssr: false,
});
return (
...
<MobileMenuHandler
isMobileMenuOpen={isMobileMenuOpen}
setIsMobileMenuOpen={setIsMobileMenuOpen}
/>
)
...
然后在MobileMenuHandler
,只在客户端调用:
import { isMobile } from 'react-device-detect';
...
return(
{isMobile && !isMobileMenuOpen ? (
<Menu
onClick={() => setIsMobileMenuOpen(true)}
className={classes.menuIcon}
/>
) : null}
)
因此,react-detect-device
仅在客户端处于活动状态,可以提供正确的读数。
只加载动态需要的JS文件
您可以使用 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
您可以查看codesandbox。只会加载所需的 component.JS。
编辑:
以上与条件加载组件有何不同?例如
isMobile ? <MobileComponent /> : <NonMobileComponent />
第一种方案不会加载JS文件,而第二种方案会同时加载两个JS文件。所以你节省了一个往返。
如果您不介意始终渲染桌面版本并在前端计算逻辑,那么挂钩逻辑可以非常简单。
export const useDevice = () => {
const [firstLoad, setFirstLoad] = React.useState(true);
React.useEffect(() => { setFirstLoad(false); }, []);
const ssr = firstLoad || typeof navigator === "undefined";
const isAndroid = !ssr && /android/i.test(navigator.userAgent);
const isIos = !ssr && /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
return {
isAndroid,
isIos,
isDesktop: !isAndroid && !isIos
};
};
import React, { useState, useEffect }
import { isMobile } from 'react-device-detect'
...
const [_isMobile, setMobile] = useState();
useEffect(() => {
setMobile(isMobile);
}, [setMobile]);
<div hidden={_isMobile}> Desktop View</div>
<div hidden={!_isMobile}> MobileView </div>
这总是有效的。 (我在尝试上述技术后使用了这个包,但它对我不起作用。)
优点:组件呈现服务器端,因此在尝试检测用户代理时客户端不会闪烁。
import { isMobile } from "mobile-device-detect";
只需导入包并创建您的布局。
import { isMobile } from "mobile-device-detect";
const Desktop = () => {
return (
<>
desktop
</>
);
};
Desktop.layout = Layout;
const Mobile = () => {
return (
<>
mobile
</>
);
};
Mobile.layout = LayoutMobile;
const Page = isMobile ? Desktop : Mobile;
export default Page;
能够通过使用 React state 来避免动态导入或组件道具。对于我的用例,我试图检测它是否是 Safari,但这也适用于其他情况。
导入代码
import { browserName } from 'react-device-detect';
组件代码
const [isSafari, setIsSafari] = useState(false);
useEffect(() => {
setIsSafari(browserName === 'Safari');
}, [browserName]);
// Then respect the state in the render
return <div data-is-safari={isSafari} />;
当我在做我的一个 next.js 项目时,我遇到了类似的情况。我从答案中得到了一些想法。我确实用下面的方法解决了它。
首先,我使用 react-device-detect
//hooks/useDevice.ts
import { isDesktop, isMobile } from 'react-device-detect';
interface DeviceDetection {
isMobile: boolean;
isDesktop: boolean;
}
const useDevice = (): DeviceDetection => ({
isMobile,
isDesktop
});
export default useDevice;
其次,我制作了一个使用自定义钩子的组件
//Device/Device.tsx
import { ReactElement } from 'react';
import useDevice from '@/hooks/useDevice';
export interface DeviceProps {
desktop?: boolean;
mobile?: boolean;
children: ReactElement;
}
export const Device = ({ desktop, mobile, children }: DeviceProps): ReactElement | null => {
const { isMobile } = useDevice();
return (isMobile && mobile) || (!isMobile && desktop) ? children : null;
};
第三,我使用 next.js next/dynamic
//Device/index.tsx
import dynamic from 'next/dynamic';
import type { DeviceProps } from './Device';
export const Device = dynamic<DeviceProps>(() => import('./Device').then((mod) => mod.Device), {
ssr: false
});
最后,我在页面中使用了它。
//pages/my-page.tsx
import { Device } from '@/components/Device';
<Device desktop>
<my-component>Desktop</my-component>
</Device>
<Device mobile>
<my-component>Mobile</my-component>
</Device>