Reactjs 如何使用Next.js在React SSR应用程序上检测设备?

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,因为它在服务器和客户端上都运行,并通过首先检测您是否刚刚收到网页请求来获取设备类型(因此您仍然在服务器上),或者如果您正在重新渲染(因此您在客户机上) 现在,您可以使用正则表达式

在web应用程序上,我想显示两个不同的菜单,一个用于移动设备,一个用于桌面浏览器。 我使用带有服务器端渲染和库的Next.js应用程序

这是你的电话号码


而不是图书馆,但我真的不喜欢这种方法。有人知道在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>