一、Next.js的介绍
1.1、使用React同构构建完整的web项目应用
1.1.1、 React同构设计的技术点
- css方案、路由方案、数据获取方案。。。
- 使用打包工具(如webpack,rollup等等)将代码进行打包
- 使用诸如Babel之类的编译器进行代码装换
- 生产环境代码优化
- 静态预先渲染、服务端渲染、客服端渲染
2.2 Next.s 与 Create React App Cli有什么异同
Next.js vs Create React App
- 有了Create React App, 为什么有出现了Next.js ?
- Create React App 与 next.js有什么不同
2.2.1 相同点
- 勇于快速构建React应用程序
- 体积轻薄
- 自动的代码拆分
- 开箱即用(零配置),同事支持自定义
- 支持所有现代浏览器环境
- 良好的开发体验,广泛应用于生产
2.2.2 不同点
Create React App
- 构建单页面应用(SPA)
- 需要自行引入路由方案
- 无法处理任何后端逻辑
Next.js
- 基于Node构建静态/服务端渲染应用
- 开箱即用的样式&路由方案
- 新加入的API Routes特性,提供了一种构建PI的解决方案
2、Next.js功能与特性
2.2.1、next.js功能
- 路由: 基于“pages”的静态/动态路由
- 数据获取: 正对不同场景的数据获取API
- css支持: 全局样式、组件级样式、CSS-in-JS
- 静态文件服务: 提供图片、文本、甚至html等静态文件支持
- TypeScript: 集成类似IDE的TS体验,开箱即用
- 环境变量: 内置对于环境变量的支持,并能导出至浏览器环境
2.2.2、next.js路由
- 静态路由: ‘pages/index.js’ -> '/'
- 嵌套静态路由: ‘pages/a/b.js’ -> '/a/b'
- 动态路由: ‘pages/books/[id].js’ -> '/books/1', '/books/2'...
- 动态全匹配路由: ‘pages/books/[...all].js’ -> '/books/1/1','/books/1/2'...
路由匹配的优先级是: 静态路由 > 嵌套静态路由 > 动态路由 > 动态全匹配路由
2.2.3 数据获取
预渲染
- 场景: 静态生成(请求前就知道的页面) &服务端渲染(请求时才知道的页面) 对应使用以下不同API
- API: getStaticProps、getServerSideProps、getStaticPaths、getInitailProps
2.2.4 css支持
- 全局样式
- 组件级样式
- Sass
- Less & Stylus: @zeit/next-less & @zeit/next-stylus
- CSS-in-JS: styled-jsx
2.2.5 静态文件服务
- 基于/public, 此路径下的静态文件,通过‘/',路径访问
2.2.6 TypeScript
- Next.js提供开箱即用的TS支持
- 创建tsconfig.json, Next为你提供默认的配置
- 此外,可扩展自定义配置
2.2.7 环境变量
- ‘.env.local’ -> 'process.env'
- 浏览器端获取: 变量前追加‘NEXT_PUBLIC_’
3、Next.js与周边生态
- 状态管理: 与redux、Mobx等高校接入
- 数据请求: 与GraphQL等工具配合使用
- 多端开发:与Elextron等框架的完美配合
- 静态站点: 与Agility CMS、 Wordpress等静态站框架协同助力
二、Next.js项目搭建
2.1 、如何初始化Next.js项目
2.1.1 常见方式总览
- 手动: 从零开始引入依赖进行构建
- 脚手架: 使用create-next-app(甚至可以借助模板)
2.1.2 手动
# 创建项目目录
mkdir nextjs_demo
# 进入项目目录
cd nextjs_demo
# 根路径下创建package.json文件
npm init -y
# 安装依赖
npm install --save next react react-dom
项目的结构
接下来在package.json中手动添加3个脚本
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "next",
"build": "next build",
"start": "next start"
},
然后创建pages的目录, 接着创建index.js, 创建第一个react组价
import React from 'react'
const Home = () => (
<main>
<h1>Hello Next.js</h1>
</main>
)
export default Home
yarn dev 启动项目,可以看见,已经是3000端口启动成功了
2.1.3 自动, create-next-app 脚手架创建项目
next.js 官网: nextjs.org/learn/basic…
脚手架 + 模板创建
npx create-next-app nextjs-demo2 --use-npm --example "https://github.com/vercel/next-learn/tree/master/basics/learn-starter"
2.1.4 自动, create-next-app 脚手架
# 这个的项目目录结构最完整
npx create-next-app
pages下:
页面/API接口
publick下:
静态文件
next.config.js
自定义配置
三、Next.js路由
- Next.js 提供的两种预渲染形式
- 基于文件系统的静态/动态路由
- next/link和next/router的使用
- Shallow Routing
3.1 Next.js 预渲染
nextjs提供: 静态生产 & 服务端渲染
3.1.1 静态生成(Staic Generation)
- 页面生产时机: 构建时(build time )
- 优势: CDN缓存
- 数据: 不包含数据
包含数据(getStaicPaths + getStaticProps)
- 场景
- 营销页面
- 博客和文章
- 帮助和文档
3.1.2 服务端渲染(Server-side Rendering)
- 页面生成时机: 请求时(request time)
- 优势: 驳斥数据更新(每次访问都是最新的数据)
- 数据:getServerSideProps
- 场景: 因请求不同而数据不同的页面
3.1.3 静态路由与动态路由
- 静态路由: 我们在pages/book/index.js, 创建一个新页面
pages/inex.js => '/'
pages/book/index.js => '/book'
新页面创建好了,路由会根据目录的名称,自动屁屁额
嵌套路由 我们写个深点的路径,也是可以识别到
- 动态路由: 我们需要通过url传一些参数(params)的时候,就需要动态路由了
pages/book/[id].js -> '/book/id' (/book/123)
pages/[book]/price.js -> /:book/price (/helloWorld/price)
pages/book/[id]/[detail].js -> '/book/:id/:detail' (/book/123/a-detail)
最后的参数
pages/book/[id].js -> '/book/id' (/book/123)
/book/321,任意:id都会匹配book/[id].js, 而不是去找index.js
目录是动态的
pages/[book]/price.js -> /:book/price (/helloWorld/price)
任意的目录都能够匹配动态路由,除了book,因为有个固定的book目录,动态的【book】优先级会低于动态的book
动态目录 和 动态文件
总结: 带【】的目录和js都理解为模糊匹配就好了
全匹配路由
pages/course/[...params].js-》 ‘/course/"params’
(匹配: /course/123 /course/detail/123 /course/123/chapter/1 )
[...params]不管后面多少级,都可以匹配到
3.1.4 路由匹配优先级?
- 静态路由 > 动态路由
- 动态路由 > 全匹配路由
总结: 越模糊的优先级越低
3.1.5 Link 路由跳转
import Link from 'next/link'
export default function Home() {
return (
<main>
<h1>首页</h1>
<ul>
<li>
<Link href={'/book/newBook/goodBook.js'}>
<a>/book/newBook/goodBook.js</a>
</Link>
</li>
<li>
<Link href={'/book'}>
<a>bookjs</a>
</Link>
</li>
</ul>
</main>
)
}
3.1.6 动态路由跳转
[if] 占位表示需要模糊匹配的占位符, as是真正跳的属性
<li>
<Link href={'/book/[if]'} as={'/book/123'}>
<a>/book/[id].js</a>
</Link>
</li>
3.1.7 自定义路由跳转组件
在componetns下新建一个CustomeLink组件
import React from 'react'
const CustomeLink = React.forwardRef(({ onClick, href }, ref) => {
return (
<a href={href} ref={ref} style={{ color: 'red' }}>
自定义函数子组件: /book/one
</a>
)
})
export default CustomeLink
通过forwardRef构建,href和ref向下传递,在调用组件时,在组件上写passHref传递点击的事件和属性
<li>
<Link href={'/book/[if]'} as={'/book/123'} passHref>
<CustomeLink></CustomeLink>
</Link>
</li>
以上两种动态路由跳转的方式效果如下:
3.1.8 query带参数跳转
<li>
<Link href={{ pathname: '/book', query: { year: '2022' } }}>
<a>URL带参数跳转 /book/newBook/goodBook.js?year=2022</a>
</Link>
</li>
3.1.9 replace不记录路由跳转
<li>
<Link href={'/book/newBook/goodBook.js'} replace>
<a>/book/newBook/goodBook.js</a>
</Link>
</li>
3.1.9 Link除了a标签,button标签等带Onclick的都可以跳
底层不是像a标签一样通过href去跳,而是,传递onClick
<Link href={'/book/newBook/goodBook.js'}>
<button>/book/newBook/goodBook.js</button>
</Link>
3.2 next/router 非声明式路由
import Router from 'next/router'
import {useRouter} from 'next/router'
import {withRouter} from 'next/router'
3.2.1 Router API
push
replace
back
relaod
prefetch
beforePopState
events
- routeChangeStart
- routeChangeComplete
- routeChangeError
- push as 动态路由用 Router.push(url,as,options) 在pages下新建routerApi.js页面
import Router from 'next/router'
export default function RouterApi() {
return (
<main>
<h1>RouterApi</h1>
<ul>
{/* push */}
<li>
<button
onClick={() => {
Router.push('/book')
}}
>
/book
</button>
</li>
</ul>
</main>
)
}
可以正常跳转
push动态路由
<li>
{/* 动态 */}
<button
onClick={() => {
Router.push('/book/[id]', '/book/123')
}}
>
/book/[id]
</button>
</li>
{/* 动态 带参数 */}
<li>
<button
onClick={() => {
Router.push({ pathname: '/book/456', query: { userId: 666 } })
}}
>
/bookid 带参数
</button>
</li>
// 不记录路由替换
<button onClick={() => Router.replace('/book')}replace /book</button>
// 返回
<button onClick={() => Router.back()}replace /book</button>
// 重新加载
<button onClick={() => Router.replace('/book')}replace /book</button>
preFetch
- 此方法只有在没有Link组件做路由跳转时生效(Link组件默认完成prefetch)
- 此方法值在生产环境有效 预加载链接,增强体验,也加重了服务器压力
import Router from 'next/router'
Router.prefetch(url,as)
3.2.2 beforePopState
路由popup事件前被调用
Router.beforePopState(cb: () => boolean)
// 返回不是’/book‘页面阻止逻辑
import { useEffect } from 'react'
export default function RouterApi() {
// beforepopState
useEffect(() => {
Router.beforePopState((url) => {
if (url !== 'book') {
alert('url not allowed')
return false
}
return true
})
}, [])
此外还有一些关于路由的事件钩子可以用
-
routeChangeStart(url): 路由开始改变时触发
-
routeCHnageComplete(url): 路由变化完成时触发
-
routeChangeError(err,url): 路由跳转发生错误或被取消时触发
-
beforeHistoryChange(url): 路由变化前触发
-
hashChangeStart(url): 页面不变,哈希变化时触发
-
hashChangeComplete(url): 页面不变,哈希辩护完成触发
-
例如第一个时间
// router events
useEffect(() => {
const handleRouterChange = (url) => {
alert(`App is change to : ${url}`)
}
Router.events.on('routeChangeStart', handleRouterChange)
return () => {
Router.events.off('routeChangeStart', handleRouterChange)
}
}, [])
注意需要在useEffect或didMounted中使用
3.2.3 useRouter & withRouter
用法一样,获取的方式写法不同
# useRouter
import {useRouter} from 'next/router'
const router = useRouter()
# withRouter
import {withRouter} from 'next/router'
const Home = ({router}) = > {
// todo
}
export default withRouter(Home)
3.2.4 Shallow Routing
就是改变url时,不触发数据获取方法 只适合同一页面的路由更改,reload这种
Router.push('book',undefined,{shallow: true})
3.3 API路由
3.3.1 API路由的基本概念
可以理解为提供node server 的接口服务
使用Next.js 构建自己的API提供的一种简单的解决方案
- pages/api 目录下的任何文件都将作为API路由映射到/api/*
api路由
export default (req,res) => {
...
}
export default(req,res) => {
res.statusCode = 200
res.setHeader('Content-type','application/json')
res.end(JSON.stringify({course: 123}))
}
// 等价与
export default (req,res) => {
res.status(200).json({course: 123})
}
3.2.2 API请求方式
- GET与POST
export default (req, res) => {
if (req.method === 'POST') {
res.status(200).json({ method: 'POST!!' })
} else if (req.method === 'GET') {
res.status(200).json({ method: 'GET!!' })
} else {
res.status(200).json({ method: 'others' })
}
}
export default function getData() {
const post = () => {
fetch('http://localhost:3000/api/methods', { method: 'POST' })
.then((res) => res.json())
.then((json) => console.log(`post`, json))
}
return (
<main>
<h1>getData</h1>
<button onClick={post}> postData</button>
</main>
)
}
3.3.3 动态API路由
与动态页面路由遵循相同的文件命名规则
pages/api/post/[xxx].js
创建 pages/api/course/[id].js
export default (req, res) => {
const {
query: { id },
} = req
res.status(200).json({ id })
}
- 🌰:场景
GET api/courses/ 列表(静态)
GET api/courses/123 详情(动态)
GET api/courses/[:id] 仅有动态不会生效
全捕获路由
pages/api/course/[...slug].js
3.3.4 API路由优先级
静态API路由优先于动态API路由
动态API路由有限与捕获所有API的路由
3.3.5 API路由使用细节
req:
cookies
query
body
res:
status
json
send
redirect([status],path)
四、Next.js 配置
4.1 next.config.js
- 位置 根路径 ./next.config.js (package.json旁边)
- 不编译 不会被Webpack,Babel or TypeScript编译
- js文件 不支持josn,只能用用你的Node版本支持的js特性
- 使用阶段 Next Server端, Build构建过程中,不会在Client端被使用
4.2 配置导出
module.exports = {
/ config.../
}
module.exports = (
phase,
{
defaultConfig
}) => {
return {
/ config.../
}
}
)
4.3 环境变量
module.exports = {
env: {
customKey: 'my-value'
}
}
// pages/page.js
function Page(){
return <h1>The value of customKey is: {process.env.customKey}</h1>
}
4.4 BasePath
module.exports = {
basePath: '/docs'
}
注意会自动补充路由前缀/docs
4.5 assetPrefix
cdn加速用, 不支持static下的
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
assetPrefix: isPro ? 'https://cdn.mydomain.com' : ''
}
static下用的
4.6 自定义Webpack Config
4.7 distDir
module.exports = {
distDir: 'build'
}
4.8 Next.js框架内置功能
4.8.1动态import
支持SSR
代码分割
4.8.2 动态import的loading
4.8.3 配置不开启SSR
4.9 浏览器兼容支持
1. >= IE11
2. 现代浏览器基本都兼容(chrome,火狐,苹果)
5.0 JavaScript语言支持
ES6基本都支持

5.1 Polyfills
5.2 postcss
5.3 路劲别名alias
5.4 路径别名paths
5.5 环境变量
在client端也有效
5.6 其他配置
五、next.js的css支持
5.1 global CSS
- 作用域 全局作用域,整个HTML文档
- 引入位置 pages/_app.js
- 文件后缀: *.css, eg.style.css
- 库CSS 支持来自node_modules的css文件
5.2 组件级CSSModule
- 作用域: 局部作用域
- 引入位置 【name].module.css
5.3 预处理器Sass
- 引入位置: 任何位置
- 全局作用域 *.scss, *.sass
- 局部作用域 *.module.scss, *.module.sass
npm installl sass
# next.config.js
const path = require('path')
module.exports = {
sassOptions: {
includePaths: [
path.join(__dirname, 'styles')
]
}
}
5.4 less & stylus
依赖插件
less: @zeit/next-less
stylus @zeit/next-stylus
后缀
.less
.stylus
5.5 styled-in-js
5.6 styled-jsx
5.7 postCss
六、 Next.js的根组件 & Document组件
项目目录
6.1 _app.js 是最高层级的组件
// 全局的css
import '../styles/globals.css'
// Component 当前路由组件
// pageProps 含有已请求数据的对象
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
6.2 index.js 是根路由 / 页面
<h1>hello next</h1>
- 🌰:实现header - container -footer的经典布局
# components/Layout.js
import s from './Layout.module.css'
export default function Layout({ children }) {
return (
<main className={s.container}>
<header>head</header>
<section>{children}</section>
<footer>footer</footer>
</main>
)
}
# components/Layout.module.css
.container {
height: 100vh;
width: 100vw;
display: flex;
flex-direction: column;
}
.container header {
height: 100px;
background: lightskyblue;
}
.container footer {
height: 100px;
background: lightpink;
}
.container section {
flex: 1;
}
# 修改_app.js
// 全局的css
import '../styles/globals.css'
import Layout from '../components/Layout'
// Component 当前路由组件
// pageProps 含有已请求数据的对象
function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
)
}
export default MyApp
- 效果