主要還是想順著開頭篇章繼續補完 React server component,所以接下來就是會碰到 Next 這個框架,因為他與 React18 合作開發出能方便使用 React server component 的 app router,那基礎的 React 如果很熟悉,進到 Next 你通常只會覺得很容易,因為 Next 主要就是改變 React 的渲染機制而產生的框架,當然他也能做後端 api server 的工作,還有很直覺的路由機制...,但我們還是先著重在前端的部分,如果要用一張圖告訴你他能處理哪些好處,那應該會是下面這張。
圖片來源
那還記得前面介紹架構和渲染模式的示意圖嗎?
那麼我們會按照上面所述的三個模式先概略講解一下
渲染模式已經走過了漫長的路程,從伺服器端渲染(SSR)和客戶端渲染(CSR),到今天在不同論壇上討論和評估的高度細緻的模式,這些可能會讓人搞得頭昏眼花,但重要的是要記住每個模式都是為了應對特定的使用案例而設計的,在某一個使用案例有益的模式特性,放到另一個使用案例中可能會有害,在同一個網站上的不同類型的頁面,可能也需要不同的渲染模式。
主要也是受限 google,Chrome 團隊鼓勵開發人員優先考慮靜態或伺服器端渲染,那也就帶表參與 SEO 的標準必須合乎他們的規範。隨著時間的推移,逐步加載和渲染技術可能在使用現代框架時,默認情況下幫助在性能和功能交付之間取得良好的平衡。
那麼Next對於渲染機制的看法又是什麼呢?我們後面的章節會一一講述,首先讓我們先安裝next的專案。
我的宗旨是你聽或念了千百遍,不如自己實際做一遍。
我這裡還是先以 page router為例,主要介紹渲染機制,後面介紹到 React server component 的時候會再做一個範例,這裡請大家跟著官網練習基本上是一樣的,記得選到page router的選項,懶的話你也可以照著我的步驟做
// 基本上都和Next官方的步驟相同
npx create-next-app@latest
// 按下enter後會跑以下訊息
What is your project named? nxt_page // 可以改自己喜歡的命名喔
Would you like to use TypeScript? No / Yes // 我是推薦Y
Would you like to use ESLint? No / Yes // 我是推薦Y
Would you like to use Tailwind CSS? No / Yes // 我是推薦Y
Would you like to use `src/` directory? No / Yes // 選Y的話會都放在src的裡面
Would you like to use App Router? (recommended) No / Yes // 這裡幫我選N
Would you like to customize the default import alias? No / Yes // 這是看你要不要簡化../../為自定義的節點
What import alias would you like configured? @/* // 上面選Y才會問
做完之後到該路徑下可以看到路徑會長這樣
在src/pages
路徑下面會有 _app.tsx
, _document.tsx
, index.tsx
和 api 的資料夾,除了index.tsx
以外我們先不會用到,我們先進 index.tsx
來試試我們熟悉的CSR模式,可以將原本的檔案改成以下:
import { useState } from "react"
export default function Home() {
// 基本上都和你寫react沒有區別
const [count, setCount] = useState<number>(0);
return (
<main className="container mx-auto">
<h2>這裡會是首頁主要內容</h2>
<div className="flex justify-center items-center">
<button
className="p-2 mx-2 rounded-lg shadow-md bg-blue-500 hover:bg-blue-300 disabled:bg-gray-100 min-w-40"
onClick={() => setCount(pre=> pre-=1)}
disabled={count===0} >-</button>
<div>{count}</div>
<button
className="p-2 mx-2 rounded-lg shadow-md bg-blue-500 hover:bg-blue-300 min-w-40"
onClick={() => setCount(pre=> pre+=1)}>+</button>
</div>
</main>
)
}
那你會發現基本使用上都和之前在 React 裡面做的都一樣,這裡不要忘記把src/styles/globals.css
的預設css清空喔!不然直接貼上會跑版,那麼我們嘗試接一些api的操作看看吧!
這裡我就使用 Rick and Morty 的 API 來練習,如果大家想要練習新的框架怎麼運作,除了寶可夢以外,我非常推薦 Rick and Morty API,因為他的網站寫得很清楚,另外還提供 GraphQL 可以練習;
那我們先以角色清單和基本的換頁功能為主。
import { useCallback, useEffect, useMemo, useState } from "react"
interface RickandmortyCharacter {
id: number,
name: string,
status: string,
species: string,
type: string,
gender: string,
origin: {
name: string,
url: string
},
location: {
name: string,
url: string
},
image: string,
episode: string[],
url: string,
created: string,
}
interface RickandmortyCharacterRes {
info: {
count: number,
pages: number,
next: string | null,
prev: string | null,
},
results: RickandmortyCharacter[]
}
interface PageInfo {
pageUrl: string;
next: string | null;
prev: string | null;
curr: number;
loading: boolean;
}
export default function Home() {
const [pageInfo, setPageInfo] = useState<PageInfo>({
pageUrl:'https://rickandmortyapi.com/api/character',
next: null,
prev: null,
loading: true,
curr: 0
});
const [resData, setResData] = useState<RickandmortyCharacterRes|null>(null)
const chkResData = useMemo(() => resData ,[resData])
const pageChange = useCallback((status: string) => {
setPageInfo(pre => ({...pre, loading: true}));
if (status === 'next') {
fetch(pageInfo.next!)
.then((response) => response.json())
.then((response: RickandmortyCharacterRes) => {
const info = response.info
setResData(response)
setPageInfo(pre => ({
...pre,
next: info.next,
pageUrl: pageInfo.next!,
prev: info.prev,
curr: pre.curr + 1,
loading: false
}))
});
}
if (status === 'prev') {
fetch(pageInfo.prev!)
.then((response) => response.json())
.then((response: RickandmortyCharacterRes) => {
const info = response.info
setResData(response)
setPageInfo(pre => ({
...pre,
next: info.next,
pageUrl: pageInfo.prev!,
prev: info.prev,
curr: pre.curr - 1,
loading: false
}))
});
}
}, [pageInfo])
useEffect(() => {
if(!chkResData) {
const controller = new AbortController();
const signal = controller.signal;
setPageInfo(pre => ({...pre, loading: true}));
fetch(pageInfo.pageUrl, {
signal: signal
})
.then((response) => response.json())
.then((response: RickandmortyCharacterRes) => {
const info = response.info
setResData(response)
setPageInfo(pre => ({...pre, next: info.next, prev: info.prev, loading: false}))
});
return () => controller.abort();
}
}, [pageInfo.pageUrl, chkResData])
return (
<main className="container mx-auto">
<h2>這裡會是首頁主要內容</h2>
<div>資料頁面</div>
<div className="flex justify-center items-center">
<button
className="p-2 mx-2 rounded-lg shadow-md bg-blue-500 hover:bg-blue-300 disabled:bg-gray-100 min-w-40"
onClick={() => pageChange('prev')}
disabled={pageInfo.prev===null || pageInfo.loading} >prev</button>
<div>{pageInfo.curr}</div>
<button
className="p-2 mx-2 rounded-lg shadow-md bg-blue-500 hover:bg-blue-300 min-w-40"
onClick={() => pageChange('next')}
disabled={pageInfo.next===null || pageInfo.loading}>next</button>
</div>
{resData?.results?.map((character) => (
<div key={character.id}>
{character.name}
</div>
))}
</main>
)
}
以上就是 Next CSR 的基本操作,也可以當作是無痛移轉的範例,可是我們使用Next的動機,就是想使用其他渲染機制,那麼下一篇我們接著範例延續,接著做其他渲染方式的實踐,以及理解各個模式的概念。