美文网首页
Next 全解

Next 全解

作者: littleyu | 来源:发表于2020-07-19 14:19 被阅读0次

    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 列表

    相关文章

      网友评论

          本文标题:Next 全解

          本文链接:https://www.haomeiwen.com/subject/mysfkktx.html