Next 全解

Next.js 的背景

开发团队是 zeit

  • zeit 团队水平如何。后改名为 Vercel
  • 简言之,一个高中开始编程的、会做平面设计的复旦大学计算机专业毕业生,在微软工作一年后,加入了 zeit 团队
  • 几乎每一个同事都有非常强大的背景
  • Next,js 核心团队四个人平均年龄 20岁
  • 按 star 数,zeit 是 GitHub 组织的 Top 20
  • 全员远程工作

Next.js 的定位

Node.js 全栈框架

  • CSS-inJS
  • 页面预渲染 + SSR(服务端渲染)
  • 前后端同构(代码同时运行在两端)
  • Node.js 10.13 以上
  • 支持 React,与 React 无缝对接
  • 支持 TypeScript

弱项

  • 完全没有提供数据库相关功能,可行搭配 Sequelize 或 TypeORM
  • 完全没有提供测试相关功能,可自行搭配 Jest 或 Cypress
  • 有一个叫做 Blitz.js 的框架在这些方向上努力

Link 快速导航

用法

  • 把 <a href=xxx> 点击链接 </a> 改成
  • <Link href=xxx><a> 点击链接 </a></Link>

优点

  • 页面不会刷新,用 AJAX 请求新页面内容
  • 不会请求重复的HTML、CSS、JS
  • 自动在页面插入新内容、删除旧内容
  • 因为省了很多请求和解析过程,所以速度极快

吐槽

  • 借鉴了 Rails Turbolinks、pjax 等技术
传统导航,访问 page2 时是浏览器请求
相比传统导航,访问 page2 时是 page1 用AJAX请求页面(在network中可见)

同构代码

一份代码运行在两端(省了一份)

  • 在组件里写一句 console.log('执行了')
  • 你会发现 Node 控制台会输出这句话
  • 同样你会发现 Chrome 控制台也会输出这句话

注意差异

  • 不是所有代码都会运行,有些需要用户触发
  • 不是所有的 API 都能用,比如 window 在 Node 里报错

全局配置

pages/_app.js

  • export default function App 是每个页面的根组件
  • 页面切换时 App 不会销毁,App 里面的组件会销毁
  • 可用 App 保存全局状态

注意

  • 创建 _app.js 之后需要重启服务(yarn dev)

全局 CSS

放在 _app.js 里

  • import '../styles/global.css'
  • 因为切换页面时 App 不会销毁
  • 其他地方不能 import global.css
  • 其他地方只能写局部 CSS

插曲

  • 相对引用好烦,能改成 import 'style/global.css'吗?
  • 文档 Absolute Import 章节(baseUrl: '.')

局部 CSS

官方支持

  • 默认支持 styled-jsx 和 CSS Modules
  • 一般来说,简单需求用前者,复杂需求用后者

React 个人体验

  • styled-jsx 不方便分离 CSS 和 JS
  • CSS Modules 用起来太麻烦
  • styled-components 用起来顺手

静态资源

next 推荐放在 public 里

  • 个人觉得不太好
  • 因为放在 public 里不支持改文件名(如哈希)

所以需要配置自定义 webpack config

  • 创建 next.config.js
  • 栗子🌰:使用 file-loader 或者 next-images
// file-loader 
module.exports = {
  webpack: (config, options) => {
    config.module.rules.push({
      test: /\.(jpg|png|gif|svg)$/,
      use: [
        {
          loader: 'file-loader',
          options: {
            name: '[name].[contenthash].[ext]', // 文件名称
            outputPath: 'static', // 硬盘路径
            publicPath: '_next/static', // 网站路径
          }
        },
      ],
    })

    return config
  },
}
// next-images
const withImages = require('next-images')
module.exports = withImages({
  webpack(config, options) {
    return config
  }
})

启用 Typescript

创建 tsconfig.json

  • tsc --init 运行后得到 tsconfig.json 或者 touch tsconfig.ts
  • 将 jsconfig.json 里面的配置合并到 tsconfig.json
  • 删除 jsconfig.json

重启服务 yarn dev

  • 会自动改写 tsconfig.json
  • 更改文件名后缀由 .js 改为 .tsx
  • 不需要一次性将所有文件全部改完
  • 在 tsconfig.json 里添加
  • "noImlicitAny": true (禁用隐式的 any)

Next.js API

目前的页面

  • index 和 posts 都是 HTML
  • 但实际开发中我们需要请求 /user /shops 等 API
  • 但返回的内容是 JSON 格式的字符串
  • 使用 Next.js API
  • 路径为 /api/v1/posts 以便与 /posts 区分开来
  • 默认导出的函数的类型为 NextApiHandler
  • 该代码只运行在 Node.js 里,不运行在浏览器中
  • 栗子🌰:
const posts: NextApiHandler = async (req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'application/json');
  const posts = await getPosts() // 数据库操作
 res.end(JSON.stringify(posts))
}

export default posts

API

