一.手動創建項目
建議看這個中文網站文檔,這個里面的案例配置都是手動的,也可以往下看我這個博客一步步操作
1.在目錄下執行下面命令,初始化package.json文件
npm init -y
2.安裝react相關包以及next包
yarn add next react react-dom
// 或者
npm install --save next react react-dom
3.在package.json文件的script節點,新增以下內容
"scripts": {...,"dev": "next", // 開發時運行"build": "next build", // 打包時運行"start": "next start" // 打完包啟動服務的命令}
注意:Next.js 只支持React 16及以上
4.根目錄下新建pages目錄(這里面就是所有頁面代碼了,會根據這個目錄的內容自動生成路由)
5.在pages里面新建index.js,放入以下內容進行測試
export default function(){return <div>我是pages/index下面的內容</div>
}
6.啟動項目測試
npm run dev
// 或者
yarn run dev
7.訪問控制臺的local地址,顯示出如下頁面
8.打包
npm run build
// 或者
yarn run build
打完包會在項目下新建生成一個.next文件,在根目錄下執行如下命令會和開發時看到的效果一致
npm run start
// 或者
yarn run start
二.快速創建項目
官網文檔
執行下面創建項目的命令
npx create-next-app next-create
下面會出現一堆詢問的配置信息,這里直接走默認就好了
定義路由
這里的頁面都是統一放到app文件夾下面每個文件的page.js文件
例如直接訪問localhost:3000則訪問的是app/page.js文件
例如直接訪問localhost:3000/user則訪問的是app/user/page.js文件
export default function(){// className={"text-red-500"} 使用原子化css標簽return <div className={"text-red-500"}>我是user/page文件</div>
}
如果訪問的頁面有很多網格效果,則去app/globals.css里面把樣式都刪除,只留前三行即可
頁面與布局
將app/Layout.js進行改造
import { Inter } from "next/font/google";
import "./globals.css";const inter = Inter({ subsets: ["latin"] });export const metadata = {title: "Create Next App", // 網站標題description: "Generated by create next app", // 描述信息
};export default function RootLayout({ children }) {return (<html lang="en"><body className={inter.className}>app下面的layout{children}</body></html>);
}
新建app/user/Layout.js存入以下內容
export default function userLayout({ children }) {return (<section>user下面的layout{children}</section>);
}
訪問localhost:3000
訪問localhost:3000/user
通過對比可以發現,app下面的就是公共的根樣式,下面每個Layout.js都會繼承到,然后每個文件夾下都可以定義當前路由頁面的樣式
鏈接和導航
修改下app/page.js內容如下
'use client';
import Link from "next/link";
import { useRouter } from "next/navigation";export default function Home() {const router = useRouter()return (<><h1 className="text-4xl text-orange-600">Hello Name</h1><br></br><Link href={"/user"}>跳轉到user路由</Link><br></br><button onClick={()=>{router.push('/user')}}>點擊跳轉/user</button></>);
}
滾動到新路由的指定位置處,相當于錨點鏈接
<Link href="/dashboard#settings">Settings</Link>// Output
<a href="/dashboard#settings">Settings</a>
路由組
項目下新建三個路徑文件
- app/(marketing)/about/page.js
- app/(marketing)/bolg/page.js
- app/(marketing)/(shop)/acconut/page.js
在每個page.js里面隨便寫點內容,訪問以下路徑
- localhost:3000/about
- localhost:3000/bolg
- localhost:3000/account
可以發現都能被訪問到,總結規律就是文件夾名字帶括號的相當于可以忽略了
路由組不參與url的設定的
個人感覺唯一作用是用于設置共同的Layout.js
創建如下兩個Layout.js文件
- app/(marketing)/Layout.js
- app/(marketing)/(shop)/Layout.js
在這兩個里面添加如下代碼
export default function userLayout({ children }) {return (<section>marketing下面的layout{children}</section>);
}
export default function userLayout({ children }) {return (<section>marketing下面shop的layout{children}</section>);
}
運行后會發現,marking的Layout,js被它里面所有文件所共用,shop里面的Layout.js被shop里面的文件所共用,因為這個案例shop在marking里面的,因此shop里面的文件也共用marking里面的樣式,這就是路由組,按照上面傳統的方式建路由,需要每個文件單獨設置自己的Layout.js,使用路由組可以達到復用性
動態路由
在app/user新建[username]文件夾,里面的page.js文件內容如下
export default function({params}){console.log('params',params);return <><div>我是user/[username]動態路由{params.username}</div></>
}
將app/page.js內容修改如下
'use client';
import Link from "next/link";
import { useRouter } from "next/navigation";export default function Home() {const router = useRouter()return (<><h1 className="text-4xl text-orange-600">Hello Name</h1><br></br><Link href={"/user/王二"}>跳轉到user路由</Link><br></br><button onClick={()=>{router.push('/user/王五')}}>點擊跳轉/user</button></>);
}
當點擊跳轉到路由時,后面的參數就是動態參數,文件名username就是參數名,可以被params.username接收到并顯示到頁面上
上面有個弊端就是只支持一級動態參數,如果希望多級的話可以將[username]文件名換成[…username]這樣就是可以匹配到后面所有參數,如下地址
- localhost:3000/user/1/2/3/4/5
效果圖
但是這里建議將[…username]文件名替換為[[…username]],兩者區別在于[[…username]]當動態參數為空時也會被匹配到,剩余部分兩者功能一致
Loadding加載和流的處理
1.在app下面新建Loading.js組件
export default function(){return <div className={"text-2xl text-pink-400"}>Loading...</div>
}
2.修改app/Layout.js
import { Inter } from "next/font/google";
import { Suspense } from "react";
import Loading from './loading'; // 引入app/loading.js
import "./globals.css";const inter = Inter({ subsets: ["latin"] });export const metadata = {title: "Create Next App", // 網站標題description: "Generated by create next app", // 描述信息
};export default function RootLayout({ children }) {return (<html lang="en"><body className={inter.className}><Suspense fallback={<Loading></Loading>}> // 2.使用SusPense將頁面包裹app下面的layout{children}</Suspense></body></html>);
}
子組件代碼
這里使用了async/await模擬了一下異步,這是個細節,因為上面的loadding效果如果要出來的話,頁面數據必須要是有異步效果,因為我沒注意到這點,費了點時間才搞明白
export default async function Posts() {await new Promise((resolve) => setTimeout(resolve, 2000));return <div>1111</div>;
}
注意:將Lodding放到app/Layout.js里面包裹的話,則針對所有頁面生效,如果某個頁面有不一樣的loading效果的話,則需要在當前文件夾里面的Layout.js去單獨引入對應的Loading.js,可以在當前文件夾里面創建個Loading.js,這樣的話Loading.js的樣式僅僅作用于當前文件夾下的所有頁面
import { Suspense } from "react";
export default function userLayout({ children }) {return (<section>// 這個loading效果僅作用于當前文件夾下面的所有頁面<Suspense fallback={<div className={"text-2xl text-pink-400"}>Loading...</div>}>user下面的layout{children}</Suspense></section>);
}
注意:必須是渲染的頁面內有異步操作(如async/await)才會有Loading.js效果
錯誤處理
新建app/error.js,放入以下內容
'use client'export default function({error,reset}){return (<div><h2>我是全局的錯誤樣式處理</h2><button onClick={()=>reset()}>重試一下</button></div>)
}
也可以對每個頁面單獨定義路由樣式,只需要在目標頁面的文件夾內新建error.js,放入以下內容即可
例如我在app/user/error.js內加入以下內容
'use client'export default function({error,reset}){return (<div><h2>app/user 頁面內有錯誤啦!!!</h2><button onClick={()=>reset()}>重試一下</button></div>)
}
例如我們在目標的user頁面加入一些錯誤信息
export default function Posts() {console.log('a',a); // 這里沒有a變量,因此這里會報錯return <div>1111</div>;
}
當我們在瀏覽器訪問localhost:3000/user就會報出以下錯誤
當我們訪問其他頁面有錯誤信息時,但是沒有給那個頁面單獨定義錯誤樣式,則會觸發全局的錯誤樣式
例如訪問: localhost:3000/about
組件化渲染
并行路線,也就是web端的組件
在app下面新建@home和@setting文件夾,里面都新建一個page.js文件,在里面寫一點頁面
在app/Layout.js里面改為如下頁面代碼
import { Inter } from "next/font/google";
import { Suspense } from "react";
import Loading from './loading';
import "./globals.css";const inter = Inter({ subsets: ["latin"] });export const metadata = {title: "Create Next App", // 網站標題description: "Generated by create next app", // 描述信息
};export default function RootLayout({ children,home,setting }) {return (<html lang="en"><body className={inter.className}><Suspense fallback={<Loading></Loading>}>app下面的layout{home}{children}{setting}</Suspense></body></html>);
}
可以發現組件效果已經出來了
但是上面的僅限于在Layout.js里面使用的組件,下面是可以應用到我們頁面里面的組件的案例
在app平級處創建components/frame/index.js,放入以下內容
import Image from "next/image";export default function({photo}){console.log('photo',photo);return <><Image src={photo.src} alt="" width={600} height={600} className={'w-full object-cover aspect-square col-span-2 w-28'}></Image></>
}
在app里面新建photo/page.js文件,插入以下內容
import Photo from "@/components/frame" // 引入組件
export default function(){const photo = {src:'https://take-saas.oss-cn-hangzhou.aliyuncs.com/wechat_applets/coach/bgcimg/bgc-13.png'}// 給組件傳值return <Photo photo={photo}></Photo>
}
注意:這里可能會報錯圖片問題(網絡圖片需要加一下白名單才能正常加載,如下在next.config.mjs里面進行配置)
/** @type {import('next').NextConfig} */
const nextConfig = {images:{domains:['take-saas.oss-cn-hangzhou.aliyuncs.com'] // 這里是存放域名白名單處}
};export default nextConfig;
然后就可以看到圖片正常加載了
定義404頁面
在app下面新建not-found.js,放入以下內容
export default function(){return <div className={"text-2xl text-pink-400"}>訪問頁面不存在...</div>
}
當頁面訪問一個不存在的頁面路由時,頁面顯示效果如下
PS:由于博客內容都是自學做的整理,在某一次排查問題時,也就是剛好博客寫到這里時,刷到了一個博主的nextjs教程合集,也挺詳細的,力推點擊進入合集地址