React服务端渲染框架 Next.js(二十一)

316 阅读8分钟

1.Next.js 介绍

Next.js 是 React 服务端渲染应用框架,用于构建 SEO 友好的 SPA 应用

  1. 支持两种预渲染方式, 静态生成和服务器端渲染
  2. 基于页面的路由系统, 路由零配置
  3. 自动代码拆分. 优化页面加载速度
  4. 支持静态导出, 可将应用导出为静态网站
  5. 内置 CSS-in-JS 库 styled-jsx
  6. 方案成熟, 可用于生产环境, 世界许多公司都在使用
  7. 应用部署简单, 拥有专属部署环境 Vercel,也可以部署在其他环境

2.创建 Next.js 项目

创建: npm init next-app next-guide

运行: npm run dev

访问: localhost:3000

临时安装 create-next- app 用于创建 Next.js 项目。

3.基于页面的路由系统

1.创建页面

在Next.js中,页面是被放置在 pages 文件夹中的 React 组件

组件文件中不需要引入React并且需要被默认导出

页面地址与文件地址是对应的关系

// pages/list.js
export default function List () {
  return (
    <div>List Page</div>
  )
}

访问地址是:http://localhost:3000/list

2. 页面跳转

页面与页面之间通过 Link 组件进行跳转

Link 组件默认使用 JavaScript 进行页面跳转. 即 SPA 形式的跳转

如果浏览器中 JavaScript 被禁用. 则使用链接跳转

Link 组件中不应添加除 href 属性以外的属性, 其余属性添加到 a 标签上

Link 组件通过预取(在生产中)功能自动优化应用程序以获得最佳性能

import Link from "next/link";

export default function Home() {
  return (
    <div>
      Index Page works
      <Link href="/list">
        <a>Jump to List Page</a>
      </Link>
    </div>
  );
}

4.静态资源、元数据和 CSS

1.静态资源

应用程序根目录中的 public 文件夹用于提供静态资源

通过以下形式进行访问:

/images/1.jpg => public/images/1.jpg /css/base.css => public/css/base.css

2.修改页面元数据

通过 Head 组件修改元数据:

import Head from "next/head";

export default function Home() {
  return (
    <>
      <Head>
        <title>index pages</title>
      </Head>
    </>
  );
}

3. CSS 样式

3.1 内置 styled-jsx

在 Next.js 中 内置了 styled-jsx,它是一个 CSS-in-JS 库

它允许在 React 组件中编写 CSS,,CSS 仅作用于组件内部

import Head from "next/head";
import Link from "next/link";

export default function Home() {
  return (
    <>
      <Head>
        <title>Index Page</title>
      </Head>
      <div>
        Index Page works
        <Link href="/list">
          <a className="demo">Jump to List Page</a>
        </Link>
        <img src="/images/1.jpeg" height="100" />
      </div>
      <style jsx>
        {`
          .demo {
            color: red;
          }
        `}
      </style>
    </>
  );
}

3.2 CSS模块

通过使用 CSS 模块功能,允许将组件的 CSS 样式编写在单独的 CSS 文件中

CSS 模块约定样式文件的名称必须为"组件文件名称.module.css"

// pages/list.js
import Head from "next/head";
import style from "./list.module.css";

export default function List() {
  return (
    <>
      <Head>
        <title>List Page</title>
      </Head>
      <div className={style.demo}>List Page</div>
    </>
  );
}
// page/list.module.css
.demo {
  color: tomato;
}

3.3 全局样式文件

  1. 在 pages 文件夹中新建 _app.js 文件并加入如下代码
  2. 在项目根目录下创建 styles 文件夹,并在其中创建 global.css
  3. 在 _app.js 中通过 import 引入 global.css.
  4. 重新启动开发服务器
// pages/_app.js
import "../global.css";

export default function App({ Component, pageProps }) {
  return <Component {...pageProps} />;
}
/** global.css **/
body {
  background-color: skyblue;
  font-size: 14px;
}

5.预渲染

1.预渲染概述

预渲染是指数据和 HTML 的拼接在服务器端提前完成

预渲染可以使 SEO 更加友好

