最新Next 14快速上手

619 阅读8分钟

Next.js是一个用于构建全栈Web应用程序的React框架。你可以使用React组件来构建用户界面,使用Next.js来实现额外的功能和优化。 ​ 在引擎盖下,Next.js还抽象并自动配置React所需的工具,如捆绑,编译等。这使您可以专注于构建应用程序,而不是花费时间进行配置。

主要特点 路由,渲染,数据获取,样式,优化项,TypeScript支持。

安装

Node.js 18.17或者以后
# https://nodejs.org/en

# 使用create-next-app启动一个新的Next.js应用程序
sudo npx create-next-app@latest

What is your project named? next-project
Would you like to use TypeScript? No / Yes
Would you like to use ESLint? No / Yes
Would you like to use Tailwind CSS? No / Yes
Would you like to use `src/` directory? No / Yes
Would you like to use App Router? (recommended) No / Yes
Would you like to customize the default import alias (@/*)? No / Yes
What import alias would you like configured? @/*

项目结构

// app 目录就是我们选择的App Router,在app文件夹创建文件夹及相关文件将对应相应的路由
// public 服务的静态资产
// components文件夹,用于放置自定义的组件
// styles文件夹,用于放置样式文件,当前使用的是CSS in JS方式
// lib文件夹,用于放置自定义的方法工具等

构建应用程序

// 文件名(后缀.js .jsx .tsx)	描述

layout	路由及其子路由的共享UI
page	路由的唯一UI并使路由可公开访问
loading	路由加载及其子路由加载的UI
not-found	找不到路由及其子路由的UI
error	Error UI for a segment and its children段及其子段的错误UI
global-error	全局错误UI,在app(根)目录下
route	服务器端API端点
template	专门的重新渲染布局UI
default	并行路由的回退UI

定义路由

Next.js使用基于文件系统的路由器,其中文件夹用于定义路由。

//如 /dashboard/settings 映射到 app/dashboard/settings/page.tsx

//page.tsx
export default function Page() {
  return <h1>Hello, Next.js!</h1>
}

页面和布局

Next.js 13中的App Router引入了新的文件约定,可以轻松创建页面、共享布局和模板。本篇将指导您如何在Next.js应用程序中使用这些特殊文件。

//app/page.js
export default function Page() {
  return <h1>Hello, Home page!</h1>
}

//app/dashboard/page.tsx
export default function Page() {
  return <h1>Hello, Dashboard Page!</h1>
}

//app/layout.js 必须的
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        {/* Layout UI */}
        <main>{children}</main>
      </body>
    </html>
  )
}

//app/dashboard/layout.tsx
export default function DashboardLayout({
  children, // will be a page or nested layout
}: {
  children: React.ReactNode
}) {
  return (
    <section>
      <Links linkList={['dashboard', 'dashboard/settings']} />
      <nav>DashboardLayout</nav>
      {children}
    </section>
  )
}

//根布局(app/layout.js)将包裹(app/dashboard/layout.js),这将包裹app/dashboard/*中的界面。

修改

元数据即html文件中head标签下的内容,可以在layout.tsx或page.tsx中导出metadata对象或generateMetadata函数来定义,如src/app/page.tsx:

import { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'Next.js',
}
 
export default function Page() {
  return '...'
}

链接和导航

在Next.js中,有四种方法可以在路由之间导航:,useRouter,redirect function,window.history.pushState

//app/page.tsx
import Link from 'next/link'
 
export default function Page() {
  return <Link href="/dashboard">Dashboard</Link>
}

// 有状态的链接
'use client'
 
import { usePathname } from 'next/navigation'
import Link from 'next/link'
 
export function Links() {
  const pathname = usePathname()
 
  return (
    <nav>
      <ul>
        <li>
          <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
            Home
          </Link>
        </li>
        <li>
          <Link
            className={`link ${pathname === '/about' ? 'active' : ''}`}
            href="/about"
          >
            About
          </Link>
        </li>
      </ul>
    </nav>
  )
}

//layout 引入
<Links></Links>



// useRouter 只能用在客户端
'use client'
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false })

//redirect function
import { redirect } from 'next/navigation'
 
async function fetchTeam(id: string) {
  const res = await fetch('https://...')
  if (!res.ok) return undefined
  return res.json()
}
 
export default async function Profile({ params }: { params: { id: string } }) {
  const team = await fetchTeam(params.id)
  if (!team) {
    redirect('/login')
  }
 
  // ...
}

路由分组

在 app 目录中,嵌套文件夹通常映射到 URL 路径。但是,您可以将文件夹标记为路由组,以防止该文件夹包含在路由的 URL 路径中。这允许您将路由段和项目文件组织到逻辑组中,而不会影响 URL 路径结构。

