Next
SSR概念
服务端渲染顾名思义实在服务端渲染dom,css生成页面,相较于客户端渲染,用户不需要再在客户端才请求首屏需要加载资源,首屏加载速度尤其对于C端会有更明显的提升。但同样的项目的复杂性会相较于客户端渲染有所增加,例如需要远程服务器的node服务,从打包构建-到环境部署可能与客户端的纯静态资源上传服务器有所不同,还有在开发难度上与原先的客户端渲染也会有差异。
React下的SSR-Next
在学习的过程中主要关注与客户端渲染是的区别
项目结构
相比于客户端渲染,Next在编写时最大的区别就是目录即路由的概念,项目中不再需要配置routes,也不需要编写嵌套路由的硬性布局。
[your-project-name]
├── app
│ ├── login
│ │ ├── page.tsx (/login打开的页面)
│ │ └── layout.tsx (/login页面布局)(可选)
│ ├── user
│ │ ├── page.tsx (/user打开的页面)
│ │ └── layout.tsx (/user页面布局)(可选)
│ └── api
│ ├── login
│ │ └── route.ts (/api/login)
│ └── user
│ └── route.ts (/api/user)
├── layout.tsx (根布局,需要html body)
└── page.tsx (/打开的页面)
不再需要入口html文件,而是在最顶层的layout.tsx文件当中显示添加html和body
Next以use client来区分服务端渲染文件还是客户端渲染文件,以use server定义服务端函数
- 使用浏览器api,或者dom事件,或者像客户端渲染时所用的useState,useEffect需要写入在客户端组件当中(也就是说这部分的内容逻辑必须要单独起一个文件来写,不能一个文件写到底了)
- 客户端组件可以作为服务端组件的子组件,服务端父组件可以通过props将值传递给客户端子组件。但是服务端组件不能显示的作为客户端的子组件,只能以children的方式传递到客户端组件中,并且无法传递值给服务端子组件。(原因:use client会使得当前文件以及导入的依赖文件都命中客户端渲染逻辑,从而导致服务端组件不能正常在服务端执行。)
- 服务端函数顾名思义是在服务端渲染的函数,在这样子的函数中执行请求,可享受不受跨域影响的请求调用。(客户端组件中也可以使用这个函数,但本质上是next发起了一次请求到服务端,服务端再执行这个函数再将结果进行返回,与rpc通信比较像)
动态路由、路由组、路由插槽、路由拦[Next实现的有别于客户端渲染的]
动态路由、路由组、路由拦截均可按照Next定义的文件夹命名规则来进行实现。
[your-project-name]
└── app
├── (card) (购物车路由组,这层的文件名称不会作为路由path部分)
│ ├── info
│ │ ├── page.tsx
│ │ └── layout.tsx
│ ├── buy
│ │ ├── page.tsx
│ │ └── layout.tsx
│ └── product
│ └── [id] (动态路由,等价于客户端渲染的/product/:id路由写法)
│ ├── page.tsx
│ └── layout.tsx
├── page.tsx
└── layout.tsx
Next中动态路由:官网文档
写法 | 匹配 | 路由入参 |
---|---|---|
[id] | /product/1(只能匹配一位不能没有) | { slug: 1 } |
[…id] | /product/1, /product/1/2(可匹配多位不能没有) | { slug: [1] },{ slug: [1, 2] } |
[[…id]] | /product,/product/1,/product/1/2(可匹配多位也能没有) | { slug: undefined },{ slug:[1] },{ slug: [1, 2] } |
Next中路由插槽:官方文档
路由插槽是Next中实现并行路由的方式,可以在一个页面布局中同时渲染多个独立的路由区域
文件夹命令以@开头,在layout文件中作为属性传入
刷新时需要加载路由插槽的default.tsx文件,如果没有的话Next会渲染出一个404页面。路由插槽的一级文件不必有page和layout文件,因为路由插槽的文件命名不会参与路由路径的组成,当插槽路由匹配不到对应的路由的时候就会默认渲染一个default,所以正常default返回null即可。
Next中路由拦截:官方文档
路由拦截是Next基于文件系统自己实现的一种功能,其效果是路由切换后实现页面覆盖效果而非切换不同的上下文。
写法 | 文件层级 |
---|---|
(.) | 与需要拦截的目标路由处于同一个层级 |
(…) | 比需要拦截的目标路由低一个层级 |
(…)(…) | 比需要拦截的目标路由低两个层级 |
(…) | 全局拦截 |
官方的解释有点抽象。我个人的理解是首先要根据路由加载的链路,未在加载范围内的路由配置的拦截不会生效。
下面的目录结构中根目录有一个@modal,test目录中也存在一个@modal。
app/@modal/(.)phone路由拦截会拦截同一层级【与注入modal插槽路由的layout同层级的路由文件,即/phone/:id】。
当我们打开/test/intercept 这个路由路径的时候,test下的layout会加载test下的@modal插槽。此时又存在了两个路由拦截,app/test/@modal/(.)phone 和app/test/@modal/(…)phone。前者拦截同一层级的路由【与注入modal插槽路由的layout同层级的路由文件,即/test/phone/:id】;后者可以比需要拦截的目标路由文件低一个文件夹层级【反过来说就是可以拦截加载modal的layout上一个层级的路由,即/phone/:id】
[your-project-name]
└── app
├── phone
│ └── [id]
│ └── page.tsx
├── @modal
│ └── (.)phone
│ └── [id]
│ └── page.tsx
├── test
│ ├── @modal
│ │ ├── (.)phone
│ │ │ └── [id]
│ │ │ └── page.tsx
│ │ └── (..)phone
│ │ └── [id]
│ │ └── page.tsx
│ ├── intercept (/test/intercept 跳转 /test/phone/:id 和 /phone/:id 路由页面)
│ │ └── page.tsx
│ ├── phone
│ │ ├── [id]
│ │ └── page.tsx
│ └── layout.tsx (modal 插槽布局)
└── layout.tsx (modal 插槽布局)
结合上面的项目目录可以看到 intercept中跳转 /test/phone/:id 会被 /test/@modal/(.)phone拦截到。而跳转/phone/:id 则会被 两个拦截规则拦截到,即/@modal/(.)phone和/test/@modal/(…)phone。目前我的理解是根据目标路由的最近原则进行匹配,来从外到内的查找拦截路由。
第一组存在app/@modal/(.)phone拦截规则的时候
第二组不存在app/@modal/(.)phone拦截规则的时候
路由拦截和路由插槽组合使用构成模态框。效果就是软导航(Link)的时候会在当前页面加载一个路由插槽,插槽中的内容是路由拦截文件渲染的内容。但是硬导航(刷新页面)会展示未被拦截的页面内容,一个完整的路由页面
[your-project-name]
└── app
├── @model (路由插槽)
│ └── (.)photo (路由拦截)
│ ├── [id] (动态路由)
│ │ └── page.tsx
│ └── default.tsx
├── photo
│ └── [id] (动态路由)
│ └── page.tsx
├── layout.tsx (model插入布局中)
└── page.tsx
下面是软访问和硬访问的两种页面效果
软访问是点击红色区域的数字触发的router.push的跳转。硬访问是直接刷新浏览器页面。