前言
next是一款用JS开发的全栈框架,它是基于express框架基础上开发而成,可以用react写客户端,node.js写服务端。一份代码可在前后端同时运行,这在next中称之为同构!
一些next.js框架基础介绍
创建项目:npm init next-app 项目名,项目创建好后next会帮你搭好基础通用的模板,大多常用的api以及写法都能在模板中找到。
自定义head:使用<Head>组件可自定义<title><meta>标签和内容组件导入。
// 文件路径 page/_app.js
import Head from "next/head";
import '../styles/globals.scss'
export default function App({ Component, pageProps }) {
return <>
<Head>
<title>我的博客 A-Tione</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"/>
</Head>
<Component {...pageProps} />
</>
}
快速导航:标准写法<Link href=xxx><a>xxx</a></Link>,在next中通过<Link>标签的href链接定位路由可实现预加载路由页面,可使点击跳转无需发送请求。
同构代码:一份代码两端运行,在一处使用console.log调试,可在服务端和客户端页面都显示出log,这样更方便调试。但请注意同构代码时需要使用前后端都存在的对象,比如window、document等客户端才有的对象就无法在服务端log出来。
局部css:因为next是与react配套使用的,我们可用这样写css:<style jsx> 或者 xxx.module.css文件。
全局组件:若需要多处地方使用到同一组件,可使用全局组件,全局组件在路径page/_app.js中声明。
Next.js API
首先在项目中的api目录中创建文件:/api/v1/posts,文件路径即API路径。
posts.tsx默认导出函数的类型为:NextApiHandler。该文件代码仅在node.js里运行,不运行在浏览器中。
/pages/api/v1/posts.tsx:
import {NextApiHandler} from 'next';
import {getPosts} from 'lib/posts';
const Posts: NextApiHandler = async (req, res) => {
const posts = await getPosts();
res.statusCode = 200;
res.setHeader('Content-type', 'application/json');
res.write(JSON.stringify(posts));
res.end();
};
export default Posts;
lib/psots:
export const getPosts = async () => {
const fileNames = await fsPromise.readdir(markdownDir);
return fileNames.map(fileName => {
const fullPath = path.join(markdownDir, fileName);
const id = fileName.replace(/\.md$/g, '');
const text = fs.readFileSync(fullPath, 'utf-8');
const {data: {title, date}, content} = matter(text);
return {
id, title, date
};
});
};
总结:
- /API/里的文件是API,通常约定返回JSON格式,当然也可以返回HTML格式(res.send('<h1>xxx</h1>'))。
- API文件默认导出NextAPihandler:这是一个函数类型,第一个参数是请求第二个参数是对象,也可以加入next() 做中间件(文档)。
Next.js 三种渲染
- 客户端渲染:只在浏览器上执行的渲染
- 静态页面生成(SSG):Static Stie Generation,解决白屏问题、SEO问题。但无法生成用户相关内容(所以用户请求的结果都相同)。
- 服务端渲染(SSR):解决白屏问题、SEO问题。并且可以生成与用户相关的请求内容(不同用户结果不同)。
- 注意:SSR与SSG都属于预渲染Pre-rendering。
三种渲染方式分别对应:
- 客户端渲染:用JS、Vue、React创建HTML
- SSG:页面静态化,把PHP提前渲染成HTML
- SSR:是PHP、Python、Ruby、Java后台的基本功能
注意:Next.js的预渲染可以与前端React无缝对接。
客户端渲染(BSR)
指用浏览器JS创建的HTML代码。
在next中举例:
前端:
import {NextPage} from 'next';
import {usePosts} from '../../hooks/usePosts';
const PostsIndex: NextPage = () => {
const {posts, isLoading, isEmpty} = usePosts();
return (
<div>
<h1>文章列表</h1>
{isLoading ? <div>加载中</div> :
isEmpty ? <div>没有文章</div> :
posts.map(p => <div key={p.id}>
{p.id}
</div>)
}
</div>
);
};
export default PostsIndex;
后端:
import {useEffect, useState} from 'react';
import axios from 'axios';
export const usePosts = () => {
const [posts, setPosts] = useState<Post[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isEmpty, setIsEmpty] = useState(false);
useEffect(() => {
setIsLoading(true);
axios.get('api/v1/posts').then(response => {
setPosts(response.data);
setIsLoading(false);
if (response.data.length === 0) {
setIsEmpty(true);
}
}, () => {
setIsLoading(false);
});
}, []);
return {posts, setPosts, isLoading, setIsLoading, isEmpty, setIsEmpty};
};
文章列表完全前端渲染的,可称之为客户端渲染。
一般来说,静态内容在代码里写死的,动态内容是来自数据库的。
在next中,图上的静态内容会在服务器渲染一次,客户端再渲染一次,为什么?
在React SSR官方文档中提到:推荐后端使用renderToString(),在前端hydrate()。前端hydrate()混合指的是会保留HTML并附上事件监听,也就是说后端渲染HTML,前端添加监听,前端也会渲染一次来保证前后端渲染结果一致。next框架已经帮我们做好了这一步。
客户端渲染的缺点:
白屏:在ajax得到响应之前,页面中之后Loading。
SEO不友好:搜索引擎访问页面,看不到posts数据,因为搜索引擎默认不会执行JS,只能看到HTML。
静态页面生成(SSG)
前提:
如果每个人都请求一个相同的资源,比如都请求相同的文章列表,那还需要在每个人的浏览器上渲染一次吗?直觉告诉我们是不是大可不必,可以直接在后端渲染好,然后每个人直接读取后端传来的内容。
n次渲染变成了一次渲染,n次客户端渲染变成了1次静态页面生成。这个过程叫做动态内容静态化。
如何做SSG:
那么后端渲染还需要通过ajax来获取渲染内容么?也可以,axios支持服务端使用,但是这样有点傻,资源就在服务端为什么还需要绕远路请求ajax来获取一次资源呢?
我们可以在服务端这样写:
通过getStaticProps获取内容。声明位置:每个page不是默认导出一个函数么,把getStaticProps声明在这个函数旁边即可,默认export导出。
getStaticProps:
export default function PostsIndex = (props)=> { ... }
默认导出的函数的第一个参数就是props。
通过同构SSR,前端也可以不用ajax就能拿到数据了,这就是同构SSR的好处:后端数据可以直接传给前端,然后前端JSON.parse一下就能得到了posts(next框架已经帮我们做了parse)。
PHP/java/Pyton 能不能做?
思路是一样的,他们也能做,但是他们不支持jsx,不好与React无缝对接,而且这些语言的对象不能直接提供给JS用,需要类型转换。
SSG静态化的时机:
- 开发环境:在开发环境每次请求都会运行一次getStaticProps,这是为了方便修改代码时重新运行。
- 生产环境:getStaticProps只在build时运行一次,这样可以提供一份html给所有用户下载。
SSG静态化的优点:
- 生产环境中直接给出完整页面
- 首屏不会白屏
- 搜索引擎能看到页面内容,方便SEO
服务端渲染(SSR)
前提:
如果是与用户相关的动态内容,较难提前静态化,需要在用户请求时,获取用户信息,然后通过用户信息去数据库拿数据。有时候这些数据更新极快,无法提前静态化,比如微博首页。
使用SSR:
这些更新极快的内容我们可以
- 客户端渲染,下拉更新
- 服务端渲染,下拉更新
但这次的服务端渲染不能用getStaticProps,因为getStaticProps是在build时执行的,可用getServerSideProps(context: NextPageContent)。
getServerSideProps:
运行时机:无论是开发环境还是生产环境都是在请求到来之后运行getServerSideProps。回顾getStaticProps,它只在生产环境build时运行一次。
参数:context,类型为NextPageContent。content.req / context.res 可以获取请求和响应,一般只需要用到context.req。
例子:
const index: NextPage<Props> = (props) => {
const {browser, xxx} = props;
const [width, setWidth] = useState(0);
console.log(xxx, 'xxx ');
useEffect(() => {
const w = document.documentElement.clientWidth;
setWidth(w);
}, []);
return (
<div>
<h1>{xxx}</h1>
<h1>你的浏览器是 {browser.name}</h1>
<h2>你的浏览器窗口大小是 {width} 像素</h2>
</div>
);
};
export default index;
export const getServerSideProps: GetServerSideProps = async (context) => {
const ua = context.req.headers['user-agent'];
const result = new UAParser(ua).getResult();
return {
props: {
browser: result.browser,
xxx: 'xxx'
}
};
};
SSG的缺点:
SSR无法获取客户端信息,比如浏览器大小。必须要用户通过客户端实际登录发送具体请求后才能知道客户端的信息,仅通过用户信息是无法得知具体的客户端信息。
总结
静态内容:直接输出HTML,没有术语。
动态内容:术语:客户端渲染,通过ajax请求,渲染成HTML。
动态内容静态化:术语:SSG,通过getStaticProps获取用户无关内容。
用户相关动态内容静态化:术语:SSR,通过getServerSideProps获取请求。缺点无法获取客户端信息,比如浏览器大小。
流程图: