Next.js API
目前的頁面
- index和posts/indext都是HTML
- 但實際開發(fā)中我們需要請求/user/shops等API
- 返回的內(nèi)容是JSON格式的字符串
使用 Next.js API
- 路徑為/api/v1/posts以便與/posts區(qū)分開來
- 默認(rèn)導(dǎo)出的函數(shù)的類型為 NextApiHandler
- 該代碼只運行在Node.js里,不運行在瀏覽器中
pages/api/v1/posts.tsx
import {NextPage} from "next";
import {usePosts} from "../../hooks/usePosts";
const PostIndex: NextPage = ()=> {
const {isLoading, empty, postData} = usePosts()
return (
<div>
<h1>文章列表</h1>
{isLoading ? <div>加載中...</div> :
empty? <div>沒有數(shù)據(jù)</div> : postData.map(item => {
return <div key={item.id}>
{item.id}
</div>
})}
</div>
)
}
export default PostIndex
lib/posts.tsx
import path from "path";
import fs, {promises as fsPromise} from "fs";
import matter from 'gray-matter';
export const getPosts = async ()=> {
const markdownDir = path.join(process.cwd(), 'markdown')
const fileNames = await fsPromise.readdir(markdownDir)
const postData = fileNames.map(fileName=> {
const fullPath = path.join(markdownDir, fileName)
const id = fileName.replace(/\.md$/g, '')
console.log(fullPath);
const text = fs.readFileSync(fullPath,'utf-8');
const {data: {title, date}, content} = matter(text);
return {
title,
date,
id
}
})
return postData
}

Next Api
Next.js三種渲染
客戶端渲染
- 只在瀏覽器上執(zhí)行的渲染
靜態(tài)頁面生成(SSG)
- Static Site Generation,解決白屏問題、SEO問題
- 無法生成用戶相關(guān)內(nèi)容(所有用戶請求的結(jié)果都一樣)
服務(wù)端渲染(SSR)
- 解決白屏問題、SEO問題
- 可以生成用戶相關(guān)內(nèi)容(不同用戶結(jié)果不同)
注意:SSR和SSG都屬于預(yù)渲染Pre-rendering
客戶端渲染
文件列表完全又前端渲染的,我們稱之為客戶端渲染
客戶端渲染的缺點
白屏
在AJAX得到響應(yīng)之前,頁面中之后Loading
SEO 不友好
- 搜索引擎訪問頁面,看不到posts數(shù)據(jù)
- 因為搜索引擎默認(rèn)不會執(zhí)行JS,只能看到HTML

image.png
靜態(tài)頁面生成(SSG)
背景
- 你有沒有想過,其實每個人看到的文章列表都是一樣的
- 那么為什么還需要在每個人的瀏覽器上渲染一次
- 為什么不在后端渲染好,然后發(fā)給每個人
- N次渲染變成了1次渲染
- N次客戶端渲染變成了1次靜態(tài)頁面生成
- 這個過程叫做動態(tài)內(nèi)容靜態(tài)化
思考
- 顯然,后端最好不要通過AJAX來獲取 posts(為什么)
- 那么,應(yīng)該如何獲取posts呢?
getStaticProps獲取posts
聲明位置
- 每個page不是默認(rèn)導(dǎo)出一個函數(shù)么?
- 把getStaticProps聲明在這個函數(shù)旁邊即可
- 別忘了加export
寫法
import {NextPage} from "next";
import {getPosts} from "../../lib/posts";
type Props = {
posts: Post[];
}
const PostIndex: NextPage<Props> = (props)=> {
const {posts} = props
return (
<div>
<h1>文章列表</h1>
{posts.map(p => <div key={p.id}>
{p.id}
</div>)}
</div>
)
}
export default PostIndex
export const getStaticProps = async ()=> {
const posts = await getPosts();
return {
props: {
posts: JSON.parse(JSON.stringify(posts))
}
}
}
getStaticProps
如何使用 props
export default function PostsIndex =(props)=> {...}- 默認(rèn)導(dǎo)出的函數(shù)的第一個參數(shù)就是
props
如何給 props 添加類型
const PostsIndex:NextPage<{ posts:Post[] }>=(props)=> {...}- 把 function 改成 const + 箭頭函數(shù)
- 類型聲明為 NextPage
- 用泛型給 NextPage 傳個參數(shù)<Props>
- Props就是props 的類型

同構(gòu)
靜態(tài)化的時機(jī)
環(huán)境
- 在
開發(fā)環(huán)境,每次請求都會運行一次getStaticProps這是為了方便你修改代碼重新運行 - 在
生產(chǎn)環(huán)境,getStaticProps只在build時運行一次這樣可以提供一份HTML給所有用戶下載
如何體驗生產(chǎn)環(huán)境
關(guān)掉yarn dev
yarn build
yarn start
生產(chǎn)環(huán)境
解讀
-
λ-(Server)SSR 不能自動創(chuàng)建HTML(等會再說) -
?-(Static)自動創(chuàng)建 HTML(發(fā)現(xiàn)你沒用到props) -
●-(SSG) 自動創(chuàng)建HTM、 JS、 JSON(發(fā)現(xiàn)你用到了props)
三種文件類型
- posts.html含有靜態(tài)內(nèi)容,用于用戶直接訪問
- posts.js也含有靜態(tài)內(nèi)容,用于快速導(dǎo)航(與HTML對應(yīng))
- posts.json含有數(shù)據(jù),跟posts.js結(jié)合得到界面
為什么不直接把數(shù)據(jù)放入posts.js呢?
顯然,是為了讓posts.js接受不同的數(shù)據(jù)(下文解釋)
當(dāng)然,目前只能接受一個數(shù)據(jù)(來自getStaticProps)