/api/ 里的文件是 API

  • 一般返回 JSON 格式的字符串
  • 但也不是不能返回 HTML, 比如 res.end('<html'></html'>)

API 文件默认导出 NextAPIHandler

  • 这是一个函数类型
  • 第一个参数是请求
  • 第二个参数是对象
  • 因为 Next.js 是基于 Express 的,所以支持 Express 的中间件,下文在分析,官方文档

Next.js 三种渲染方式

客户端渲染

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

静态页面生成(SSG)

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

服务端渲染(SSR)

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

注意:SSR 和 SSG 都属于预渲染 Pre-rendering

旧瓶装新酒

三种渲染方式分别对应

  • 客户端渲染 -- 用 JS、Vue、React 创建 HTML
  • SSG -- 页面静态化,把 PHP 提前渲染成HTML
  • SSR -- PHP、Python、Ruby、Java 后台的基本功能

与传统的后端不同点

  • Next.js 的预渲染可以与前端 React 无缝对接

客户端渲染的缺点

白屏

  • 在 AJAX 得到相应之前,页面中 Loading

SEO 不友好

  • 搜索引擎访问页面,看不到 AJAX 得到的数据
  • 因为搜索引擎默认不会执行 JS,只能看到 HTML

静态内容 VS 动态内容

上图的静态内容

  • 是服务渲染的,还是客户端渲染的?
  • 渲染了几次?一次还是两次?

参考 React SSR 的官方文档

  • 推荐在后端 renderToString() 在前端 hydrate()
  • hydrate() 混合,会保留 HTML 并附上事件监听
  • 也就是说后端渲染 HTML,前端添加监听
  • 前端也会渲染一次,用以确保前后端渲染结果一致(如何看出渲染了两次,当使用 styled-conponents 时会有报错)

推论

  • 所有页面至少有一个标签是静态内容,由服务端渲染

静态页面生成(SSG)

背景

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

getStaticProps 获取 posts

声明位置

  • 每个 page 不是默认导出一个函数么?
  • 把 getStaticProps 声明在这个函数旁边即可
  • 栗子🌰:
export const getStaticProps: GetStaticProps = async () => {
  const posts = await getPosts()
  return {
    props: {
      posts,
    }
  }
}

必须按照这个格式,不能变(命名和返回值{ props: {...} })

打开控制台我们可以清楚的看见,原来我们需要通过 AJAX 的内容,直接被打包进 HTML 里面了,这样浏览器不需要用 AJAX 就可以直接拿到数据了!

这就是同构 SSR 的好处:后端数据可以传给前端

前端 JSON.parse 一下就能够得到了 posts(现在 Next.js 帮你做了)

难道 PHP / Java / Python 就做不到么

  • 其实也可以做到,思路一样
  • 但是他们不支持 JSX,很难与 Reactr 无缝对接
  • 而且他们的对象不能直接提供 JS 用(他们又有 int 之类的类型),需要类型转换

静态化的时机

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

解读打包文件

  • λ (Server) server-side renders at runtime (uses getInitialProps or getServerSideProps)
  • ○ (Static) automatically rendered as static HTML (uses no initial props)
  • ● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)

动态内容静态化

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

优点

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

getServerSideProps

用户相关动态内容

就难提前静态化

  • 需要在用户请求时,获取用户信息,然后通过用户信息去数据库拿数据
  • 如果因要做,就要给每个用户都提前创建一个页面(占内存,麻烦)
  • 但还有时候这些数据更新极快,无法提前静态化
  • 比如微博首页的信息流

所以

  • 要么客户端渲染,下拉更新(1)
  • 要么服务端渲染,下拉更新(2)
  • 但这次的服务端渲染不能用 getStaticProps
  • 因为 getStaticProps 是在 build 时执行的
  • 所以要用 getServerSideProps

运行时机

  • 无论是开发环境还是生产环境
  • 都是在请求到来之后运行 getServerSideProps
  • 与 getStaticProps 区别,build 时运行一次

参数

  • context,类型为 NextPageContext
  • context.req / context.res 可以获取请求和响应
  • 一般只需要用到 context.req
  • 栗子🌰:
export const getServerSideProps: GetServerSideProps = async (context) => {
  const ua = context.req.headers["user-agent"]
  return {
    props: {
      ua,
    }
  }
}

必须按照这个格式,不能变(命名和返回值{ props: {...} })

这个栗子展示了用户访问的浏览器,这些信息我们不可能提前(在用户请求之前)知道

总结

静态内容

  • 直接输出 HTML,没有术语

动态内容

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

动态内容静态化

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

用户相关动态内容静态化

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

流程图

有动态内容吗?没有什么都不用做,自动渲染为 HTML
动态内容跟客户端相关?相关就只能用客户端渲染(BSR)
动态内容跟请求/用户相关吗?相关就只能用服务端渲染(SSR)或 BSR
其他情况可以用 SSG 或 BSR

补充:路由的另一个功能

点击列表查看详情功能

  • 简单,不就是加个 Link>a 标签吗
  • href={/post/${id}}
<Link href="/posts/[id]" as={`/posts/${post.id}`}>
    <a>{post.title}</a>
</Link>

但是新建的文件叫做什么

  • pages/posts/[id].tsx
  • 没错,文件名就是 [id].tsx,(约定)

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

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

实现

  • 用 getServerSideProps 渲染列表页面
  • 详情页用 getStaticProps,从第一个参数接受 params.id
  • 用 getStaticPaths 返回 id 列表
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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