Next.js 是一个基于 React 的综合性服务端网页渲染框架。
像 Umi、Antd-Pro 应用场景为 管理后台,而 Next.js 则强调前台页面,或者说是 完整的网站应用。
Next.js 英文官网:https://nextjs.org/
Next.js 中文站点(非官方):https://www.nextjs.cn/
Next.js 的官方文档写的非常好,直接看官方文档很容易上手 Next.js。
当前 Next.js 最新版本为 13.0.5。
本文以下内容仅为自己在初学 Next.js 时的一些记录。
假设我们想创建的 Next.js 项目名为 my-next-app,那么执行下面命令:
npx create-next-app my-next-app
或者是
yarn create next-app my-next-app
这个命令几乎和 create-react-app 是一样的,只不过是把其中的 react 替换成了 next
项目使用 TypeScript
如果项目打算使用 TypeScript,那么只需在创建的命令中加入参数 --typescript
,即:
yarn create next-app my-next-app --typescript
是否使用 ESLint ?
当执行命令后,该脚手架中间会有一次询问:是否使用 ESLint?
可根据自己需求来决定,默认为 Yes。
如果你只是最基础的 Next.js 项目,那么默认只需安装:react、react-dom、next
如果你使用 TypeScript,那么额外需安装:typescript、@types/react、@types/node、@types/react-dom
如果你使用 ESLint,那么额外需安装:eslint、eslint-config-next
若安装卡顿可切换成淘宝源
yarn config set registry 'https://registry.npm.taobao.org'
默认创建好的项目内容都存放在 根目录下的 pages 中。
结合实际情况,我个人建议是:
- 项目根目录下创建 src 目录
- 将 pages 移动到 src 里
- 在 src 下创建其他各种目录,例如 hooks、utils、components 等
tsconfig.json
将默认的编译目标由 ES5
修改为 esnext
。
配置路径映射(alias)
Next.js 框架自带支持路径映射,需要做的就是直接在 tsconfig.json 文件中增加即可。
例如:
{
"compilerOptions": {
"target": "esnext",
+ "baseUrl": ".",
+ "paths": {
+ "@/components/*": ["src/components/*"],
+ "@/geometrys/*": ["src/geometrys/*"],
+ "@/hooks/*": ["src/hooks/*"],
+ "@/shaders/*": ["src/shaders/*"],
+ "@/utils/*": ["src/utils/*"]
+ }
}
}
如果你项目没有使用 TypeScript,那你可能需要修改的文件是 jsconfig.json
next.config.js
可以在此文件中增加对 webpack 的一些配置。
例如,增加 .wgsl 文件的处理规则
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
+ webpack: (config, options) => {
+ config.module.rules.push({
+ test: /\.wgsl$/,
+ type: "asset/source"
+ })
+ return config
+ }
}
module.exports = nextConfig
.vscode/settings.json
在根目录创建 ./vscode/settings.json
文件,可以增加一些自己的 VSCode 设置。
我个人比较习惯增加一些配置:
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": [
"javascript",
"html"
],
"eslint.options": {
"extensions": [
".js",
".jsx",
".ts",
".tsx"
]
},
"editor.detectIndentation": false,
"editor.tabSize": 4,
"javascript.format.insertSpaceBeforeFunctionParenthesis": true,
"vscode-edge-devtools.webhint": false
}
页面结构
我们看一下一个常规的 Next 页面代码结构:
import Head from 'next/head'
export default function Home() {
return (
<div>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>...</main>
<footer>...</footer>
</div>
}
Next 与 React 页面开发的相同之处:
- 都支持 函数组件
- 都是在 return 对象中编写具体内容
- return 对象的最外成都需要一个独立的标签
Next 与 React 页面开发的不同之处:
React: 我们编写的组件(.tsx) 仅为页面 body 中某 div 对应的内容
react 项目中最终呈现的 html 是由 /public/index.html 加上我们编写的组件共同构成
Next: 我们编写的组件(.tsx) 还包含 头部信息标签 <head>
next 项目中每一个页面都可以独立定义自己的
<head>
标签内容在 .tsx 中 return 的内容需要包含
<Head>
标签,最终会转化为 html 中的<head>
标签
请注意,在 Next 中是没有
<Body>
标签的
结论:Next 把每个页面的 <head>
标签控制权也交给了我们。
但是,这也变相要求我们需要对每一个页面都增加上 <Head>
标签。
实际开发中,为了简化一些组件共同的 <Head>
标签,我们可以对页面进行简单改造。
假定我们实际开发中,有以下需求:
- 不同页面上
<head>
标签主要差异为<title>
的值,其他相同 - 不同页面上都有一些相同的模块组件,例如都需要一个
<PageList>
那么我们可以创建一个 基础页面组件(BasePage),用来做为每一个页面的基础框架。
实现思路:
- 我们把不同页面的 标题文字(string) 作为一个 参数 title 传递进来
- 我们把不同页面的 具体内容(ReactNode) 作为 BasePage 的 children 传递进来
BasePage.tsx
import { ReactNode } from 'react'
import Head from 'next/head'
import PageList from '@/components/page-list'
interface BasePageProps {
title?: string
children?: ReactNode
}
const BasePage = ({ title = 'Hello WebGPU', children = [] }: BasePageProps) => {
return (
<div className='container'>
<Head>
<title>{title}</title>
<meta name="description" content="Next.js Samples" />
<link rel="icon" href="/favicon.ico" />
</Head>
<PageList />
<main className='main'>
{
children
}
</main>
</div>
)
}
export default BasePage
有了 BasePage 组件以后,我们再想创建具体的页面,例如 XxxPage,我们就可以这样写。
XxxPage.tsx
const XxxPage: FC = () => {
const [xx,setXx] = useState()
useEffect(()=>{ ... },[])
return (
<BasePage title='Hello Next.js'>
<div> .... </div>
<div> .... </div>
</BasePage>
)
}
提醒:对于
<BasePage>
标签内部,就好像普通的网页组件一样编写内容即可。
在 Next.js 组件中,可以通过 useRouter()
获取当前路由。
const router = useRouter()
console.log(router.route) //当前页面的路由(除域名端口外的url)
在上面页面改造时,我们提到了不同页面共同使用到的一个组件 <PageList>
。
实际上在它的内部,就可以通过获取当前路由进行相关动态变化。
特别强调一下:
由于 Next.js 默认是开启 SSR 的,这意味着你直接写的 JS 代码实际上并不是直接运行在浏览器环境中的。
你可以理解为是运行在 Node.js 环境中的,也可以理解为 它仅仅是代码片段。
由于不是浏览器环境,造成一个直接结果就是:你直接编写的 JS 中不存在 window 和 document 对象,当然 useEffect() 中代码是个例外,在 useEffect() 中的 JS 是存在 window 和 document 对象的。
解决方案:
假设你的 JS 代码中需要访问 window 和 document 对象,那么你有 2 个选择:
-
把相关代码在 useEffect() 中
实际中,你可以单独编写一个 hook 函数,内部通过 useEffect() 来对外提供某些 window/document 的属性值
-
动态引入并且禁用 SSR
换句话说,就是不再让你写的 JS 当做代码片段,而是以 动态导入 的形式来引入到网页(浏览器环境)中。
动态引入与禁用SSR:
这部分知识点,位于 Next.js 文档中的:高级特性(Advanced Features) > 动态导入(Dynamic Import)
中文文档:https://nextjs.org/docs/advanced-features/dynamic-import
英文文档:https://www.nextjs.cn/docs/advanced-features/dynamic-import
假定你编写了一个组件 hello.tsx,那么动态引入该组件的方式为:
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello'),
{ ssr: false }
)
function Home() {
return (
<div>
<Header />
<DynamicComponentWithNoSSR />
<p>HOME PAGE is here!</p>
</div>
)
}
上述代码中通过定义一个名为
DynamicComponentWithNoSSR
的组件,将 hello.tsx 作为它的动态实际内容引入使用。你可以把
DynamicComponentWithNoSSR
看作是一个占位组件
。同时第 2 个参数中明确配置
ssr:false
,这样意味着 hello.tsx 组件是运行在浏览器环境中了,它的内部就可以随心所欲使用 window/document 对象了。
解决找不到 window/document 对象,究竟采用哪种方式,是把代码都写到 useEffect() 中,还是 动态引入禁用 SSR,需要根据实际情况来决定。
Next.js 框架非常灵活(页面、组件、路由、SSR),如果想深入学习,需要看大量别人写的代码(套路),里面有非常多的奇淫技巧。