前言

在学习完React后打算手撸一个博客,鉴于React是单页面SPA应用,SEO属实不友好,最后了解到Next.js,一个轻量级的 React 服务端渲染应用框架,也就是说具有良好的SEO优化,故大致学习一下。

环境搭建

这里我使用的是create-next-app,一个官方的脚手架,全局安装搞起来~

npm install -g create-next-app

创建项目

create-next-app demo #demo即项目名称

目录详解

|-- components   //用于放置自己写的组件,这里的组件不包括页面,指公用的或者专门用途的组件
|-- node_modules   //Next项目的依赖包
|-- pages   //放置页面,这里的内容会自动生成路由,并由服务端渲染,渲染后进行数据同步
|-- static   //静态文件夹,比如静态资源
|-- package.json   //定义了项目所需要的文件和项目的配置信息(名称、版本和许可证),最主要的是使用npm install 就可以下载项目所需要的所有包

下面进入正题

使用

Page

直接在根目录下的pages文件夹下,新建一个app.js页面:

function App(){
  return (<h1>Hello,Nextjs</h1>)
}
export defalut App;

码了以上之后,Next框架会自动帮我们配置好路由,帮我们节省了大量的时间。

Component组件

新建一个button组件,直接在components目录下建立一个文件newbutton.js,写入如下代码:

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

组件写完后需引入,比如在index页面进行引入:

import newButton from '../components/newbutton.js'

然后直接使用标签即可。

<newButton>按钮</newButton>

路由

基础和基本跳转

Next框架下页面跳转一般有两种形式,第一张是利用标签<Link>,第二种是使用js编程的方式跳转,也就是利用Router组件

标签式导航<Link>

首先进行引入

import Link from 'next/link'

然后新建两个页面A.jsB.js,新建后写个最简单的页面,能够标识出来这两个页面

// A.js
import Link from 'next/link'

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

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

index首页的代码:

// index.js
import React from 'react'
import Link from 'next/link'

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

export default Home

使用<Link>标签进行跳转是非常容易的,但这里有一个小坑需要注意下,即它不支持兄弟标签并列的情况。

<div>
  <Link href="/A">
    <span>去A页面</span>
    <span>Nextjs</span>
  </Link>
</div>

如果这样写会直接报错,如下:

client pings, but there's no entry for page: /_error
Warning: You're using a string directly inside <Link>. This usage has been deprecated. Please add an <a> tag as child of <Link>

解决方案:在这两个标签外边套一个父标签即可。

<div>
  <Link href="/A">
    <a>
    <span>去A页面</span>
    <span>Nextjs</span>
    </a>
  </Link>
</div>

让我们再来看看如何使用编程的方式进行跳转

Router模块进行跳转

使用前引入Router

import Router from 'next/router'

然后在index.js页面加入,直接使用Router进行跳转即可

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

但这个写法不太优雅,所以在这里封装一个方法,然后调用

import React from 'react'
import Link from 'next/link'
import Router from 'next/router'
const Home = () => {
  function goTo(path){
    Router.push(path)
  }
  return(
    <>
      <div>我是首页</div>
      <div>
        <Link href="/A">
          <a>
            <span>去A页面</span>
            <span>Nextjs</span>
          </a>
        </Link>
      </div>
      <div><Link href="/jspangB"><a>去B页面</a></Link></div>
      <div>
        <button onClick={goTo('/A')}>去A页面</button>
      </div>
    </>
  )

}
export default Home

跳转时用query传递和接收参数

只能使用query传递参数

Next.js中只能通过query(?id=1)来传递参数,而不能通过(path:id)的形式传递参数,因为Nextjs已经写死了路由

改写index.js文件

import React from 'react'
import Link from 'next/link'
import Router from 'next/router'
const Home = () => {
  return(
    <>
      <div>我是首页</div>
      <div>
        <Link href="/movie?name=杀破狼"><a>杀破狼</a></Link><br/>
        <Link href="/movie?name=钢铁侠"><a>钢铁侠</a></Link>
      </div>
    </>
  )

}
export default Home
接收传递的参数

创建Movie.js页面,写入如下:

import { withRouter} from 'next/router'
import Link from 'next/link'

const Movie = ({router})=>{
    return (
        <>
            <div>{router.query.name}</div>
            <Link href="/"><a>返回首页</a></Link>
        </>
    )
}

export default withRouter(Movie)

withRouter是Next.js框架的高级组件,用来处理路由的,这里不做详解。

编程式跳转传递参数

优雅的写法如下

function goTo(path,query){
  Router.push({
    pathname: path,
    query: query //query是一个object
  })
}

其实<Link>标签也可以写成如下形式:

<Link href={{pathname:'/movie',query:{name:'钢铁侠'}}}><a>钢铁侠</a></Link><br/>
六个钩子事件
routerChangeStart 路由发生变化时

在监听路由发生变化时,需要用到Router组件,然后用on方法来进行监听

Router.events.on('routeChangeStart',(...args)=>{
    console.log('1,routeChangeStart->路由开始变化,参数为:',...args)
})
routerChangeComplete 路由结束变化时
Router.events.on('routeChangeComplete',(...args)=>{
    console.log('2,routeChangeComplete->路由结束变化,参数为:',...args)
})
beforeHistoryChange 浏览器history触发前

history是HTML中的API,有兴趣可以百度了解一下,此外Next.js路由变化默认都是通过history进行的,所以每次都会调用。不适用history的话,亦可使用hash。

