nextJS

2021/03/07 nextjs

简单认识一下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可以使用linkas属性,路由映射功能。

<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服务端渲染流程如下

  1. 浏览器发起请求
  2. 服务端收到请求,并调用nextJs
  3. nextJs开始渲染
  4. 调用app的getInitialProps
  5. 在调用appgetInitialProps的时候会执行页面上的getInitialProps
  6. 页面上请求数据然后渲染出最终的html
  7. 返回给客户端

客户端页面跳转渲染流程如下

  1. 用户点击跳转
  2. 页面加载页面的组件js
  3. 调用页面的getInitialProps
  4. 获取数据并返回数据,路由改变
  5. 页面跳转成功,进行渲染展示

Search

    Table of Contents