next.js 获取数据的方式

32 min read

Nextjs 获取数据的方式

  • 四个 api 都只能在 pages 文件夹内的文件中使用

getStaticProps

当页面内容取决于外部数据

Next.js 推荐我们尽可能使用静态生成的方式,因为所有页面都可以只构建一次并托管到 CDN 上,这比让服务器根据每个页面请求来渲染页面快得多。

比如下列一些类型的页面:

  • 营销页面
  • 个人博客
  • 产品列表
  • 静态文档

如果一个页面使用了 静态生成(SSG),在构建时将生成此页面对应的 HTML 文件 。HTML 文件将在每个页面请求时被重用,还可以被 CDN 缓存。这个跟 hexo,Gatsby 类似的,生成静态文件还有 json 文件。

Next.js 会尽可能地自动优化应用并输出静态 HTML,如果你在你的页面组件添加了getInitialProps方法才会禁用自动静态优化,getInitialProps方法下面会说到。

比如我们可以执行命令查看

npm run build

打包默认是 SSG 模式:

● (SSG) automatically generated as static HTML + JSON (uses getStaticProps)

会发现.next/server/pages/下多出了几个 HTML 文件,这些就是静态构建出的 HTML 文件。

新建文件pages/blog.js

const Blog = ({ posts }) => {
  return <div>title: {posts.title}</div>
}

// 此函数在构建时被调用
export async function getStaticProps() {
  // 调用外部 API 获取内容
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1')
  const posts = await res.json()

  // 在构建时将接收到 `posts` 参数
  return {
    props: {
      posts,
    },
  }
}

export default Blog

进入http://localhost:3000/blog,发现页面能正常获取到请求结果:

title: delectus aut autem

getStaticPaths

当页面的路径取决于外部数据

这种方式和上面就有些不同了,上面http://localhost:3000/blog访问路径时我们已经写了一个 js 页面文件,但如果我们想要根据获取的数据动态生成多篇文章路径的话,那就需要用到这个 API,通常用这个 API 会同时配合getStaticProps一起用。

新建文件pages/posts/[id].js,你没有看错,文件名就是[id].js

id标识单篇博文,比如你访问http://localhost:3000/posts/1就展示id为 1 的文章。

代码如下:

const Post = ({ post }) => {
  return (
    <div>
      <div>文章id: {post.id}</div>
      <div>博客标题: {post.title}</div>
    </div>
  )
}

// 构建路由
export async function getStaticPaths() {
  // 调用外部 API 获取博文列表
  const res = await fetch('https://jsonplaceholder.typicode.com/todos')
  const posts = await res.json()

  // 根据博文列表生成所有需要预渲染的路径
  const paths = posts.map(post => `/posts/${post.id}`)

  // fallback为false,表示任何不在 getStaticPaths 的路径的结果将是 404 页面。
  return { paths, fallback: false }
}

// 获取单个页面博文数据
export async function getStaticProps({ params }) {
  // 如果路由是 /posts/1,那么 params.id 就是 1
  const res = await fetch(`https://jsonplaceholder.typicode.com/todos/${params.id}`)
  const post = await res.json()

  // 通过 props 参数向页面传递博文的数据
  return { props: { post } }
}

export default Post

拉取下来的数据全部有 200 条,也就是说动态构建了 200 个路由,现在我们访问第 50 篇博文的话,直接输入http://localhost:3000/posts/50

页面显示:

文章id: 50
博文标题: cupiditate necessitatibus ullam aut quis dolor voluptate

接下来让我们再次打包,执行npm run build(如果打包发生错误很可能是网络差和请求链接有关,因为要请求很多页面,如果是网络问题多试几次就行),你会发现这次打包时间变长了,因为要构建的静态页面多了,我们请求返回来的数据有 200 条,所以应该生成 200 个静态 HTML 文件和相应的 json 数据文件。

我们来看 1.json 文件,就是请求后的数据

{"pageProps":{"post":{"userId":1,"id":1,"title":"delectus aut autem","completed":false}},"__N_SSG":true}

getServerSideProps

每次页面请求时重新生成页面的 HTML

如果无法在用户请求之前预渲染页面,那上面的"静态生成"就不太适用了,或者是页面数据需要频繁的更新,并且页面内容会随着每个请求而变化。这个时候,有两种方案:

  • 将“静态生成”与 客户端渲染 一起使用:你可以跳过页面某些部分的预渲染,然后使用客户端 JavaScript 来填充它们
  • 使用 服务器端渲染: Next.js 针对每个页面的请求进行预渲染。由于 CDN 无法缓存该页面,因此速度会较慢,但是预渲染的页面将始终是最新的。

由于服务器端渲染会导致性能比“静态生成”慢,因此仅在绝对必要时才使用此功能。

新建文件pages/server-blog.js

const Blog = ({ posts }) => {
  return <div>title: {posts.title}</div>
}

// 在每次页面请求时都会运行,而在构建时不运行。
// 要设置某个页面使用服务器端渲染,就需要导出 getServerSideProps 函数
export async function getServerSideProps() {
  // 调用外部 API 获取内容
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1')
  const posts = await res.json()

  // 在构建时将接收到 `posts` 参数
  return {
    props: {
      posts,
    },
  }
}

export default Blog

运行npm run build,发现这次并没有生成对应的 html 文件——server-blog.html,因为我们指定了服务端渲染,请求构建时不会执行,不是静态生成。

getInitialProps

getInitialProps 是在渲染页面之前就会运行的 API。 如果该路径下包含该请求,则执行该请求,并将所需的数据作为 props 传递给页面。当第一次访问直接页面,getInitialProps 就在服务器端运行,加载完毕后页面给客户端托管,使用客户端的路由跳转,之后页面的 getInitialProps 就在客户端执行了。

推荐: getStaticProps 或 getServerSideProps。如果你使用的是 Next.js 9.3 或更高版本,我们建议你使用 getStaticProps 或 getServerSideProps 来替代 getInitialProps。这些新的获取数据的方法使你可以在静态生成(static generation)和服务器端渲染(server-side rendering)之间进行精细控制。

新建文件pages/initial-blog.js

import Link from 'next/link'

const InitialBlog = ({ post }) => {
  return (
    <div>
      <h1>getInitialProps Demo Page</h1>
      <div>获取到的title: {post.title}</div>
      <Link href='/'>
        <a>返回首页</a>
      </Link>
    </div>
  )
}

InitialBlog.getInitialProps = async ctx => {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1')
  const post = await res.json()
  return { post }
}

export default InitialBlog

再修改pages/index.js代码,增加一个Link标签可以跳转回该页面

<div>
  <Link href='/initial-blog'>
    <a>进入Initial Blog</a>
  </Link>
</div>

接着直接地址栏输入http://localhost:3000/initial-blog,在渲染该页面内容前,先会执行getInitialProps方法,执行完的结果再传到页面组件,开始渲染页面。由于我们是第一次访问页面,首屏则为服务端渲染。

页面内容显示为:

getInitialProps Demo Page
获取到的title: delectus aut autem

打开浏览器的 Network 面板,找到 Name 值为initial-blog的请求,会看到请求响应内容的正是该页面的 HTML。

点击返回首页,再从首页点击进入Initial Blog,重新进入该页面,注意的是此时已经不是我们浏览器通过地址栏访问该页面,而是通过前端路由进入该页面。

查看 Network 面板的请求,会看到此时返回的是getInitialProps请求回来的数据,而不是 HTML 页面内容,由此可知此时页面已经交由客户端渲染。这就是所谓 SSR 渲染,首屏交给服务端渲染返回,返回后页面跳转就是客户端渲染了。