预渲染会带来更好的用户体验,可以无需运行 JavaScript 即可查看应用程序 UI

2.预渲染的两种形式

在 Next.js 中支持两种形式的预渲染: 静态生成和服务器端渲染

静态生成和服务器端渲染是生成 HTML 的时机不同

静态生成:静态生成是在构建时生成 HTML ,以后的每个请求都共用构建时生成好的 HTML

服务器端渲染:服务器端渲染是在请求时生成 HTML ,每个请求都会重新生成 HTML

3.两种预渲染方式的选择

Next.js 允许开发者为每个页面选择不同的预渲染方式, 不同的预渲染方式拥有不同的特点, 应根据场景进行渲染

但建议大多数页面建议使用静态生成

静态生成一次构建,反复使用,访问速度快,因为页面都是事先生成好的

适用场景:营销页面、博客文章、电子商务产品列表、帮助和文档

服务器端渲染访问速度不如静态生成快,但是由于每次请求都会重新渲染,所以适用数据频繁更新的页面或页面内容随请求变化而变化的页面

4.无数据和有数据的静态生成

如果组件不需要在其他地方获取数据,直接进行静态生成

如果组件需要在其他地方获取数据, 在构建时 Next.js 会预先获取组件需要的数据,然后再对组件进行静态生成

5. 静态生成 getStaticProps

getStaticProps 方法的作用是获取组件静态生成需要的数据,并通过 props 的方式将数据传递给组件

该方法是一个异步函数,需要在组件内部进行导出

在开发模式下,getStaticProps 改为在每个请求上运行

// pages/list.js
import Head from "next/head";
import style from "./list.module.css";
import { readFile } from "fs";
import { promisify } from "util";
import { join } from "path";

const read = promisify(readFile);

export default function List({ data }) {
  return (
    <>
      <Head>
        <title>List Page</title>
      </Head>
      <div className={style.demo}>List Page</div>
      <div>{data}</div>
    </>
  );
}

export async function getStaticProps() {
  let data = await read(join(process.cwd(), "pages", "_app.js"), "utf-8");
  console.log(data); // 会在 node 环境下输出,这个函数会在构建时运行
  return {
    props: {
      data,
    },
  };
}

6. 服务器端渲染 getServerSideProps

如果采用服务器端渲染, 需要在组件中导出 getServerSideProps 方法

// pages/list.js
export async function getServerSideProps(context) {
  // context 包含特定的请求参数如 query
  console.log(context.query);
  let data = await read(join(process.cwd(), "pages", "_app.js"), "utf-8");
  return {
    props: {
      data,
    },
  };
}

运行 npm run build 之后,生产 .next 文件夹,可以看到 list 页面不会生成 HTML 页面

然后运行 npm start 启动生产环境的代码,访问 /list 页面, node 控制台会输出 getServerSideProps 方法中的打印语句

因为使用了 getServerSideProps 则表示采用服务端渲染,而不是静态生成,所以每次访问都会执行 getServerSideProps 方法

7.实现基于动态路由的静态生成

  1. 创建基于动态路由的页面组件文件, 命名时在文件名称外面加上[], 比如[id].js

  2. 导出异步函数 getStaticPaths, 用于获取所有用户可以访问的路由参数

    export async function getStaticPaths () {
      // 此处获取所有用户可以访问的路由参数
      return {
        // 返回固定合适的路由参数
        paths: [{params: {id: 1}}, {params: {id: 2}}],
        // 当用户访问的路由有参数没有在当前函数中返回时,是否显示404页面, false表示显示,true 表示不显示
        fallback: false
      }
    }
    
  3. 导出异步函数 getStaticProps, 用于根据路由参数获取具体的数据

    export async function getStaticProps ({params}) {
      // params.id
      // 此处根据路由参数获取具体数据
      return {
        // 将数据传递到组件中进行静态页面的生成
        props: {}
      }
    }
    

注意:getStaticPaths 和 getStaticProps 只运行在服务器端,永远不会运行在客户端,甚至不会被打包到客户端 JavaScript 中,意味着这里可以随意写服务器端代码,比如查询数据库

8.自定义 404 页面

要创建自定义 404 页面,需要在 pages 文件夹中创建 404.js 文件

