项目初始化 手动创建 Next 项目 第一步:创建文件夹 next-demo 第二步:初始化 package.json
第三步;安装 next react
1 npm install next@latest react@latest react-dom@latest
第四步:package.json 添加执行命令
1 2 3 4 5 6 7 8 { "scripts" : { "dev" : "next dev" , "build" : "next build" , "start" : "next start" , "lint" : "next lint" } }
第五步:新建 layout 文件 src/app/layout.js
1 2 3 4 5 6 7 export default function RootLayout ({ children } ) { return ( <html lang ="en" > <body > {children}</body > </html > ); }
第六步:新建 page 文件 src/app/page.js
1 2 3 4 5 6 7 export default function Home ( ) { return ( <main > <h1 > Hello Next</h1 > </main > ); }
然后执行 dev 命令就能启动项目了
通过 create-next-app 脚手架创建项目 1 npx create-next-app@latest
App Router 在 Next 13.4 版本后推荐使用 App Route 在上文通过 create-next-app 脚手架创建项目时,有个选项就是是否使用 App Router
页面
创建如上的目录结构,page.js 可以理解为对应的页面文件,app 下的 page.js 对应的路由就是 / about 文件下的 page.js 对应的路由就是 /about 如果目录下没有 page.js 文件,也就不会生成对应的路由
Layout 布局 Layout 相当于页面的“壳”,每个页面都会在 layout 下,另外还可以给每个页面单独设置一个 layout,例如在 about 目录下新建 layout.js 文件
最后呈现的效果大概是这样,Layout 就是最外层 app/layout.js About Page 是 about/layout.js
根目录的 layout 根目录下的 layout 有一点特殊,首先这个文件是必需的,另外这个文件必须有 html 和 body 标签,而其他的 layout 不能有
模板 template 模板和 layout 的区别在于 template 没有办法保持状态。
这里 detail 和 detail2 只是用来进行跳转用,重点看一下 layout 和 template
layout.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 'use client' import {useState} from "react" ;import Link from "next/link" ;export default function ProductLayout ({children} ) { const [state, setState] = useState (0 ) return ( <> Layout: {state} <br /> <button onClick ={() => setState(state+1)}>add</button > {children} <br /> <Link href ="/product/detail" > TO DETAIL</Link > <br /> <Link href ="/product/detail2" > TO DETAIL2</Link > </> ) }
template.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 'use client' import {useState} from "react" ;export default function ProductLayout ({children} ) { const [state, setState] = useState (0 ) return ( <> Template: {state} <br /> <button onClick ={() => setState(state+1)}>add</button > {children} </> ) }
在 layout 和 template 中分别加入状态,当我们切换路由时看状态变化
加载 loading 在页面同级下创建 loading.js,以上例子是 about/loading.js
1 2 3 export default function DashboardLoading ( ) { return <> Loading...</> }
然后在 about/page.js 中
1 2 3 4 5 6 7 8 9 10 export default async function About ( ) { async function getData ( ) { await new Promise ((resolve ) => setTimeout (resolve, 3000 )) return { data : 'About Page' , } } const { data } = await getData (); return <h1 > {data}</h1 > }
这时再进入到 about 页面就会先出现 Loading… 等到 page.js 内的请求完毕 loading 关闭,所以 page 必须是一个异步函数,或者使用 React 的 use 函数才能触发 loading
1 2 3 4 5 6 7 8 9 10 export default function About ( ) { async function getData ( ) { await new Promise ((resolve ) => setTimeout (resolve, 3000 )) return { data : 'About Page' , } } const { data } = React .use (getData ()); return <h1 > {data}</h1 > }
loading 会给 page 包一层 <Suspense> 用来捕获 page 加载的 promise,实现 loading 效果。
错误 error 通过 error 可以在页面发生错误时,展示 error
还是页面的同级目录创建 error.js,另外需要注意的是 error 组件必须是客户端组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 'use client' import { useEffect } from 'react' export default function Error ({ error, reset } ) { useEffect (() => { console .error (error) }, [error]) return ( <div > <h2 > Something went wrong!</h2 > <button onClick ={ // 尝试恢复 () => reset() } > Try again </button > </div > ) }
error 会给 page 包一层 <ErrorBoundary>
全局错误处理 global-error 404页面 not-found next 存在默认的 404 页面
如果想要自定义 404 页面,可以在 app 目录下新增一个 not-found.js 作为 404 页面。
跳转 404 next 有两种方式可以触发 not-found,第一种是执行 notFound 函数,第二种是路由找不到的情况。
1 2 3 4 5 6 7 import { notFound } from 'next/navigation' export default async function Index ( ) { notFound () }
层级关系
层级结构:
1 2 3 4 5 6 7 8 9 10 11 <Layout > <Tempolate > <ErrorBoundary fallback ={{ <Error /> }}> <Suspense fallback ={ <Loading /> }> <ErrorBoundary fallback ={ <NotFound /> }> <Page /> </ErrorBoundary > </Suspense > </ErrorBoundary > </Tempolate > </Layout >
Next 中的懒加载 React 提供了 lazy + Suspense 实现了懒加载。在 Next 中额外提供了 next/dynamic 方式实现懒加载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 'use client' import { useState } from 'react' import dynamic from 'next/dynamic' const ComponentA = dynamic (() => import ('../components/A' ))const ComponentB = dynamic (() => import ('../components/B' ))const ComponentC = dynamic (() => import ('../components/C' ), { ssr : false }) export default function ClientComponentExample ( ) { const [showMore, setShowMore] = useState (false ) return ( <div > {/* Load immediately, but in a separate client bundle */} <ComponentA /> {/* Load on demand, only when/if the condition is met */} {showMore && <ComponentB /> } <button onClick ={() => setShowMore(!showMore)}>Toggle</button > {/* Load only on the client side */} <ComponentC /> </div > ) }
使用时需要注意的是
import 中不能是模板字符串和变量
dynamic 要在模块顶层
客户端组件默认会被预渲染(SSR),通过 ssr: false 可以关闭预渲染,这会让组件比正常预渲染的组件晚显示,实际上会有一个 template 给该组件占位,然后等待加载完成再渲染。
关于导入导出 1 2 3 export function Hello ( ) { return <p > Hello World</p > }
在使用 Next 的懒加载时,如果需要导入以上这种非默认导出需要:
1 2 3 4 5 import dynamic from 'next/dynamic' const ClientComponent = dynamic (() => import ('../components/hello' ).then ((mod ) => mod.Hello ) )