NextJS: 渲染模式和数据获取

18546
2022/11/17 19:14:27

如果你已经忘了我们之前讨论的 React 和 SEO 的问题,可以点击这里回顾一下。

NextJS 的渲染模式

NextJS 提供了几种渲染模式,你可以针对不同场景进行选择:

渲染模式说明应用场景举例是否SEO友好
静态网站生成将我们的组件和数据(包括 stateprops 等,下同)生成静态的 HTML 文件。可用于静态文件生成,实现诸如 hugo、hexo 等静态网站生成器的功能。
客户端渲染就是传统的 React 的渲染模式。适用于不关心 SEO 的项目,比如后台管理、企业内部系统等。
服务端渲染利用内置的服务器,将我们的组件和数据动态地生成 HTML。类似于使用 AXUM 开发网站。适合需要 SEO,数据实时更新的项目。

静态网站生成 Static Site Generation

NextJS 支持将整个项目生成为一个纯 HTML 的静态网站。我们通过一个小案例来体验一下这个强大的功能。本案例就是显示一个用户列表,为了验证静态网站生成是否真的会将组件和数据都生成静态HTML,我们特意使用了两个组件:用于显示用户列表的 UserList及其子组件 UserItem

// components/UserItem.jsx
export default function UserItem({ user }) {
  return (
    <li>
      ID: {user.id}, 姓名: {user.name}
    </li>
  );
}

// components/UserList.jsx
import UserItem from './Item';

export default function UserList() {
  const users = [
    { id: 1, name: 'axum中文网1' },
    { id: 2, name: 'axum中文网2' },
    { id: 3, name: 'axum中文网3' },
    { id: 4, name: 'axum中文网4' },
    { id: 5, name: 'axum中文网5' },
    { id: 6, name: 'axum中文网6' },
  ];
  return (
    <ul>
      {users.map((user) => (
        <UserItem key={user.id} user={user} />
      ))}
    </ul>
  );
}

// pages/index.jsx
import { UserList } from '../components/User';
export default function Home() {
  return (
    <div>
      <h1>用户列表</h1>
      <UserList />
    </div>
  );
}

生成静态网站

现在可以使用命令 npx next build && npx next export 生成静态网站了,为了方便使用,我们把它作为自定义命令加到 package.json 中:

"scripts": {
  //...
  "html": "next build && next export"
},

这样,我们就可以简单的使用下面的命令来生成静态网站了:

yarn html

# 如果你用npm
npm run html

生成的静态文件在 out 目录下,打开 out/index.html

<!DOCTYPE html><html><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link rel="preload" href="/_next/static/css/ef46db3751d8e999.css" as="style"/><link rel="stylesheet" href="/_next/static/css/ef46db3751d8e999.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-c67a75d1b6f99dc8.js"></script><script src="/_next/static/chunks/webpack-8fa1640cc84ba8fe.js" defer=""></script><script src="/_next/static/chunks/framework-8c5acb0054140387.js" defer=""></script><script src="/_next/static/chunks/main-a15ee65a2f1e1948.js" defer=""></script><script src="/_next/static/chunks/pages/_app-2398585b21d2d8c8.js" defer=""></script><script src="/_next/static/chunks/pages/index-4c6abd58992a79df.js" defer=""></script><script src="/_next/static/uuuuuuuuuuuuuuuuuuuuu/_buildManifest.js" defer=""></script><script src="/_next/static/uuuuuuuuuuuuuuuuuuuuu/_ssgManifest.js" defer=""></script></head><body><div id="__next"><div><h1>用户列表</h1><ul><li>ID: <!-- -->1<!-- -->, 姓名: <!-- -->axum中文网1</li><li>ID: <!-- -->2<!-- -->, 姓名: <!-- -->axum中文网2</li><li>ID: <!-- -->3<!-- -->, 姓名: <!-- -->axum中文网3</li><li>ID: <!-- -->4<!-- -->, 姓名: <!-- -->axum中文网4</li><li>ID: <!-- -->5<!-- -->, 姓名: <!-- -->axum中文网5</li><li>ID: <!-- -->6<!-- -->, 姓名: <!-- -->axum中文网6</li></ul></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/","query":{},"buildId":"uuuuuuuuuuuuuuuuuuuuu","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>

非常非常耐丝,我们的组件和数据全部变成了静态 HTML,再也不用担心 SEO了。

本小节代码:axum-rs-nextjs-static-site-gen-1

客户端渲染 Client-Side Rendering

客户端渲染就是 React 的模式,React 怎么玩,客户端渲染也怎么玩

  • 如果要从后端获取数据,将 HTTP 请求放在 useEffect hook 里
  • 构建最终项目时,只需要 build ,不需要像生成静态网站那样,还要 export

还是使用那个从 https://httpbin.org/ip 获取客户端ip为例:

import { useState, useEffect } from 'react';

export default function Home() {
  const [ip, setIp] = useState('正在获取');

  useEffect(() => {
    window
      .fetch('https://httpbin.org/ip')
      .then((resp) => resp.json())
      .then((data) => {
        setIp(data.origin);
      });
  }, []);
  return <h1>你好,你的IP是 {ip}</h1>;
}

你会看到,不就是 React 嘛!此例我们使用 HTML 5 提供的 fetch() 来发起 HTTP 请求。

为了告诉打包工具,我们使用的是浏览器提供 fetch(),所以这里要写上完整的 window.fetch()

本小节代码:axum-rs-nextjs-client-side-rendering

服务端渲染 Server-Side Rendering