//即使路由内部 (marketing) 和 (shop) 共享相同的 URL 层次结构,您也可以通过在文件夹内添加 layout.js 文件来为每个组创建不同的布局。

// 创建下列测试文件
(marketing)/about/page.js
(marketing)/blog/page.js
(shop)/account/page.js

动态路由

如果您提前不知道确切的路由名称,并且想要根据动态数据创建路由,则可以使用在请求时填充或在构建时预呈现的动态路由。 可以通过将文件夹的名称括在方括号中来创建动态路由: [folderName] 。例如, [id] 或 [slug] 。动态路由作为 params prop传递给 、 layout route 、 page 和 generateMetadata 函数。

//src/app/blogs/[id]/page.tsx
export default function Page({ params }: { params: { id: string } }) {
  return <div>My Post: {params.id}</div>
}

// app/blogs/[id]/page.js	/blogs/a	 params={ id: 'a' }

加载UI

特殊文件 loading.js 可帮助您使用 React Suspense 创建有意义的加载 UI。使用此约定,您可以在加载路由段的内容时显示来自服务器的即时加载状态。渲染完成后,新内容将自动交换。

//在src/app/dashboard下新建文件loading.tsx
export default function Loading() {
  return <>加载中...</>
}

// 在同一个文件夹中, loading.js 将嵌套在 layout.js .它会自动将 page.js 文件和下面的任何子项包装在 <Suspense> 边界中。

错误处理

error.js文件约定允许您优雅地处理嵌套路由中的意外运行时错误。

//app/dashboard/error.tsx
'use client' // Error components must be Client Components
 
import { useEffect } from 'react'
 
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    // Log the error to an error reporting service
    console.error(error)
  }, [error])
 
  return (
    <div>
      <h2>Something went wrong!</h2>
      <button
        onClick={
          // Attempt to recover by trying to re-render the segment
          () => reset()
        }
      >
        Try again
      </button>
    </div>
  )
}

并行路由

并行路由允许您在同一布局中同时或有条件地呈现一个或多个页面。对于应用的高度动态部分(例如社交网站上的仪表板和源),并行路由可用于实现复杂的路由模式。

// 定义了两个显式插槽: app/parallel/@analytics/page.js 和 app/parallel/@team/page.js 。

// 上面的文件夹结构意味着 app/parallel/layout.js 组件中现在接受 @analytics 和 @team 插槽 props,并且可以将它们与 children 并行渲染:

//app/parallel/layout.tsx

export default function RootLayout({
  children,
  team,
  analytics
}: {
  children: React.ReactNode
  team: React.ReactNode
  analytics: React.ReactNode
}) {
  return (
        <>
          {children}
          {team}
          {analytics}
        </>
  )
}

拦截路由

拦截路由允许您从当前布局中应用程序的另一部分加载路由。当您希望在用户不切换到其他上下文的情况下显示路由的内容时,此路由范式非常有用。

//单击源中的照片时,可以以Modal模态框显示照片,覆盖源。在这种情况下,Next.js 会截获 /photo/123 路由,屏蔽 URL,并将其覆盖 /feed 在 上。

//(.)photos/[id]/page.tsx
import Frame from '../../../../components/frame/Frame'
import Modal from '../../../../components/modal/Modal'
import swagPhotos, { Photo } from '../../../../lib/photos'

export default function PhotoModal({ params: { id: photoId } }: { params: { id: string } }) {
  const photos = swagPhotos
  const photo: Photo = photos.find(p => p.id === photoId)!

  return (
    <Modal>
      <Frame photo={photo} />
    </Modal>
  )
}

//具体代码参考源码

API 路由匹配

next.js 首页标榜的 12 个特性之一就是 API routes,简单的说就是可以 next.js 直接写 node 代码作为后端服务来运行。因此我们可以直接使用 next.js 直接维护一个全栈项目,听起来很香的样子。而 API 的文件命名有三种方式:

  1. pages/api/route.js
  2. pages/api/route/[param].js
  3. pages/api/route/[...slug].js
//pages/api/route.js
import { NextApiRequest, NextApiResponse } from "next";

export function GET(
  req: NextApiRequest,
) {
  return Response.json({ message: 'Hello from Next.js! /api/mapi' })
}

//pages/api/route/[param].js
export async function GET(
  request: Request,
  { params }: { params: { postSlug: string } }
) {
  const { postSlug } = params;
  
  return Response.json({ message: 'Hello from Next.js! ' + postSlug })
}

//pages/api/route/[...slug].js
export async function GET(
  request: Request,
  { params }: { params: { slug: string } }
) {
  const { slug } = params;
  
  return Response.json({ message: 'Hello from Next.js! ' + slug })
}

