Next.js 实战 (七):浅谈 Layout 布局的嵌套设计模式

简介: 这篇文章介绍了在Next.js框架下,如何处理中后台管理系统中特殊页面(如登录页)不包裹根布局(RootLayout)的问题。作者指出Next.js的设计理念是通过布局的嵌套来创建复杂的页面结构,这虽然保持了代码的整洁和可维护性,但对于特殊页面来说,却造成了不必要的布局包裹。文章提出了一个解决方案,即通过判断页面的skipGlobalLayout属性来决定是否包含RootLayout,从而实现特殊页面不包裹根布局的目标。

业务场景

在目前常见的中后台管理系统中,比较常见的是固定的布局方式包裹页面,但一些特殊页面,比如:登录页面注册页面忘记密码页面这些页面是不需要布局包裹的。

但在 Next.js AppRouter 中,必须包含一个根布局文件(RootLayout),默认情况下,文件夹层次结构中的布局也是嵌套的,这意味着它们通过其子布局的属性来包装子布局。这是 Next.js 框架的设计理念,目的是允许你创建复杂的页面结构,同时保持代码的整洁和可维护性。

以官网 Nesting layouts 为例,最后 app/blog/[slug]/page.js 生成的结果为:

<RootLayout>
  <BlogLayout>
    {children}
  </BlogLayout>
</RootLayout>

正常页面是这样的:
nzem4zxksnpd7k3n6jpqtmgm4est097j.png

但登录页面如果不处理就会变成这样:
wyek4y2ksv439xe7afhrq6gq5xhnwfbf.png

很明显,这不是我们想要的,我们希望这些特殊页面不需要父级 layout 包裹,那这个问题该怎么去解决呢?

解决方案

我在网上几乎找不到关于 Next.js layout 嵌套布局 的资料,但我觉得这个问题挺有意思的,所以特地写篇文章讨论一下。

这个问题归根结底就是你要不要在 RootLayout 里面写入布局代码,这时候就会分两种情况:

  1. 如果你不嫌麻烦,RootLayout 根布局留空,然后在需要的页面下都新建一个 layout.tsx 文件:

    export default async function RootLayout({
    children,
    }: Readonly<{
          
    children: React.ReactNode;
    }>) {
    return (
     <html lang={locale} suppressHydrationWarning>
       <body>
         {children}
       </body>
     </html>
    );
    }
    

    我看一些 Next.js 教程的源码就是这样布局的,感觉有点麻烦。

  2. 还有一种就是默认 RootLayout 是常规布局,我们需要想个办法在一些特殊页面把 RootLayout 包裹去掉。

我采用的是后者,确定方案后,决定结合 zustand 来定义一个变量用来是否显示根布局。

具体步骤

1、 新建 /store/layoutStore.ts 文件:

import {
    create } from 'zustand';

type LayoutState = {
   
  skipGlobalLayout: boolean;
  setSkipGlobalLayout: (skip: boolean) => void;
};

export const useLayoutStore = create<LayoutState>((set) => ({
   
  skipGlobalLayout: false,
  setSkipGlobalLayout: (skip) => set({
    skipGlobalLayout: skip }),
}));

2、新建 /components/GlobalLayout 文件:

'use client';
import {
    SessionProvider } from 'next-auth/react';

