Next.js(下)

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é)果不同)

注意:SSRSSG都屬于預(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
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容