掌握Next.js(一)– 基础知识

​本系列开始介绍Next.js.


先决条件

学习本系列,你无需任何Next.js知识,但需要对React、TypeScript有基本的了解。


什么是Next.js

Next.js是一个用于构建快速且搜索引擎友好的应用程序的框架。

Next.js基于React,React相关技能在Next.js中一样有效。不同的是,React只是一个用于创建交互UI的库,而Next.js则是一个综合的框架。

比如,在React中,我们需要单独安装react-router,而在Next.js中则无需安装,它已经自带了。

在工具方面,Next.js带来的编译器可以转换及缩减我们的JS代码,命令行工具可以构建并启动应用程序,以及带来一个Node.js运行环境。

那到底什么是Node.js?

Node.js是可以运行JavaScript的环境。众所周知,Javascript可以运行在浏览器环境中,另一个可以运行的环境就是Node.js。

利用Node.js可以运行Javascript的特点,Next.js可以做些很酷的事情。

  • 全栈开发。我们可以在同一个Next.js项目中开发前端和后端代码,后端代码在Node.js中执行,前端代码打包后发往客户端在浏览器中执行。作为对比,如果使用React开发,我们一般还需要用其他语言创建一个后端项目。
  • 允许服务端渲染(SSR)。可以在服务器端渲染组件,然后发到客户端。这种方式会使我们的应用程序更快,对搜索引擎也更友好。
  • 预渲染静态组件。这样我们可以只渲染一次,后续就可以随时使用。

设置开发环境

首先需要16.8以上版本的Node.js,可以去nodejs.org下载安装最新版。

编辑器方面建议用vscode,访问code.visualstudio.com下载。

vscode安装以下扩展:

  • ES7+React/Redux/React-Native Snippets
  • TypeScript and TypeScript Nightly
  • Tailwind CSS IntelliSense

创建您的第一个Next.js项目

创建项目:

 npx create-next-app@13.4

输入项目名称,并选择一系列问题,一般保持默认选项即可。接着等待相关依赖库安装完成。

完成后。我们进入项目目录,并运行项目。

cd next-app
npm run dev

再访问 http://localhost:3000,能看到Next.js项目的页面。


项目结构

我们来看一看Next.js项目的关键文件和文件夹。

  • 先看app文件夹。

这是Next.js的路由系统,它不像React需要创建routes并进行映射,它基于文件系统,只需创建文件和文件夹即代表路由。

app文件夹有favicon.ico、global.css、layout.tsx、page.tsx文件,global.css是应用全局的css,layout.tsx是一个通用的布局,page.tsx则是我们看到的主页,我们清除内容,只加上hello world字样。

// page.tsx
​
import Image from "next/image";
​
export default function Home() {
  return (
    <main>
      <h1>Hello World</h1>
    </main>
  );
}
​

我们也稍微修正下global.css的body这一节

// global.css
​
......
​
body {
  color: rgb(var(--foreground-rgb));
  padding: 1rem;
}

回看主页,已变了。

  • 再看public文件夹。

主要用于放置一些公用资源,比如图像。这里默认就放了next.svg和vercel.svg。顺便说一下,vercel就是创建next.js的公司。

  • 根目录。

根目录下有一些配置文件。在绝大多数情况下,我们不必碰这些文件。


路由和导航

前面说过,Next.js是通过文件系统来路由的。

app文件夹下,新建users文件夹并在该文件夹下新建page.tsx文件,就相当于新建了一个路由 /users。该路由展现的页面组件定义在page.tsx文件,该文件名是约定的,必须是page,扩展名可以是tsx(typescript)或jsx(javascript)。

// app/users/page.tsx
​
import React from "react";
​
const UsersPage = () => {
  return <div>UsersPage</div>;
};
​
export default UsersPage;

访问 /users,就能显示UserPage。

我们在 users文件下新建其他文件比如 test.tsx,再在Url中访问 /users/test.tsx ,老版本是允许的,但新版本已不再有效了。

我们可以在 users下再建文件夹如new,再在new文件夹下新建page.tsx创建组件,访问 /users/new 就可展现该组件,以此类推。

导航方面,尽量避免使用 <a href=…. />的方式,这样会导致全部资源重载,而应该使用Link组件, <Link  href=…./>,这样就只会更新需要更新的部分,使用Link组件也叫客户端导航。

// app/page.tsx
​
import Link from "next/link";
​
export default function Home() {
  return (
    <main>
      <h1>Hello World</h1>
      <Link href="/users">Users</Link>
    </main>
  );
}

导航还包含其他很多内容,后续会详细介绍,这里仅作基本了解。


客户端与服务器组件

在Next.js项目中有两个环境可以渲染组件或生成HTML标记:客户端的浏览器 和 服务端的Node.js环境。客户端渲染简称CSR,服务端渲染简称SSR。

我们来看看两种渲染的差别。

客户端渲染需要将相关资源打包并发往客户端浏览器,随着项目的增长,打包将越来越大,属于资源密集型。客户端渲染不能被搜索引擎感知,因为搜索引擎不能执行Javascript代码无法知道具体的内容。最后一点也是重要的,有些敏感信息如API Key也需要暴露到客户端,存在一定的安全风险。

服务器端渲染不会有以上问题。

但服务器端渲染会失去交互性。

服务器端组件不能监听浏览器事件,比如点击、提交等。服务器组件不能访问浏览器APIs,比如本地缓存。服务器组件也不能维护State,不能使用页面特效等。

实际项目中,我们应该默认使用服务器组件,必须使用客户端组件时才使用它。