// res 中扩展了以下几个常用的方法:
// res.status(code) 响应的 http 状态码
// res.json(body) json 响应体
// res.send(body) 其它响应体,可以是 string、object、Buffer
// res.redirect([status,] path) 重定向
// res.revalidate(urlPath) 重新进行校验

//req 中则扩展了以下几个常用属性:
// req.cookies 请求包含的 cookies
// req.query 请求的 query 参数
// req.body 请求体

CSR(客户端渲染)

"use client";

import react, { useEffect, useState } from "react";

type Product = {
  id: string;
  title: string;
};

const CsrPage = () => {
  const [products, setProducts] = useState<Product[]>([]);

  const fetchData = async () => {
    // const res = await fetch("https://dummyjson.com/products");
    // const data = await res.json();

    setProducts([{id:'1234',title:"aaa"},{id:'1235',title:"bbb"}]);
  };

  useEffect(() => {
    fetchData();
  }, []);

  return (
    <>
      <h3>Built with CSR</h3>
      <br />
      <ul>
        {products.map((product) => (
          <li key={product.id}>
            {product.id}: {product.title}
          </li>
        ))}
      </ul>
    </>
  );
};

export default CsrPage;

SSR(服务器端渲染)

当接收到来自客户端的请求时,服务器在服务器端执行 JavaScript 以生成(呈现)要显示的 HTML。然后生成的 HTML 返回给客户端,客户端在其浏览器中按原样显示该 HTML。 与 CSR 的主要区别在于浏览器端的工作量。在CSR中,客户端的工作是“运行JavaScript并生成要显示的HTML”,但在SSR中,生成HTML的工作在服务器端,因此浏览器只显示返回的HTML。

  1. 将fetch()的第二个参数设置为{ cache: “no-store” }
  2. 没有使用useEffect、useState、onClick等依赖于浏览器的功能
import React from "react";

type Product = {
  id: string;
  title: string;
};

const SsrPage = async () => {
  const res = await fetch("https://dummyjson.com/products", {
    cache: "no-store",
  });
  const data = await res.json();

  return (
    <>
      <h3>Built with SSR</h3>
      <br />
      <ul>
        {data.products.map((product: Product) => (
          <li key={product.id}>
            {product.id}: {product.title}
          </li>
        ))}
      </ul>
    </>
  );
};

export default SsrPage;

SSG(静态站点生成)

在应用程序构建时生成页面并作为静态页面储存在服务器中。当有请求时,服务器返回该静态文件。

  1. 在fetch()的第二个参数中,我们设置了{ cache: “force-cache” }(因为这是默认选项,所以可以省略)。
  2. 未使用useEffect、useState、onClick等依赖于浏览器的功能。
import React from "react";

type Product = {
  id: string;
  title: string;
};

const SsgPage = async () => {
  const res = await fetch("https://dummyjson.com/products",{
    cache: "force-cache",
  });
  const data = await res.json();

  return (
    <>
      <h3>Built with SSG</h3>
      <br />
      <ul>
        {data.products.map((product: Product) => (
          <li key={product.id}>
            {product.id}: {product.title}
          </li>
        ))}
      </ul>
    </>
  );
};

export default SsgPage;

ISR(增量静态再生)

这是一种结合了SSR和SSG的渲染方法。应用程序构建时会生成页面,作为静态页面存储在服务器上,发出请求时返回静态文件。到目前为止,行为与 SSG 类似,但 ISR 允许你在一段时间后再次运行构建。它是为了解决 SSG 的弱点而创建的,SSG 偶尔更新时需要手动重建。

  1. 在fetch()的第二个参数中,设置了{ next: { revalidate: 10 } }
  2. 未使用useEffect、useState、onClick等依赖于浏览器的功能。
import React from "react";

type Product = {
  id: string;
  title: string;
};

const IsrPage = async () => {
  const res = await fetch("https://dummyjson.com/products", {
    next: {
      revalidate: 30,
    },
  });
  const data = await res.json();

  return (
    <>
      <h3>Built with ISR</h3>
      <br />
      <ul>
        {data.products.map((product: Product) => (
          <li key={product.id}>
            {product.id}: {product.title}
          </li>
        ))}
      </ul>
    </>
  );
};

export default IsrPage;

构建

我们可以通过构建来检查 SSR、SSG 和 ISR 的行为,使用以下命令执行构建:

npm run build

// { cache: “force-cache” }或者没有指定任何内容👉 SSG
// { cache: “no-store” } 指定👉SSR
// { next: { revalidate: 10 } }指定👉 ISR

源码下载:https://gitee.com/leolee18/next-project