Reactjs 如何使用Next.js在React SSR应用程序上检测设备?
在web应用程序上,我想显示两个不同的菜单,一个用于移动设备,一个用于桌面浏览器。 我使用带有服务器端渲染和库的Next.js应用程序 这是你的电话号码Reactjs 如何使用Next.js在React SSR应用程序上检测设备?,reactjs,next.js,server-side-rendering,device-detection,Reactjs,Next.js,Server Side Rendering,Device Detection,在web应用程序上,我想显示两个不同的菜单,一个用于移动设备,一个用于桌面浏览器。 我使用带有服务器端渲染和库的Next.js应用程序 这是你的电话号码 而不是图书馆,但我真的不喜欢这种方法。有人知道在react代码中直接处理SSR应用程序上的设备类型的良好做法吗?我认为您应该在页面中使用getInitialProps,因为它在服务器和客户端上都运行,并通过首先检测您是否刚刚收到网页请求来获取设备类型(因此您仍然在服务器上),或者如果您正在重新渲染(因此您在客户机上) 现在,您可以使用正则表达式
而不是图书馆,但我真的不喜欢这种方法。有人知道在react代码中直接处理SSR应用程序上的设备类型的良好做法吗?我认为您应该在页面中使用getInitialProps,因为它在服务器和客户端上都运行,并通过首先检测您是否刚刚收到网页请求来获取设备类型(因此您仍然在服务器上),或者如果您正在重新渲染(因此您在客户机上) 现在,您可以使用正则表达式查看设备是移动设备还是桌面设备
// still in getInitialProps
let isMobile = Boolean(userAgent.match(
/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|WPDesktop/i
))
return { isMobile }
现在您可以访问isMobile道具,该道具将返回true或false
const IndexPage = ({ isMobile }) => {
return (
<div>
{isMobile ? (<h1>I am on mobile!</h1>) : (<h1>I am on desktop! </h1>)}
</div>
)
}
请注意,由于
getServerSideProps
和getStaticProps
是互斥的,因此您需要放弃getStaticProps
提供的SSG优势,以便了解用户的设备类型。如果您只需要处理两个styil,我建议不要为此使用getServerSideProps查看详细信息。如果页面结构因设备类型的不同而大不相同,则可能值得一看。最新更新:
因此,如果您不介意在客户端执行此操作,您可以按照下面几个人的建议使用动态导入。这将适用于使用静态页面生成的用例
我创建了一个组件,该组件通过所有react device detect
导出作为道具(明智的做法是只过滤掉所需的导出,因为这样就不会产生树形效果)
//设备/Device.tsx
从“react”导入{ReactNode}
从“反应设备检测”导入*作为rdd
接口设备PROPS{
子项:(道具:rdd的类型)=>ReactNode
}
导出默认功能设备(道具:DeviceProps){
返回{props.children(rdd)}
}
//设备/index.ts
从“下一个/动态”导入动态
const Device=dynamic(()=>import('./Device'),{ssr:false})
导出默认设备
当你想利用这个组件时,你可以
const Example = () => {
return (
<Device>
{({ isMobile }) => {
if (isMobile) return <div>My Mobile View</div>
return <div>My Desktop View</div>
}}
</Device>
)
}
我有一个问题,滚动动画是恼人的移动设备,所以我做了一个基于设备的滚动动画组件
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}
/>
)
}
这导致初始设备为服务器,从而导致错误检测
我提出了回购协议,创建并添加了一个要求您传入用户代理的协议。这可以使用初始道具来完成
更新: 由于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
因为每次调用该钩子时我都需要该包,UA信息会更新,它还会修复SSR不同步警告。使用当前的Next.js(V9.5+)我使用Next/dynamic
和react detect device
完成了这一任务
例如,在我的标题组件上:
...
import dynamic from 'next/dynamic';
...
const MobileMenuHandler = dynamic(() => import('./mobileMenuHandler'), {
ssr: false,
});
return (
...
<MobileMenuHandler
isMobileMenuOpen={isMobileMenuOpen}
setIsMobileMenuOpen={setIsMobileMenuOpen}
/>
)
...
这样,react-detect设备
仅在客户端处于活动状态,并且可以提供正确的读数
请参阅。仅动态加载所需的JS文件
您可以使用next/dynamic动态加载组件,并且只加载适当的组件
您可以使用react-detect设备或是移动设备,在我的例子中,我为移动设备和桌面创建了单独的布局,并根据设备加载相应的组件
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
从“下一个/动态”导入动态;
const mobile=require('is-mobile');
const ShowMobile=dynamic(()=>mobile()?导入('./ShowMobile.mobile'):导入('./ShowMobile'),{ssr:false})
常量测试页=()=>{
返回
}
导出默认测试页
您可以查看。将仅加载所需的component.JS
编辑:
上述内容与条件加载组件有多大区别
isMobile ? <MobileComponent /> : <NonMobileComponent />
isMobile?:
第一个解决方案不会加载JS文件,而在第二个解决方案中,两个JS文件都会加载。因此您可以节省一次往返时间。如果您不介意始终呈现桌面版本并在前端计算逻辑,那么钩子逻辑可以非常简单
export const useDevice=()=>{
const[firstLoad,setFirstLoad]=React.useState(true);
React.useffect(()=>{setFirstLoad(false);},[]);
const ssr=firstLoad | | typeof navigator==“未定义”;
const isAndroid=!ssr&&/android/i.test(navigator.userAgent);
const-isIos=!ssr&&/iPad | iPhone | iPod/.test(navigator.userAgent)&!window.MSStream;
返回{
靛红,
伊西奥,
isDesktop:!isAndroid&&!isIos
};
};
import-React,{useState,useffect}
从“react device detect”导入{isMobile}
...
const[_isMobile,setMobile]=useState();
useffect(()=>{
setMobile(isMobile);
},[setMobile]);
桌面视图
MobileView
我认为atm没有解决方案,因为在服务器端呈现浏览器信息是不可知的,它还与html响应的缓存时间有关。知道了,我去测试一下Dylanbob211,从任何组件获得isMobile
道具的方法是什么?好吧,你的页面组件中有这个道具(index.js或getInitialProp函数所在的任何页面组件),因此您需要传递道具。您可以使用道具钻取技术(从父级传递到子级的分类道具)和状态管理器(redux、mobx等)来传递道具或者使用上下文API。问题是,您只能在页面组件中获得此道具,因为您需要使用getInitialProps请记住,navigator
永远不会在getInitialProps
中定义,getInitialProps
只在服务器端运行,因此您将始终收到req.Header['user-agent']
您仍然可以应用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
...
import dynamic from 'next/dynamic';
...
const MobileMenuHandler = dynamic(() => import('./mobileMenuHandler'), {
ssr: false,
});
return (
...
<MobileMenuHandler
isMobileMenuOpen={isMobileMenuOpen}
setIsMobileMenuOpen={setIsMobileMenuOpen}
/>
)
...
import { isMobile } from 'react-device-detect';
...
return(
{isMobile && !isMobileMenuOpen ? (
<Menu
onClick={() => setIsMobileMenuOpen(true)}
className={classes.menuIcon}
/>
) : null}
)
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
isMobile ? <MobileComponent /> : <NonMobileComponent />
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>