Ados

a fullstack game worker

0%

每天进步一点点032 - Next.js学习

Preface

被迫向生活妥协,哪有什么鬼的生活与远方,能找到工作就不错了,操蛋的人生。

Contents

不用国内的文档,问题太多了。

创建项目

1
npx create-next-app@latest

之后 npm run dev 就可以看到效果。

1
2
3
4
5
6
"scripts": {
"dev": "next dev",//应用以开发模式启动
"build": "next build",//应用打包生产环境
"start": "next start",//启动一个生产环境服务器
"lint": "next lint"//设置NextJs内置的ESLint配置
}

新建项目的一些说明

  • app/page.tsx : 类似主页
  • app/layout.tsx : 所有页面的根容器,这个页面没有写的话,会在 npm run dev 的时候自动生成
  • public : 这里用于存放静态资源的,如,图片,字体等。 在代码中可以直接以 / 前缀访问

一些原则

服务端组件与客户端组件之间的传值

服务端组件与客户端组件之间的值传递需要序列化。也就是函数,日期这样的对象不能直接传给客户端组件。

仅限服务端的代码不要和客户端组件混到一起,这个是很致命的

可以使用 server-only 包给开发者报错。 cnpm i server-only,然后到仅限服务端的代码中导入即可。

数据获取

虽然客户端组件也可以获取数据,建议是通过服务端组件获取数据,除非特殊原因。服务端组件获取数据更快更方便。

第三方组件

服务端组件是一个新的概念,所以生态系统跟不上。很多组件 use client 没有指令会导致在服务端组件中使用的时候会报错。这个时候需要我们自己在本地封装一下:

1
2
3
'use client';
import { AcmeCarousel } from 'acme-carousel';
export default AcmeCarousel;

并不是所有的三方组件都需要封装一下,有一个例外是 provider 组件,因为他们只依赖React state和context,通常他们用在应用的root处。

Context

大部分的React应用依赖 context 在组件之间共享数据,或者直接通过 createContext*,或者通过第三方库 *provider 导入。
Next.js 13开始, 客户端组件全面支持 context*, 但是不能直接在服务端组件中创建或者消费。这是因为服务端组件没有 React state,因为他们不能交互,
*context
主要用于渲染完成后渲染树下的交互组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
'use client';

import { createContext, useContext, useState } from 'react';

const SidebarContext = createContext();

export function Sidebar() {
const [isOpen, setIsOpen] = useState();

return (
<SidebarContext.Provider value={{ isOpen }}>
<SidebarNav />
</SidebarContext.Provider>
);
}

function SidebarNav() {
let { isOpen } = useContext(SidebarContext);

return (
<div>
<p>Home</p>

{isOpen && <Subnav />}
</div>
);
}
  • root 中创建 context 会导致报错。
  • contextprovider 一般会在 root 附近创建,这样依赖可以提供全局支持。

服务端组件间共享数据

服务端组件不支持React。那么可以通过js自身的一些能力来做数据共享,如单例。
请求结果数据的共享是不必要的耦合。

路由

一般 app 底下的都是服务端组件。

路由约定

  • 文件夹用于定义路由
  • 叶子文件夹中包含了 page.js 用于定义UI

    文件约定

    客户端路由包含的文件:
  • page.js : 定义路由的UI,并且使次路由可公开访问
    • route.js : 创建路由的服务端api端点
  • layout.js : 为一个 segment 和它的子元素创建共享 UI 。一个 layout 包装了一个页面和他的子集。
    • template.js : 和 layout.js 类似, 他会为导航中的每个字元素创建一个新的实例,这意味着state不会保留,每次都是重新创建。除非你需要这个特性,否则直接使用layout。
  • loading.js :
  • error.js :
    • global-error.js :
  • not-found.js
    当然,也可以添加其他文件,如css,测试用例,组件等等

利用客户端导航的服务端路由

服务端组件和客户端组件不一样。

部分渲染

兄弟路由之间导航的时候,Next.js只渲染路由变动的部分,不会重新渲染整个也看。

高级路由

  • 平行路由 : 一次展示多个页面,多用于仪表盘
  • 拦截路由 :

修改

可以通过内置的SEO支持,在 app 文件夹下面修改 <head> 元素,如 titlemeta

路由之间的导航方法

  • Link 组件, 需要href属性
  • useRouter 钩子

可以使用usePathname() 来判断链接是否是激活状态。

方式滚动到顶部

1
2
3
<Link href="/#hashid" scroll={false}>
Scroll to specific id.
</Link>

useRouter

1
2
3
4
5
6
7
8
9
10
11
12
13
'use client';

import { useRouter } from 'next/navigation';

export default function Page() {
const router = useRouter();

return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
);
}

无特殊要求可以直接用 link 组件。

一些概念

Server Component Vs Client Component

服务端组件只在服务端渲染,可以在页面初始化的时候提速;客户端组件优先在客户端渲染,但是服务端也可以进行预渲染然后在客户端合成。
'use client' 必须定义在第一行,所有import之前,模组里面只要在入口点定义一次就可以了,然后在模组里面导入的其他组件都将被视为客户端组件。
|场景|Server Component| Client Component |
|—-|—-|—-|
| 获取数据 | ✅ | ❎ |
| 访问后端资源 | ✅ | ❎ |
| 敏感数据保存在服务端(访问token,api key等等) | ✅ | ❎ |
| 将大型依赖保存在服务端/减少客户端js | ✅ | ❎ |
| 添加互动与事件监听 例如:onClick,onChange 等 | ❎ | ✅ |
| useState和生命周期effect : useState,useReducer,useEffect等 | ❎ | ✅ |
| 使用仅浏览器支持的api | ❎ | ✅ |
| 使用基于state,effect或者仅支持浏览器api的自定义钩子 | ❎ | ✅ |
| 使用React Class Component | ❎ | ✅ |

组合使用的渲染流程

  • 服务端在将渲染结果发送到客户端之前渲染所有的服务端组件
    • 包括了客户端组件内嵌的服务端组件
    • 遇到客户端组件就跳过
  • 在客户端React渲染服务端返回的服务端组件渲染后的结果中的客户端组件和插槽
    • 如果客户端组件中嵌入了任何服务端组件,他们的渲染内容会正确的渲染到客户端组件中

Next.js 在初始页面加载期间,会将以上组件渲染结果提前备服务端预渲染成html,以使页面加载更快。
在服务端渲染组件中的子客户端组件中,可以直接插入服务端组件。

鉴于以上流程,在客户端组件中导入服务端组件有一些限制:

不能直接导入服务端组件,建议通过props的方式传入

Reference