前言
在学习完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.js和B.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模式下的两个事件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="#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