在服务端渲染模式下,我们通常需要从后端获取数据,并将数据填充给组件,然后动态地将组件转换成 HTML。

要从后端获取数据,自然要有 HTTP 请求库,注意:

  • 在服务端渲染模式下,一定要通过 yarn 来安装 HTTP 请求库
  • 哪怕你想用 fetch() 也要额外安装,因为服务端渲染实际会开启一个 nodejs 的 HTTP 服务器,而 nodejs 并没有 fetch
  • 或者也可以安装服务端和客户端都能用的 axios

另外,NextJS 是通过一个特殊的函数来识别出是服务端渲染的,这个函数就是:getServerSideProps(),函数名简单明了:获取服务端的Props

  • getServerSideProps 只能定义在页面组件中,为页面组件服务(pages目录下的组件),对其它组件(比 compontents目录下的组件)无效
  • getServerSideProps中,必须将那些从服务端获取的数据包裹在 props对象里再进行返回,而不能直接返回从服务端获取的对象
  • nextjs 会自动将 getServerSideProps的返回值传递给页面组件,页面组件只需要通过参数就能接收到——就和在 React 中,组件接收 props 一样的写法

下面通过两个案例来演示服务端渲染。

第一个案例是将上面的静态代码生成变成服务端渲染。

第二个案例是将上面的获取IP的客户端渲染变成服务端渲染。

案例1:使用服务端渲染实现用户列表

import { UserList } from '../components/User';
export default function Home({ users }) {
  return (
    <div>
      <h1>用户列表</h1>
      <UserList users={users} />
    </div>
  );
}

export async function getServerSideProps() { // 第11行
  const users = [
    { id: 1, name: 'axum中文网1' },
    { id: 2, name: 'axum中文网2' },
    { id: 3, name: 'axum中文网3' },
    { id: 4, name: 'axum中文网4' },
    { id: 5, name: 'axum中文网5' },
    { id: 6, name: 'axum中文网6' },
  ];
  return { props: { users } }; // 第20行
} // 第21行
  • export default function Home({ users }):注意此时 Home 页面组件,它:

    • 从外界接收了 props。这个 props 将由 nextjs 自动把 getServerSideProps() 的返回值传递过来。

    • props 解构获取 users

    • 如果你对解构还不是很清楚,可以将上面的代码看成:

      export default function Home(props) {
        const users = props.users;
        return (
          <div>
            <h1>用户列表</h1>
            <UserList users={users} />
          </div>
        );
      }
      
  • <UserList users={users} /> :将这个从 props 里接收到的 users 原封不动地传递给 UserList 组件

  • 第11~21行:实现特殊的 getServerSideProps()

    • 它的返回值会作为参数发送给页面组件,此例中的 Home 组件

    • 第20行:必须将数据包裹在 props对象中进行返回

      // 错误!
      return {user};
      
      // 正确:要将数据作为 props 对象的成员进行返回
      return { props: {user}};
      

然后对 UserList 进行精简——从父组件接收数据

import UserItem from './Item';

export default function UserList({ users }) {
  return (
    <ul>
      {users.map((user) => (
        <UserItem key={user.id} user={user} />
      ))}
    </ul>
  );
}

本小节代码:axum-rs-nextjs-server-side-rendering-1

案例2:使用服务端渲染获取IP

import axios from 'axios';

export default function Home({ ip }) {
  return <h1>你好,你的IP是 {ip}</h1>;
}

export async function getServerSideProps() { //  第7行
  // 从服务端获取数据
  const { data } = await axios.get(`https://httpbin.org/ip`);
  const { origin } = data;

  // 通过 props 对象,将获取到的数据传递给页面组件
  return { props: { ip: origin } }; // 第13行
} // 第14行
  • export default function Home({ ip }):从 props 解构出 ip
  • 第7~14行:实现 getServerSideProps函数,它会从服务端获取数据,并将结果返回。页面组件将能接收到它的返回结果
    • 第13行:再次提醒,从服务端获得的数据不能直接返回,而是要包裹在 props 对象里

记得安装 axios

yarn add axios

本小节代码:axum-rs-nextjs-server-side-rendering-2

特殊的函数

通过上面的例子不难发现,NextJS 实现服务端渲染的关键在于一个名为 getServerSideProps 的特殊函数。其实 NextJS 提供了几个类似的特殊函数,适用于不同场景和目的。

  • 【服务器渲染】getServerSideProps():用于填充服务器渲染时所需要的动态 props,通常应该在这里发起 HTTP 请求,从远程服务端获取数据。NextJS 能保证,某用户在访问带有 getServerSideProps() 的页面时,总是会从远程服务端获取最新数据。
  • 【静态网站生成】getStaticProps():用于填充静态网站生成时所需要的 props,作用和 getServerSideProps()相似。注意,由于是静态网站生成,NextJS 只会将那些在构建项目时,从 getStaticProps() 获取的数据转换成 HTML,如果远程服务器数据发生变化,NextJS 不会自动更新 HTML。
  • 【静态网站生成】getStaticPaths():如果在生成静态网站时,有特殊要处理的路径,可以在这里指定。
  • 【客户端渲染】典型的 React,不需要特殊的函数,使用 React 提供的 useEffect hook 即可获取远程服务端的数据。

渲染模式的选择

问题来了,该如何选择 NextJS 的渲染模式呢?从2个维度供你参考:

是否要求数据实时更新是否要求SEO友好渲染模式
客户端渲染
静态网站生成
服务端渲染