1.Next.js简介

Next.js 是一个轻量级的 React 服务端渲染应用框架。

它的优点:

  • 完善的React项目架构,搭建轻松。比如:Webpack配置,服务器启动,路由配置,缓存能力,这些在它内部已经完善的为我们搭建完成了。
  • 自带数据同步策略,解决服务端渲染最大难点。把服务端渲染好的数据,拿到客户端重用,这个在没有框架的时候,是非常复杂和困难的。有了Next.js,它为我们提供了非常好的解决方法,让我们轻松的就可以实现这些步骤。
  • 丰富的插件帮开发人员增加各种功能。每个项目的需求都是不一样的,包罗万象。无所不有,它为我们提供了插件机制,让我们可以在使用的时候按需使用。你也可以自己写一个插件,让别人来使用。
  • 灵活的配置,让开发变的更简单。它提供很多灵活的配置项,可以根据项目要求的不同快速灵活的进行配置。

2.快速创建Next.js

#全局安装
npm install -g create-next-app
#npx创建项目
npx install create-next-app demo13
cd demo13
#本地运行
yarn dev

3.项目结构介绍

  • components文件夹:这里是专门放置自己写的组件的,这里的组件不包括页面,指公用的或者有专门用途的组件。
  • node_modules文件夹:Next项目的所有依赖包都在这里,一般我们不会修改和编辑这里的内容。
  • pages文件夹:这里是放置页面的,这里边的内容会自动生成路由,并在服务器端渲染,渲染好后进行数据同步。
  • static文件夹: 这个是静态文件夹,比如项目需要的图片、图标和静态资源都可以放到这里。
  • .gitignore文件: 这个主要是控制git提交和上传文件的,简称就是忽略提交。
  • package.json文件:定义了项目所需要的文件和项目的配置信息(名称、版本和许可证),最主要的是使用npm install 就可以下载项目所需要的所有包。

4.Page和Component使用

在pages文件下,创建Test.js。

function Test(){
    return (<button> Test page</button>)
}

export default  Test;

写完以上的代码,Next框架就自动作好了路由,这个也算是Next的一个重要优点,给我们节省了大量的时间。

现在要作一个更深的页面,比如把有关博客的界面都放在这样的路径下http://localhost:3000/blog/nextBlog,其实只要在pages文件夹下再建立一个新的文件夹blog,然后进入blog文件夹,新建一个nextBlog.js文件,就可以实现了。

编写组件也特别方便,比如要建立一个comm组件,直接在components目录下建立一个文件comm,然后写入下面代码:

export default ({children})=><button>{children}</button>

组件写完后需要先引入,比如我们在Index页面里进行引入:

import comm from '../components/comm'

使用就非常简单了,直接写入标签就可以。

<comm>按钮</comm>

5.路由基础及跳转

5.1 标签是导航

首先先删除index.js中的代码,引入以下:

import Link from 'next/link'

然后新建两个页面testA.jstestB.js

import Link from 'next/link'

export default ()=>(
    <>
        <div>TestA page .  </div>
        <Link href="/"><a>返回首页</a></Link>
    </>
)
import Link from 'next/link'

export default ()=>(
    <>
        <div>TestB page .  </div>
        <Link href="/"><a>返回首页</a></Link>
    </>
)

然后编写index文件

import React from 'react'
import Link from 'next/link'


const Home = () => (
  <>
    <div>我是首页</div>
    <div><Link href="/testA"><a>去testA页面</a></Link></div>
    <div><Link href="/testB"><a>去testB页面</a></Link></div>

  </>
)

export default Home

注意不支持兄弟并列标签。

5.2 Router模块跳转

在index.js中,添加以下:

<div>
   <button onClick={()=>{Router.push('/testA')}}>去testA页面</button>
 </div>

6.路由跳转传值和接收参数

6.1 query传递参数及接收
<div><Link href="/testA?name=karma"><a>Link跳转去testA页面</a></Link></div>
//或
<div><Link href={{pathname: '/testA', query: {name: 'karma'}}}><a>Link跳转去testA页面</a></Link></div>

然后在testA.js文件中接收该参数:

import React from 'react'
import Link from 'next/link'
import {withRouter} from "next/router";