// 404.js
export default function Error () {
  return <div>404 ~</div>
}

9.API Routes

API Routes 可以理解为接口,客户端向服务器端发送请求获取数据的接口

Next.js 应用允许 React 开发者编写服务器端代码创建数据接口

实现 API Routes:

  1. 在 pages/api 文件夹中创建 API Routes 文件,比如 user.js
  2. 在文件中默认导出请求处理函数,函数有两个参数,req 为请求对象,res 为响应对象
// 访问 API Routes: localhost:3000/api/user
// 不要在 getStaticPaths 或 getStaticProps 函数中访问 API Routes, 因为这两个函数就是在服务器端运行的,可以直接写服务器端代码
export default function (req, res) {
  res.status(200).send({ id: 1, name: "yunmu" });
}

6.movie 项目

案例代码:React/next-movie · 云牧/exampleCode - 码云 - 开源中国 (gitee.com)

1.创建项目

npm init next-app movie

cd movie

npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion

npm install react-icons

npm install @emotion/babel-preset-css-prop -D

npm install @babel/core

npm run dev

访问: localhost:3000

pages/_app.js

// import '../styles/globals.css'

import { ChakraProvider } from "@chakra-ui/react";
import theme from "@chakra-ui/theme";

function App({ Component, pageProps }) {
  return (
    <ChakraProvider theme={theme}>
      <Component {...pageProps} />
    </ChakraProvider>
  );
}

export default App;

根目录创建 .babelrc 文件配置:

{
    "presets": ["next/babel", "@emotion/babel-preset-css-prop"]
}

2.启动数据服务

找到对应项目中的 api 文件

npm install

npm run dev

数据服务地址IP: localhost:3005

在 axiosConfig.js 文件中导出 baseURL = 'http://localhost:3005'

3.编写代码

编写 Header 组件

编写 Navigation 组件

编写 Swiper 组件

编写 Movie 组件

编写 pages/detail/[id].js

4.生成静态文件

pages/detail/[id].js

// 获取到用户能够访问到的所有的路由参数
export async function getStaticPaths() {
  // ["1", "2"]
  let { data } = await axios.get("/api/videos", { baseURL });
  // [{params: {id: "1"}}]
  let paths = data.map((id) => ({ params: { id } }));
  return {
    paths,
    fallback: false,
  };
}

// 根据参数获取其对应的数据
export async function getStaticProps({ params }) {
  let id = params.id;
  let { data: detail } = await axios.get(`/api/detail?id=${id}`, { baseURL });
  return {
    props: {
      detail,
    },
  };
}

index.js

import Layout from "../components/Layout";
import Swiper, { loadSwiper } from "../components/Swiper";
import Movie, { loadMovie } from "../components/Movie";

export default function Home({ swiper, movie }) {
  return (
    <Layout>
      <Swiper data={swiper} />
      <Movie data={movie} title="电影" />
    </Layout>
  );
}

export async function getStaticProps() {
  // 获取轮播图数据
  let { data: swiper } = await loadSwiper();
  // 获取电影列表数据
  let { data: movie } = await loadMovie();
  return {
    props: {
      swiper,
      movie,
    },
  };
}

然后根据数据渲染的对应的页面即可

最后需要导出静态网站:

"export": "next build && next export"

5.自定义 next 服务

package.json

"dev": "nodemon server/index.js",

server/index.js

const express = require('express')
const next = require('next')

const dev = process.env.NODE_ENV !== 'production'

const app = next({dev})

const handler = app.getRequestHandler()

// prepare 方法是准备一下 next 应用
app.prepare().then(() => {
  const server = express()
  // 自定义路由系统
  server.get('/hello', (req, res) => {
     res.send('Hello Next.js')
  })
  // 使用内部基于页面的路由系统处理
  server.get('*', (req, res) => {
    handler(req, res)
  })

  server.listen(3000, () => console.log('服务器启动成功,请访问: http://localhost:3000'))
})

6. 部署到 Vercel

GitHub 上创建一个仓库,将这个项目代码导入到 GitHub 仓库

登陆 vercel.com/ 选择 GitHub 的这个仓库进行部署