routeChangeErroe 路由跳转发生错误时
Router.events.on('routeChangeError',(...args)=>{
    console.log('4,routeChangeError->跳转发生错误,参数为:',...args)
})
转变成hash路由模式

还有两种事件,是针对hash的。hash模式下的两个事件hashChangeStarthashChangeComplete

  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="#iobiji"><a>视觉志笔记</a></Link>
</div>

下面给出index.js完整代码

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

const Home = () => {
  function goTo(path,query){
  Router.push({
    pathname: path,
    query: query //query是一个object
    })
  }

  Router.events.on('routeChangeStart',(...args)=>{
    console.log('1,routeChangeStart->路由开始变化,参数为:',...args)
  })

  Router.events.on('routeChangeComplete',(...args)=>{
    console.log('2,routeChangeComplete->路由结束变化,参数为:',...args)
  })

  Router.events.on('beforeHistoryChange',(...args)=>{
    console.log('3,beforeHistoryChange->在改变浏览器 history之前触发,参数为:',...args)
  })

  Router.events.on('routeChangeError',(...args)=>{
    console.log('4,routeChangeError->跳转发生错误,参数为:',...args)
  })

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

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

  return(
    <>
      <div>我是首页</div>
      <div>
        <Link href={{pathname:'/movie',query:{name:'钢铁侠'}}}><a>钢铁侠</a></Link><br/>
      </div>
      <div>
        <button onClick={goTo('/movie','{name:\'钢铁侠\')'}>钢铁侠</button>
      </div>
      <div>
         <Link href="#iobiji"><a>视觉志笔记</a></Link>
      </div>
    </>
  )

}
export default Home

getInitialProps中用Axios获取接口数据

Next.js框架中提供了getInitialProps静态方法用于获取远端数据,这是框架的约定,因此只能在这个方法内获取远端数据,不要再试图在声明周期里获得,虽然也可以在ComponentDidMount中获取。

axios的安装与引入

yarn add axios
import axios from 'axios'

引入后,就可以使用getInitialProps进行获取后端接口数据了。

getInitialProps 中获取数据

movie.js页面中使用getInitialProps,因为是远程获取数据,所以采用异步请求的方式

Movie.getInitialProps = async ()=>{
  const promise = new Promist((resolve)=>{
    axios('API地址').then(res=>{
      console.log(res)
      resolve(res.data)
    })
  })
  return await promise
}
import { withRouter} from 'next/router'
import Link from 'next/link'
import axios from 'axios'

const Movie = ({router,list})=>{
    return (
        <>
            <div>{router.query.name}<br/>{list}</div> #list就是我们获取的数据
            <Link href="/"><a>返回首页</a></Link>
        </>
    )
}

Movie.getInitialProps = async ()=>{
  const promise = new Promist((resolve)=>{
    axios('API地址').then(res=>{
      console.log(res)
      resolve(res.data)
    })
  })
  return await promise
}

export default withRouter(Movie)

使用Style JSX编写页面CSS样式

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

动态显示样式

import React, {useState} from 'react'

function App(){
    //关键代码----------start-------
    const [color,setColor] = useState('blue')

    const changeColor=()=>{

        setColor(color=='blue'?'red':'blue')
    }
     //关键代码----------end-------

    return (
        <>
            <div>CSS</div>
            <style jsx>
                {`
                    div { color:${color};}
                `}
            </style>
        </>
    )
}
export default App

Lazy Loading实现模块懒加载

当项目越来越大时,模块的加载是需要管理的,如果不管理会出现首次打开过慢,页面长时间没有反映一系列问题。这时候可用Next.js提供的LazyLoading来解决这类问题。让模块和组件只有在用到的时候进行加载,并且它一般分为两种情况,一种是懒加载(或者说是异步加载)模块,另一种是异步加载组件,他们的使用方法也稍有不同。

懒加载模块

这里使用一个在开发中常用的模块Moment.js,它是一个JavaScript日期处理类库

yarn add moment

然后在pages文件夹下,建立一个time.js,并使用moment类库来格式化时间

import React, {useState} from 'react'

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文件夹下建立一个cmp.js文件

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

有了自定义组件后,先要在懒加载这个组件的文件中引入dynamic,继续改写time.js

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

const Cmp = dynamic(import('../components/cmp'))

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>
            <Cmp/>
            <div><button onClick={changeTime}>改变时间格式</button></div>
        </>
    )
}
export default Time

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

Caps1 在各个页面加上标签

pages文件夹下新建一个header.js文件

import Head from 'next/head'
function Header(){ 
    return (
        <>
            <Head>
                <title>视觉志</title>
                <meta charSet='utf-8' />
            </Head>
            <div>iobiji.com</div>
        </> 
    )
}
export default Header

Next.js框架下使用Ant Design UI

让Next.js支持CSS文件

Next.js默认是不支持CSS文件的,它用的是style jsx,也就是说它不支持直接用import进行引入CSS,因此需要安装一个包来加载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({})
按需加载ANTD

加载antd在我们打包时会把antd的所有包都打包进来,这样就会产生性能问题,让项目加载变得非常慢,所以我们需要按需加载antd,首先需要安装babel-plugin-import插件

yarn add babel-plugin-import

安装完成后,在项目根目录新建.babelrc文件,写入如下

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

这样就可以愉快的使用antd了~

最后还有使用antd的一个小坑,解决如下,在pages目录下,新建一个_app.js文件,写入如下

import App from 'next/app'
import 'antd/dist/antd.css'

export default App