比如,我们需要创建一页面显示商品清单。添加商品到购物车的操作需要客户端组件,由于该操作在ProductCard组件,可以将该组件拎到客户端,其他在服务端渲染。

还有一种更好的处理方法,可以从ProductCard抽取一个组件AddToCart放至客户端,ProductCard继续放在服务端。

回到我们的项目。

在app文件夹下的组件默认都是服务端渲染。可以从浏览器工具查看到。这样搜索引擎也能获取到。

我们在app文件夹下新建components文件夹,前面说过,不加page.tsx文件就不会成为路径,这样我们可以将其他文件夹与我们的实际路由文件夹并排放置。再在components文件夹下新建ProductCard.tsx文件。

// ProductCard.tsx
​
const ProductCard = () => {
  return (
    <div>
      <button onClick={
        () => console.log("click")}
        >Add To Cart</button>
    </div>
  );
};
​
export default ProductCard;

将ProductCard组件添加到首页上。

// app/page.tsx
​
import Link from "next/link";
import ProductCard from "./components/ProductCard";
​
export default function Home() {
  return (
    <main>
      <h1>Hello World</h1>
      <Link href="/users">Users</Link>
      <ProductCard />
    </main>
  );
}

查看页面就出错了。

原因是onClick不能在服务端执行,服务端是不能交互的。

我们有两个选择,一是将ProductCard变成客户端组件,二是将Button抽取出来形成一个客户端组件AddToCart,而ProductCard继续保持在服务端渲染。

声明是客户端组件,只需在首行加上 “use client”; 即可。

"use client";
......

我们在components文件夹下新建AddToCart.tsx文件,声明该组件是客户端组件。

// AddToCart.tsx
​
"use client";
import React from "react";
​
const AddToCart = () => {
  return <button onClick={() => console.log("click")}>Add To Cart</button>;
};
​
export default AddToCart;

然后在ProductCard调用。

// ProductCard.tsx
​
import AddToCart from "./AddToCart";
​
const ProductCard = () => {
  return (
    <div>
      <AddToCart />
    </div>
  );
};
​
export default ProductCard;

这样之后,页面就不会出错了。如代码所示,ProductCard还是服务端组件,它渲染后开了一个孔或一个槽,留给客户端插入。客户端收到后继续渲染AddToCart组件


数据获取

有两种方式:客户端获取和服务端获取。

客户端获取就是我们在React中介绍过的useState+useEffect方式,或者使用useQuery来代替。但不管怎样,上一节介绍过的客户端缺点均存在。

客户端还带来另一个问题,就是额外增加到服务器的往返,我们加载时先下载jss、html、css,然后又发起获取数据的请求。

而使用服务端获取数据,这些问题均不再存在。

修改下 /users 的页面。这个组件默认在服务端渲染,使用fetch获取jsonplaceholder的伪数据。

// users/page.tsx
​
import React from "react";
​
interface User {
  id: number;
  name: string;
}
const UsersPage = async () => {
  const res = await fetch("https://jsonplaceholder.typicode.com/users ");
  const users: User[] = await res.json();
  return (
    <div>
      <ul>
        {users.map((u) => (
          <li key={u.id}>{u.name}</li>
        ))}
      </ul>
    </div>
  );
};
​
export default UsersPage;

查看页面,能看到响应已形成页面。如果客户端渲染的话将是空白页面,然后再向服务端发起请求获取数据,会额外增加服务器的往返。

所以,一般尽量采用服务端获取数据。


缓存

从服务端获取数据有一个好处是缓存。

什么是缓存?缓存就是某个存放数据的地方,它能使我们快速访问。一般而言,有三种类型:内存、文件系统、网络。内存最快,文件系统次之,网络最慢。

Next.js内建文件系统缓存!当我们获数据后,Next.js自动存入缓存,下次需要数据时,直接从缓存里获取。当然我们也可以控制要不要缓存,以及缓存有效时间等。

如下面代码,在fetch函数加上第2个参数。在某些数据变化很快的场景中可能不需要缓存,可以设置 {cache: ‘no-store’}。设置间隔的话,可以设置{next:{revalidate:10}} 表示10秒后后台自动更新缓存。

// users/page.tsx
​
......
​
const UsersPage = async () => {
  const res = await fetch("...", {
    // cache: "no-store",
    next: { revalidate: 10 },
  });
  
......
​

另外需要注意,只有使用fetch才有自动缓存功能,如果用第三方的方法,则不会有自动缓存。


静态和动态渲染

有些静态数据,我们只需在构建的时候渲染一次,后续需要时不必再渲染,直接从缓存取出即可。一次渲染多次使用。这就叫静态渲染。

而动态渲染则是每次请求时都进行渲染。

默认情况下,在生产环境下,只要fetch数据时未取消缓存(“no-store”),都会把页面作静态渲染处理。

(开发环境下,每次请求都会进行渲染,可能为了方便试)

我们添加时间信息,以便看出渲染时间。

// users/pages.tsx
​
......
​
  return (
    <div>
      <h1>Users</h1>
      <p>{new Date().toLocaleTimeString()}</p>
      ......
      
    </div>
  );
};
​
export default UsersPage;
​

然后,停止项目运行,重新进行构建及实际部署。

npm run build
npm start

然后我们刷新页面,发现时间是不会变的。

如果我们添加上 fetch(“…”,{cache:’no-store’}),再重新构建运行,时间就会在每次请求时刷新了,也说明是在动态渲染了。


小结

本文介绍了什么是Next.js,如何设置Next.js项目,并介绍了Next.js的基础知识。

下一篇介绍Next.js的样式。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注