最近,我被分配了一个任务,要使用NextJS从零开始构建一个电子商务应用程序。该应用程序设计有两个变体:桌面版和移动版。当我开始移动版本时,我有些沮丧。所有我最喜欢的钩子函数,如useWindowSize
或useIsMobile
由于服务器端环境的限制而无法正常工作!它们依赖于全局的窗口对象,而服务器上根本没有这个对象。因此,无法获得正确的初始窗口大小,而且默认设置为null也不正确。这可能会导致UI中的布局变化。所以我必须找到一种方法,为初始标记设置适当的默认值。
我开始寻找解决方案,我找到了它。我们可以通过用户代理来检测移动设备,而不是通过窗口宽度。在本文中,我们将理解这个简单优雅的解决方案。
getServerSideProps
在每次页面请求时被调用。它接收上下文作为唯一参数,并返回页面所需的数据。这就是定义用户设备的确切位置。
首先,我们需要编写一个核心函数,它将接受服务器端上下文并检测移动设备。由于编写自定义实现相当困难且不必要,我们将使用一个名为mobile-detect
的小型库。所以,让我们来看看代码!
import MobileDetect from "mobile-detect";
import { GetServerSidePropsContext } from "next";
export const getIsSsrMobile = (context: GetServerSidePropsContext) => {
const md = new MobileDetect(context.req.headers["user-agent"] as string);
return Boolean(md.mobile());
};
核心逻辑完成了,但是如何在代码中方便地使用它?我们不想一直传递这些属性,因为这可能会变得混乱。这是合适的时机来使用React Context!
老实说,这没有什么复杂的。我们只需要创建一个包含布尔值的Context即可。
import { createContext } from "react";
export const IsSsrMobileContext = createContext(false);
请不要忘记用它包装应用程序。最适合的位置是_app.tsx
。
import type { AppProps } from "next/app";
import { IsSsrMobileContext } from "@/utils/useIsMobile";
export default function App({
Component,
pageProps
}: AppProps<{ isSsrMobile: boolean }>) {
return (
<IsSsrMobileContext.Provider value={pageProps.isSsrMobile}>
<Component {...pageProps} />
</IsSsrMobileContext.Provider>
);
}
我认为我们应该从钩子的骨架开始,然后在其上添加更复杂的逻辑。
export const useIsMobile = () => {
const isSsrMobile = useContext(IsSsrMobileContext);
return isSsrMobile;
};
目前,useIsMobile
只能在服务器端检测移动设备,但是在浏览器端呢?我们来写另一个钩子,用于客户端。它可以看起来像这样,但是根据你的需要,可以自由地编写适合你的解决方案。
export const useWindowSize = () => {
const [windowSize, setWindowSize] = useState<{
width?: number;
height?: number;
}>({
width: undefined,
height: undefined
});
useEffect(() => {
function handleResize() {
setWindowSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener("resize", handleResize);
// 立即调用处理程序,以便状态与初始窗口大小更新
handleResize();
// 不要忘记在清理时删除事件监听器
return () => window.removeEventListener("resize", handleResize);
}, []);
return windowSize;
};
最后,我们将它们组合在一起...
export const useIsMobile = () => {
const isSsrMobile = useContext(IsSsrMobileContext);
const { width: windowWidth } = useWindowSize();
const isBrowserMobile = !!windowWidth && windowWidth < 992;
return isSsrMobile || isBrowserMobile;
};
import { getIsSsrMobile } from "@/utils/getIsSsrMobile";
import { useIsMobile } from "@/utils/useIsMobile";
import { GetServerSidePropsContext } from "next";
export default function Home() {
const isMobile = useIsMobile();
return isMobile ? <div>移动版</div> : <div>桌面版</div>;
}
export async function getServerSideProps(context: GetServerSidePropsContext) {
return {
props: {
isSsrMobile: getIsSsrMobile(context)
}
};
}
请记住,始终从getServerSideProps
返回isSsrMobile
属性。其余部分非常简单明了。