Next.js(下)

Next.js API

目前的页面

  • index和posts/indext都是HTML
  • 但实际开发中我们需要请求/user/shops等API
  • 返回的内容是JSON格式的字符串

使用 Next.js API

  • 路径为/api/v1/posts以便与/posts区分开来
  • 默认导出的函数的类型为 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>没有数据</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三种渲染

客户端渲染

  • 只在浏览器上执行的渲染

静态页面生成(SSG)

  • Static Site Generation,解决白屏问题、SEO问题
  • 无法生成用户相关内容(所有用户请求的结果都一样)

服务端渲染(SSR)

  • 解决白屏问题、SEO问题
  • 可以生成用户相关内容(不同用户结果不同)

注意:SSRSSG都属于预渲染Pre-rendering

客户端渲染

文件列表完全又前端渲染的,我们称之为客户端渲染

客户端渲染的缺点

白屏
在AJAX得到响应之前,页面中之后Loading

SEO 不友好

  • 搜索引擎访问页面,看不到posts数据
  • 因为搜索引擎默认不会执行JS,只能看到HTML
image.png

静态页面生成(SSG)

背景

  • 你有没有想过,其实每个人看到的文章列表都是一样的
  • 那么为什么还需要在每个人的浏览器上渲染一次
  • 为什么不在后端渲染好,然后发给每个人
  • N次渲染变成了1次渲染
  • N次客户端渲染变成了1次静态页面生成
  • 这个过程叫做动态内容静态化

思考

  • 显然,后端最好不要通过AJAX来获取 posts(为什么)
  • 那么,应该如何获取posts呢?

getStaticProps获取posts

声明位置

  • 每个page不是默认导出一个函数么?
  • 把getStaticProps声明在这个函数旁边即可
  • 别忘了加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)=> {...}
  • 默认导出的函数的第一个参数就是props

如何给 props 添加类型

  • const PostsIndex:NextPage<{ posts:Post[] }>=(props)=> {...}
  • 把 function 改成 const + 箭头函数
  • 类型声明为 NextPage
  • 用泛型给 NextPage 传个参数<Props>
  • Props就是props 的类型
同构

静态化的时机

环境

  • 开发环境,每次请求都会运行一次getStaticProps这是为了方便你修改代码重新运行
  • 生产环境getStaticProps只在build时运行一次这样可以提供一份HTML给所有用户下载

如何体验生产环境
关掉yarn dev
yarn build
yarn start

生产环境

解读

  • λ-(Server)SSR 不能自动创建HTML(等会再说)
  • -(Static)自动创建 HTML(发现你没用到props)
  • -(SSG) 自动创建HTM、 JS、 JSON(发现你用到了props)

三种文件类型

  • posts.html含有静态内容,用于用户直接访问
  • posts.js也含有静态内容,用于快速导航(与HTML对应)
  • posts.json含有数据,跟posts.js结合得到界面
    为什么不直接把数据放入posts.js呢?
    显然,是为了让posts.js接受不同的数据(下文解释)
    当然,目前只能接受一个数据(来自getStaticProps)
动态文件静态化

小结

动态内容静态化

  • 如果动态内容与用户无关,那么可以提前静态化
  • 通过getStaticProps可以获取数据
  • 静态内容+数据(本地获取)就得到了完整页面
  • 代替了之前的静态内容+动态内容(AJAX 获取)

时机

  • 静态化是在yarn build的时候实现的

优点

  • 生产环境中直接给出完整页面
  • 首屏不会白屏
  • 搜索引擎能看到页面内容(方便 SEO)

渲染方式:SSR

getServerSideProps

运行时机

  • 无论是开发环境还是生产环境
  • 都是在请求到来之后运行getServerSideProps

回顾一下 getStaticProps

  • 开发环境,每次请求到来后运行,方便开发
  • 生产环境,build时运行一次

参数

  • context,类型为NextPageContext
  • context.req/context.res 可以获取请求和响应一般只需要用到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>
  )
}
  • 展示了当前用户的浏览器
  • 这些信息不可能在请求之前知道
  • 思考:如果我要在页面上展示当前窗口大小,可以吗答案:只能用客户端渲染做到
流程图

总结

静态内容

  • 直接输出HTML,没有术语

动态内容

  • 术语:客户端渲染,通过AJAX请求,渲染成HTML

动态内容静态化

  • 术语:SSG,通过getStaticProps获取用户无关内容

用户相关动态内容静态化

  • 术语:SSR,通过 getServerSideProps获取请求
  • 缺点:无法获取客户端信息,如浏览器窗口大小

还差一个功能

** 点击posts列表查看文章**

<Link href="/posts/[id]" as={`/posts/${p.id}`}>
      <a> {p.title}</a>
</Link>

新建的文件名应该叫做什

  • pages/posts/[id].tsx
  • 你没有看错,文件名就是[id].tsx

/pages/posts/[id].tsx的作用

  • 既声明了路由/posts/:id
  • 又是/posts/:id的页面实现程序

[id].tsx

步骤

  • 实现PostsShow,从props接收post数据
  • 实现getStaticProps,从第一个参数接受params.id
  • 实现 getStaticPaths,返回id列表

优化

  • 使用 marked 得到markdown的HTML内容
    yarn add marked

build

  • 中断 yarn dev
  • yarn build 然后看一下.next/server目录
  • yarn start
    目录

fallback:false的作用

  • 是否自动兜底
  • false 表示如果请求的id不在getStaticPaths的结果里,则直接返回404页面
    true表示自动兜底,id找不到依然渲染页面
  • 注意id不在结果里不代表id不存在,比如大型项目无法讲所有产品页面都静态化,只静态化部分id对应的页面

[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
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容