動態(tài)文件靜態(tài)化
小結(jié)
動態(tài)內(nèi)容靜態(tài)化
- 如果動態(tài)內(nèi)容與用戶無關(guān),那么可以提前靜態(tài)化
- 通過
getStaticProps可以獲取數(shù)據(jù) -
靜態(tài)內(nèi)容+數(shù)據(jù)(本地獲取)就得到了完整頁面 - 代替了之前的
靜態(tài)內(nèi)容+動態(tài)內(nèi)容(AJAX 獲取)
時機(jī)
- 靜態(tài)化是在yarn build的時候?qū)崿F(xiàn)的
優(yōu)點
- 生產(chǎn)環(huán)境中直接給出完整頁面
- 首屏不會白屏
- 搜索引擎能看到頁面內(nèi)容(方便 SEO)
渲染方式:SSR
getServerSideProps
運行時機(jī)
- 無論是開發(fā)環(huán)境還是生產(chǎn)環(huán)境
- 都是在
請求到來之后運行g(shù)etServerSideProps
回顧一下 getStaticProps
- 開發(fā)環(huán)境,每次請求到來后運行,方便開發(fā)
- 生產(chǎn)環(huán)境,
build時運行一次
參數(shù)
- context,類型為NextPageContext
- context.req/context.res 可以獲取請求和響應(yīng)一般只需要用到context.req
const Home: NextPage<Props> = (props) => {
const {browser} = props
const [width, setWidth] = useState(0)
useEffect(()=> {
const w = document.documentElement.clientWidth
setWidth(w)
}, [])
return (
<div>
<p>你的瀏覽器是{browser.name}</p>
<p>你的瀏覽器窗口大小 {width} px</p>
</div>
)
}
- 展示了當(dāng)前用戶的瀏覽器
- 這些信息不可能在請求之前知道
- 思考:如果我要在頁面上展示當(dāng)前窗口大小,可以嗎答案:只能用客戶端渲染做到

流程圖
總結(jié)
靜態(tài)內(nèi)容
- 直接輸出HTML,沒有術(shù)語
動態(tài)內(nèi)容
- 術(shù)語:客戶端渲染,通過AJAX請求,渲染成HTML
動態(tài)內(nèi)容靜態(tài)化
- 術(shù)語:SSG,通過getStaticProps獲取用戶無關(guān)內(nèi)容
用戶相關(guān)動態(tài)內(nèi)容靜態(tài)化
- 術(shù)語:SSR,通過 getServerSideProps獲取請求
- 缺點:無法獲取客戶端信息,如瀏覽器窗口大小
還差一個功能
** 點擊posts列表查看文章**
<Link href="/posts/[id]" as={`/posts/${p.id}`}>
<a> {p.title}</a>
</Link>
新建的文件名應(yīng)該叫做什
pages/posts/[id].tsx- 你沒有看錯,文件名就是
[id].tsx
/pages/posts/[id].tsx的作用
- 既聲明了路由/posts/:id
- 又是/posts/:id的頁面實現(xiàn)程序
[id].tsx
步驟
- 實現(xiàn)
PostsShow,從props接收post數(shù)據(jù) - 實現(xiàn)
getStaticProps,從第一個參數(shù)接受params.id - 實現(xiàn)
getStaticPaths,返回id列表
優(yōu)化
- 使用 marked 得到markdown的HTML內(nèi)容
yarn add marked
build
- 中斷
yarn dev -
yarn build然后看一下.next/server目錄 -
yarn start
目錄
fallback:false的作用
- 是否自動兜底
- false 表示如果請求的id不在getStaticPaths的結(jié)果里,則直接返回404頁面
true表示自動兜底,id找不到依然渲染頁面 - 注意id不在結(jié)果里不代表id不存在,比如大型項目無法講所有產(chǎn)品頁面都靜態(tài)化,只靜態(tài)化部分id對應(yīng)的頁面
[id].tsx
import {NextPage} from "next";
import {Post} from "../../type";
import {getPostById, getPostIds} from "../../lib/posts";
type Props = {
post: Post
}
const PostShow: NextPage<Props>= (props)=> {
const {post} = props
return (
<div>
<h1>{post.title}</h1>
<article dangerouslySetInnerHTML={{__html: post.htmlContent}}/>
</div>
)
}
export default PostShow
export const getStaticPaths = async () => {
const idList = await getPostIds()
return {
paths: idList.map(id => ({params: {id: id}})),
fallback: false
}
}
export const getStaticProps = async (x: any)=> {
const id = x.params.id
const post = await getPostById(id)
return {
props: {
post: post
}
}
}