const testA = ({router}) => (
    <>
        <div>testA page . {router.query.name}</div>
        <Link href="/"><a>返回首页</a></Link>
    </>
)
//withRouter是Next.js框架的高级组件,用来处理路由用的
export default withRouter(testA)
6.2 Router编程式传递参数
<button onClick={()=>{Router.push('/testA?name=karma')}}>去testA页面</button>
#或者
<button onClick={()=>{Router.push({
      pathname:'/testA',
      query:{
        name:'karma'
      }
    })}>去testA页面</button>

7.路由-六个钩子事件

routerChangeStart路由发生变化
 Router.events.on('routeChangeStart',(...args)=>{
  console.log('1.routeChangeStart->路由开始变化,参数为:',...args)
})
routerChangeComplete路由结束变化时
Router.events.on('routeChangeComplete',(...args)=>{
  console.log('routeChangeComplete->路由结束变化,参数为:',...args)
})
beforeHistoryChange浏览器history触发前
Router.events.on('beforeHistoryChange',(...args)=>{
  console.log('3,beforeHistoryChange->在改变浏览器 history之前触发,参数为:',...args)
})
routeChangeError路由跳转发生错误时
Router.events.on('routeChangeError',(...args)=>{
   console.log('4,routeChangeError->跳转发生错误,参数为:',...args)
 })

需要注意的是404找不到路由页面不算错误。

转变成hash路由模式

还有两种事件,都是针对hash的,所以现在要转变成hash模式。hash模式下的两个事件hashChangeStart和`hashChangeComplete。

Router.events.on('hashChangeStart',(...args)=>{
  console.log('5,hashChangeStart->hash跳转开始时执行,参数为:',...args)
})

Router.events.on('hashChangeComplete',(...args)=>{
  console.log('6,hashChangeComplete->hash跳转完成时,参数为:',...args)
})

在下面的jsx语法部分,再增加一个链接,使用hash来进行跳转,代码如下:

<div>
    <Link href="#test"><a>test</a></Link>
</div>

8.在getInitialProps中使用Axios获取远端数据

Next.js框架中提供了getInitialProps静态方法用来获取远端数据,这个是框架的约定,所以你也只能在这个方法里获取远端数据。

安装Axios:

yarn add axios

在testA.js中引入Axios

import Axios from 'axios'

然后使用getInitialProps获取远程数据并渲染页面

import React from 'react'
import Link from 'next/link'
import {withRouter} from "next/router";
import Axios from "axios";

const testA = ({router, list}) => (
    <>
        <div>testA page . {router.query.name} {list}</div>
        <Link href="/"><a>返回首页</a></Link>
    </>
)
testA.getInitialProps = async () => {
    const promise = new Promise((resolve => {
        Axios('https://www.easy-mock.com/mock/5f96cc6134c55d14fda96ea1/example/query').then((res) => {
            console.log('远程数据结果:', res)
            resolve(res.data.data)
        })

    }))
    return await promise
}
//withRouter是Next.js框架的高级组件,用来处理路由用的
export default withRouter(testA)

9.使用style JSX编写页面的css样式

在pages文件下,创建testC.js.

function testC(){
    return (
        <>
            <div>testC</div>
        <style jsx>
          {`
              div{color:blue;}
          `}
  			</style>
        </>
    )
}
export default testC

通过自定义类名加载css

function testC(){
    return (
        <>
            <div>testC</div>
       			<div className="karma">karma</div>
        		<style jsx>
              {`
                  div{color:blue;},
									.karma{color:red}
              `}
  					</style>
        </>
    )
}
export default testC

动态显示样式

import {useState} from "react";

function testC() {
    const [color, setColor] = useState('blue')
    const changeColor = () => {
        setColor(color === 'blue' ? 'red' : 'blue')
    }

    return (
        <>
            <div>testC</div>
            <div>
                <button onClick={changeColor}>改变颜色</button>
            </div>
            <div className="karma">karma</div>
            {/*Style JSX语法*/}
            <style jsx>
                {`
                    div{color:${color};},
                `}
            </style>
        </>
    )
}

export default testC

10.Lazy Loading 实现模块懒加载

懒加载模块

安装moment

yarn add moment

然后在pages文件夹下,新建立一个time.js文件,并使用刚才的moment库来格式化时间,代码如下:

import React, {useState} from 'react'
import moment from 'moment'

function Time(){

    const [nowTime,setTime] = useState(Date.now())

    const changeTime=()=>{
        setTime(moment(Date.now()).format())
    }
    return (
        <>
            <div>显示时间为:{nowTime}</div>
            <div><button onClick={changeTime}>改变时间格式</button></div>
        </>
    )
}
export default Time

这个案例,存在着一个潜在的风险,就是如何有半数以上页面使用了这个momnet的库,那它就会以公共库的形式进行打包发布,就算项目第一个页面不使用moment也会进行加载,这就是资源浪费,对于我这样有代码洁癖的良好程序员是绝对不允许的。下面我们就通过Lazy Loading来进行改造代码。

import React, {useState} from 'react'
//删除import moment
function Time(){

    const [nowTime,setTime] = useState(Date.now())

    const changeTime= async ()=>{ //把方法变成异步模式
        const moment = await import('moment') //等待moment加载完成
        setTime(moment.default(Date.now()).format()) //注意使用defalut
    }
    return (
        <>
            <div>显示时间为:{nowTime}</div>
            <div><button onClick={changeTime}>改变时间格式</button></div>
        </>
    )
}
export default Time
懒加载自定义组件

先写一个最简单的组件,在components文件夹下建立一个one.js文件,然后编写如下代码:

export default ()=><div>Lazy Loading Component</div>

有了自定义组件后,先要在懒加载这个组件的文件中引入dynamic,我们这个就在上边新建的time.js文件中编写了。

import dynamic from 'next/dynamic'

引入后就可以懒加载自定义模块了,代码如下:

import React, {useState} from 'react'
import dynamic from 'next/dynamic'

const One = dynamic(import('../components/one'))

function Time(){

    const [nowTime,setTime] = useState(Date.now())

    const changeTime= async ()=>{
        const moment = await import('moment')

        setTime(moment.default(Date.now()).format())
    }
    return (
        <>
            <div>显示时间为:{nowTime}</div>
            <One/>
            <div><button onClick={changeTime}>改变时间格式</button></div>
        </>
    )
}
export default Time

11.自定义Head更加友好的SEO操作

一般有两种方式。

方法1:在各页面加上标签

先在/pages文件夹下面建立一个header.js文件,然后写一个最简单的Hooks页面,代码如下:

function Header(){ 
    return (<div>JSPang.com</div>)
}
export default Header

写完后到浏览器中预览一下,可以发现title部分并没有任何内容,显示的是localhost:3000/header,接下来就自定义下<Head>。自定义需要先进行引入next/head

import Head from 'next/head'

引入后你就可以写一些列的头部标签了,全部代码如下:

import Head from 'next/head'
function Header(){ 
    return (
        <>
            <Head>
                <title>Header</title>
                <meta charSet='utf-8' />
            </Head>
            <div>Karma520.com</div>

        </> 
    )
}
export default Header

这时候再打开浏览器预览,你发现已经有了title

方法2:全局定义

比如在components文件夹下面新建立一个myheader.js,然后写入下面的代码:

import Head from 'next/head'

const MyHeader = ()=>{
    return (
        <>
            <Head>
                <title> Karma520.com </title>   
            </Head>
        </>
    )
}

export default MyHeader

把刚才编写的header.js页面改写一下,引入自定义的myheader,在页面里进行使用。

import Myheader from '../components/myheader'
function Header(){ 
    return (
        <>
            <Myheader />
            <div>Karma520.com</div>

        </> 
    )
}
export default Header

12.Next.js脚手架中使用Ant Design UI

Next.js默认是不支持CSS文件的,它用的是style jsx,也就是说它是不支持直接用import进行引入css的。

在static下新建一个test.css文件,写入一些CSS Style

body{
    color:green;
}

然后用importheader.js里引入。

import '../static/test.css'

写完到浏览器中进行预览,没有任何输出结果而且报错了。这说明Next.js默认是不支持CSS样式引入的,要进行一些必要的设置,才可以完成。

开始进行配置,让Next.js支持CSS文件

先用yarn命令来安装@zeit/next-css包,它的主要功能就是让Next.js可以加载CSS文件。

yarn add @zeit/next-css

包安装好以后就可以进行配置文件的编写了,建立一个next.config.js.这个就是Next.js的总配置文件(如果感兴趣可以自学一下)。

const withCss = require('@zeit/next-css')

if(typeof require !== 'undefined'){
    require.extensions['.css']=file=>{}
}

module.exports = withCss({})

重启服务可以让配置生效,这时候你到浏览器中可以发现CSS文件已经生效了,字变成了绿色。

按需加载Ant Design

加载Ant Design在我们打包的时候会把Ant Design的所有包都打包进来,这样就会产生性能问题,让项目加载变的非常慢。

** 先来安装Ant Design库 **

直接使用yarn来安装就可以。

yarn add antd

** 安装和配置babel-plugin-import 插件 **

yarn add babel-plugin-import

然后在项目根目录建立.babelrc文件,然后写入如下配置文件。

{
    "presets":["next/babel"],  //Next.js的总配置文件,相当于继承了它本身的所有配置
    "plugins":[     //增加新的插件,这个插件就是让antd可以按需引入,包括CSS
        [
            "import",
            {
                "libraryName":"antd",
                "style":"css"
            }
        ]
    ]
}

配置好了以后,webpack就不会默认把整个Ant Design的包都进行打包到生产环境了,而是我们使用那个组件就打包那个组件,同样CSS也是按需打包的。

然后在header.js里,引入<Button>组件,并进行使用。

import Myheader from '../components/myheader'
import {Button} from 'antd'


import '../static/test.css'
function Header(){ 
    return (
        <>
            <Myheader />
            <div>Karma520.com</div>
            <div><Button>按钮</Button></div>

        </> 
    )
}
export default Header

运行,看效果。

13.Next打包CSS问题

打包 :yarn build

然后在终端里运行一下yarn build,如果这时候报错,其实是我们在加入Ant Design的样式时产生的,这个已经在Ant Design的Github上被提出了,但目前还没有被修改,你可以改完全局引入CSS解决问题。

在page目录下,修改_app.js文件。

import App from 'next/app'

import 'antd/dist/antd.css'

export default App

并且注释.babelrc中该行配置 “style”:”css”,然后执行命令。

源码地址:传送门