简单认识一下React框架nextJs
。它可以配置静态及服务器端融合渲染、支持 TypeScript、智能化打包、路由预取等功能 无需任何配置。
1、下载安装
下载及安装看文档:https://www.nextjs.cn/docs/getting-started
2、文件目录
一些nextJs
会用到或者重要的文件夹及作用。
.next
:nextJs根据代码生成的目录文件,用于提供服务pages
: 是存放页面文件的地方,这个文件夹里面放的每个js文件都可以通过路由访问,例如pages/about.js
,可以通过'/about
来访问这个页面的内容。components
:存放公用组件的地方lib
:存放插件及其他东西static
:存放静态资源,比如图片
3、路由
路由会通过pages
目录自动生成对应的路由表。
路由跳转使用nextJs提供的Link
组件。它本身不会渲染出任何标签,所以必须传入一个可以渲染出来内容的组件且只能传入一个子节点
// pages/index.js
import Link from 'next/link';
export default ()=>{
return <div>
<span>22</span>
<Link href="/b">点击跳转</Link>
</div>
}
// pages/b.js
export default ()=>{
return <div>
<span>bbbbbbbbbbbbbb</span>
</div>
}
不想用标签也可以使用router
进行跳转。
import Router from 'next/router';
export default ()=>{
const toIndex = ()=>{
Router.push('/')
}
return <div>
<span onClick={toIndex}>bbbbbbbbbbbbbb</span>
</div>
}
路由想传参的话可以使用?id=2
这种。这种方式显示在浏览器上面b?id=2
,如果想调整为b/2
可以使用link
的as
属性,路由映射
功能。
<Link href="/b?id=2" as="/b/2">点击跳转</Link>
// router方式
Router.push({
pathname: '/b',
query: {
id: 2
}
}, '/b/2')
使用路由映射功能,页面刷新会报404,vue&react的history模式
同款问题,后台配置以下对应的路径匹配就好了。
// server.js
const Koa = require('koa');
const next = require('next');
const Router = require('koa-router');
const dev = process.env.NODE_ENV != 'production';
const app = next({dev});
const handle = app.getRequestHandler();
app.prepare().then(()=>{
const server = new Koa();
const router = new Router();
// 匹配路径
router.get('/b/:id', async (ctx)=>{
const id = ctx.params.id;
await handle(ctx.req, ctx.res,{
pathname: '/b',
query: {id}
})
ctx.respond = false;
})
server.use(router.routes());
server.use(async (ctx, next)=>{
await handle(ctx.req, ctx.res);
ctx.respond = false;
})
server.listen(3000, ()=>{
console.log('koa success')
});
})
被跳转的页面接受参数需要使用withRouter
// index.js
<Link href="/b?id=2">点击跳转</Link>
// b.js
import Router, {withRouter} from 'next/router';
const MyB = ({router})=>{
const toIndex = ()=>{
Router.push('/')
}
console.log(router.query.id); // 2
return <div>
<span onClick={toIndex}>bbbbbbbbbbbbbb</span>
</div>
}
export default withRouter(MyB)
3.1 路由钩子
共有6个钩子
- routeChangeStart:当路由开始改变时候触发,接受一个参数:路由即将变成的地址
- routeChangeComplete:路由完全改变的时候触发,接受一个参数:路由即将变成的地址
- routeChangeError:发生错误时候触发
- beforeHistoryStart:修改历史记录之前触发,接受一个参数:路由即将变成的地址
- hashChangeStart:哈希值改变的时候触发
- hashChangeComplete:
4、getInitialProps
4、getInitialProps
是获取数据的方法,此方法只能在pages
下面的文件里使用,该方法返回一个数据对象,可以用于组件在服务端渲染,该方法也会在服务端执行。
该方法支持async/await
,但是有一个问题,就是代码里有加载的时候,加载会阻塞页面的跳转渲染。
// b.js
const MyB = ({router, name})=>{
const toIndex = ()=>{
Router.push('/')
}
console.log(router.query.id);
return <div>
<span onClick={toIndex}>bbbbbbbbbbbbbb</span>
<br/>
<span>{name}</span>
</div>
}
MyB.getInitialProps = ()=>{
console.log('111')
return {
name: 'cccc'
}
}
看network
服务端返回的页面数据
<!DOCTYPE html><html><head><style data-next-hide-fouc="true">body{display:none}</style><noscript data-next-hide-fouc="true"><style>body{display:block}</style></noscript><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><noscript data-n-css=""></noscript><link rel="preload" href="/_next/static/chunks/main.js?ts=1615617554866" as="script"/><link rel="preload" href="/_next/static/chunks/webpack.js?ts=1615617554866" as="script"/><link rel="preload" href="/_next/static/chunks/pages/_app.js?ts=1615617554866" as="script"/><link rel="preload" href="/_next/static/chunks/pages/b.js?ts=1615617554866" as="script"/><noscript id="__next_css__DO_NOT_USE__"></noscript></head><body><div id="__next">
`<div><span>bbbbbbbbbbbbbb</span><br/><span>cccc</span></div></div>`
<script src="/_next/static/chunks/react-refresh.js?ts=1615617554866"></script><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{"name":"cccc"}},"page":"/b","query":{"id":"2"},"buildId":"development","isFallback":false,"customServer":true,"gip":true}</script><script nomodule="" src="/_next/static/chunks/polyfills.js?ts=1615617554866"></script><script src="/_next/static/chunks/main.js?ts=1615617554866"></script><script src="/_next/static/chunks/webpack.js?ts=1615617554866"></script><script src="/_next/static/chunks/pages/_app.js?ts=1615617554866"></script><script src="/_next/static/chunks/pages/b.js?ts=1615617554866"></script><script src="/_next/static/development/_buildManifest.js?ts=1615617554866"></script><script src="/_next/static/development/_ssgManifest.js?ts=1615617554866"></script></body></html>
name
直接在服务端渲染完成了,任何刷新页面看开服务的控制台也会打印出来111
的内容,说明在服务端也同样执行了
5、自定义_app.js
nextJs里面_app.js
是最外层容器,可以在里面定义一些自定义的内容,有以下的好处
- 固定layout
- 保持一些公用的状态
- 给页面传入自定义数据
- 自定义错误处理
我们扩展可以在_app.js
里面定义一个子类,继承自App
,然后进行扩展。
import App from 'next/app';
class myApp extends App{
static async getInitialProps({Component, ctx}){
// 获取子页面定义的`getInitialProps`并且执行
const pageProps = Component.getInitialProps ? await Component.getInitialProps(ctx) : {};
// 把子页面的内容返回到this.props上
return {
pageProps
}
}
render(){
const {Component, pageProps} = this.props; // 子页面
return (
<>
<Component {...pageProps}></Component>
</>
)
}
}
export default myApp;
加layout的话可以在根目录创建一个components/layout.jsx
import Link from 'next/link';
export default ({children}) => (
<>
<div className="header">
<div>
<Link href="/">首页</Link>
<Link href="/b?id=2" as="/b/2">点击跳转到B</Link>
</div>
<div>
{children}
</div>
</div>
</>
)
然后在_app.js
里面引入
import Layout from '../components/layout'
class myApp extends App{
// ...
render(){
const {Component, pageProps} = this.props; // 子页面
return (
<>
<Layout>
<Component {...pageProps}></Component>
</Layout>
</>
)
}
}
6、自定义document
document在使用服务端渲染的时候才会被调用,用来修改服务端渲染的内容。
如果要进行自定义document,则下面的内容为必填。
import Document, { Html, Head, Main, NextScript } from 'next/document';
class myDocument extends Document{
static async getInitialProps(ctx){
const props = await Document.getInitialProps(ctx);
return {
...props
}
}
render(){
return (
<Html>
<Head>
</Head>
<body>
<Main></Main>
<NextScript></NextScript>
</body>
</Html>
)
}
}
export default myDocument;
重写就是有其他需求,其他需求需要在以上基础代码上添加。
7、css-in-js
低版本nextjs默认是不支持去load一个css文件的,需要通过修改配置,不支持css文件,我们需要给标签增加样式,需要用到css-in-js
的方案。该方案next默认集成,自动做好了服务端渲染的内容,服务端返回的时候就已经有了样式而不用到客户端再进行渲染。
return <>
<div>
<span onClick={toIndex}>bbbbbbbbbbbbbb</span>
<span>{name}</span>
</div>
<style jsx>
{
`
span{
color: #333;
}
`
}
</style>
</>
8、lazy Loading
next默认把所有的页面都切割成不同的chunk,当加载那个路由对应的页面的时候才会去懒加载那个页面js。
默认加载会有两种情况:1. 异步加载模块 2. 异步加载组件
不想把依赖模块打包到全局,初次进来便加载的话可以使用异步加载模块,可以在使用页面里的getInitialProps
里执行import()
xx.getInitialProps = async (ctx)=>{
const xxxModule = await import('xxxx');
return {
name: 'cccc'
}
}
异步加载组件的话只需要引入dynamic
import dynamic from 'next/dynamic';
const Component = dynamic(import('../components/component.jsx'));
这样Component
组件只有在被渲染的时候才会被加载。
9、next.config.js
const configs = {
// 编译文件的输出目录
distDir: 'dest',
// 是否给每个路由生成Etag. Etag:进行缓存验证
generateEtags: true,
// 页面内容缓存配置
onDemandEntries: {
// 内容在内存中缓存的时长(ms)
maxInactiveAge: 25 * 1000,
// 同时缓存多少个页面
pagesBufferLength: 2,
},
// 在pages目录下那种后缀的文件会被认为是页面
pageExtensions: ['jsx', 'js'],
// 配置buildId
generateBuildId: async () => {
if (process.env.YOUR_BUILD_ID) {
return process.env.YOUR_BUILD_ID
}
// 返回null使用默认的unique id
return null
},
// 手动修改webpack config
webpack(config, options) {
return config
},
// 修改webpackDevMiddleware配置
webpackDevMiddleware: config => {
return config
},
// 可以在页面上通过 procsess.env.customKey 获取 value
env: {
customKey: 'value',
},
// 下面两个要通过 'next/config' 来读取
// 只有在服务端渲染时才会获取的配置
serverRuntimeConfig: {
mySecret: 'secret',
secondSecret: process.env.SECOND_SECRET,
},
// 在服务端渲染和客户端渲染都可获取的配置
publicRuntimeConfig: {
staticFolder: '/static',
},
}
10、渲染流程解析
nextJs作为react的同构框架,即实现了服务端渲染,同时服务端渲染期间的一些流程问题也都帮解决了,比如getInitialProps
,这个方法可以让我们在页面渲染之前获取数据,然后通过props传递给渲染的页面,在客户端跳转和服务端渲染的时候都会执行。
nextJs服务端渲染流程如下
- 浏览器发起请求
- 服务端收到请求,并调用nextJs
- nextJs开始渲染
- 调用app的
getInitialProps
- 在调用
app
的getInitialProps
的时候会执行页面上的getInitialProps
- 页面上请求数据然后渲染出最终的html
- 返回给客户端
客户端页面跳转渲染流程如下
- 用户点击跳转
- 页面加载页面的组件js
- 调用页面的
getInitialProps
- 获取数据并返回数据,路由改变
- 页面跳转成功,进行渲染展示