import AppSideBar from '@/components/AppSideBar';
import GlobalFooter from '@/components/GlobalFooter'; // 底部版权
import GlobalHeader from '@/components/GlobalHeader'; // 头部布局
import PageAnimatePresence from '@/components/PageAnimatePresence';
import {
    SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
import {
    useLayoutStore } from '@/store/layoutStore';

type GlobalLayoutProps = {
   
  children: React.ReactNode;
};

export default function GlobalLayout({
    children }: GlobalLayoutProps) {
   
  // 是否跳过全局布局
  const skipGlobalLayout = useLayoutStore((state) => state.skipGlobalLayout);

  return skipGlobalLayout ? (
    <>{
   children}</>
  ) : (
    <SessionProvider>
      <SidebarProvider>
        <AppSideBar />
        <SidebarInset>
          {
   /* 头部布局 */}
          <GlobalHeader />
          <PageAnimatePresence>{
   children}</PageAnimatePresence>
          {
   /* 底部版权 */}
          <GlobalFooter />
        </SidebarInset>
      </SidebarProvider>
    </SessionProvider>
  );
}

3、 修改根目录 layout.tsx 文件:

import GlobalLayout from '@/components/GlobalLayout'; // 全局布局

export default async function RootLayout({
   
  children,
}: Readonly<{
   
  children: React.ReactNode;
}>) {
   
  return (
    <html lang={
   locale} suppressHydrationWarning>
      <body>
        <GlobalLayout>{
   children}</GlobalLayout>
      </body>
    </html>
  );
}

4、 在不需要 RootLayout 的页面 layout.tsx 文件中:

'use client';

import {
    useMount, useUnmount } from 'ahooks';

import LangSwitch from '@/components/LangSwitch';
import ThemeModeButton from '@/components/ThemeModeButton';
import {
    useLayoutStore } from '@/store/layoutStore';

export default function LoginLayout({
    children }: {
    children: React.ReactNode }) {
   
  useMount(() => {
   
    useLayoutStore.setState({
    skipGlobalLayout: true });
  });

  useUnmount(() => {
   
    // 如果需要在离开页面时重置状态
    useLayoutStore.setState({
    skipGlobalLayout: false });
  });
  return (
    <div className="relative flex h-[calc(100vh_-_2rem)] w-[calc(100vw_-_2rem)] overflow-hidden justify-center items-center">
      {
   children}
      <div className="flex absolute top-0 right-0">
        <ThemeModeButton />
        <LangSwitch />
      </div>
    </div>
  );
}

我们根据 skipGlobalLayout 属性来判断是否显示 RootLayout 布局,这样就能达到我们的目的了。

相关文章
|
6天前
|
存储 安全 API
Next.js 实战 (九):使用 next-auth 完成第三方身份登录验证
这篇文章介绍了next-auth,一个为Next.js设计的身份验证库,支持多种认证方式,如电子邮件和密码、OAuth2.0提供商(如Google、GitHub、Facebook等)以及自定义提供商。文章包含了如何配置Github Provider以及会话管理,并提到了适配器Adapters在next-auth中的作用。最后,文章强调了next-auth的强大功能值得进一步探索。
41 10
|
16天前
|
设计模式 缓存 应用服务中间件
「全网最细 + 实战源码案例」设计模式——外观模式
外观模式(Facade Pattern)是一种结构型设计模式,旨在为复杂的子系统提供一个统一且简化的接口。通过封装多个子系统的复杂性,外观模式使外部调用更加简单、易用。例如,在智能家居系统中,外观类可以同时控制空调、灯光和电视的开关,而用户只需发出一个指令即可。
126 69
|
2天前
|
设计模式 存储 关系型数据库
「全网最细 + 实战源码案例」设计模式——六大设计原则
本文介绍了面向对象设计中的六大原则,旨在提高软件系统的可维护性、可复用性和可拓展性。这些原则包括:开闭原则(OCP)、里氏代换原则(LSP)、依赖倒转原则(DIP)、接口隔离原则(ISP)、迪米特法则(LoD)和合成复用原则(CARP)。每项原则通过具体示例展示了如何通过抽象、多态、组合等方式降低耦合度,增强系统的灵活性与稳定性,从而提升开发效率并降低成本。
19 10
|
2天前
|
设计模式 缓存 安全
「全网最细 + 实战源码案例」设计模式——单例设计模式
单例模式是一种创建型设计模式,确保一个类在整个程序运行期间只有一个实例,并提供一个全局访问点来获取该实例。它常用于控制共享资源的访问,如数据库连接、配置管理等。实现方式包括饿汉式(类加载时初始化)、懒汉式(延迟加载)、双重检查锁、静态内部类和枚举单例等。其中,枚举单例最简单且安全,能有效防止反射和序列化破坏。
22 7
|
1天前
|
设计模式 关系型数据库
「全网最细 + 实战源码案例」设计模式——工厂方法模式
简单工厂模式是一种创建型设计模式,通过一个工厂类根据传入参数创建不同类型的产品对象,也称“静态工厂方法”模式。其结构包括工厂类、产品接口和具体产品类。适用于创建对象种类较少且调用者无需关心创建细节的场景。优点是封装性强、代码复用性好;缺点是扩展性差,增加新产品时需修改工厂类代码,违反开闭原则。
27 15
|
1天前
|
设计模式 关系型数据库
「全网最细 + 实战源码案例」设计模式——简单工厂模式
简单工厂模式是一种创建型设计模式,通过工厂类根据传入参数创建不同类型的对象,也称“静态工厂方法”模式。其结构包括工厂类、产品接口和具体产品类。优点是封装性强、代码复用性好;缺点是扩展性差,增加新产品时需修改工厂类代码,违反开闭原则。适用于对象种类较少且调用者无需关心创建细节的场景。
33 19
|
7天前
|
中间件 API
Next.js 实战 (八):使用 Lodash 打包构建产生的“坑”?
这篇文章介绍了作者在使用Nextjs15进行项目开发时遇到的部署问题。在部署过程中,作者遇到了打包构建时的一系列报错,报错内容涉及动态代码评估在Edge运行时不被允许等问题。经过一天的尝试和调整,作者最终删除了lodash-es库,并将radash的部分源码复制到本地,解决了打包报错的问题。文章最后提供了项目的线上预览地址,并欢迎读者留言讨论更好的解决方案。
20 10
|
2天前
|
监控 安全 中间件
Next.js 实战 (十):中间件的魅力,打造更快更安全的应用
这篇文章介绍了什么是Next.js中的中间件以及其应用场景。中间件可以用于处理每个传入请求,比如实现日志记录、身份验证、重定向、CORS配置等功能。文章还提供了一个身份验证中间件的示例代码,以及如何使用限流中间件来限制同一IP地址的请求次数。中间件相当于一个构建模块,能够简化HTTP请求的预处理和后处理,提高代码的可维护性,有助于创建快速、安全和用户友好的Web体验。
|
2月前
|
JavaScript 前端开发
JavaScript中的原型 保姆级文章一文搞懂
本文详细解析了JavaScript中的原型概念,从构造函数、原型对象、`__proto__`属性、`constructor`属性到原型链,层层递进地解释了JavaScript如何通过原型实现继承机制。适合初学者深入理解JS面向对象编程的核心原理。
43 1
JavaScript中的原型 保姆级文章一文搞懂
|
6月前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的客户关系管理系统附带文章源码部署视频讲解等
127 2

热门文章

最新文章