文章说明:
1. 之前想搭建个人博客,由于学习的是react技术栈,所以就到处搜罗资料学了nextjs,配合koa就把博客搭起来了。该系列文章基于我的学习笔记,重新整理了一遍,如果有错误之处,还请指正。
2. 个人能力有限,我更注重的是使用,所以对于许多原理也只是简单理解,并未深究。如果是想研究源码,或者追求性能深度优化的老铁,可以不用往下看了
3. 转载请注明出处
各工具版本:npm v6.10,node v12.13,react v16.12,next v9.1
可能需要的预备知识:React项目经验、nodejs开发经验,webpack、npm等常用工具使用经验。最好会koa(部分地方可能会用到)
一、Nextjs与服务端渲染
Nextjs的官方解释:Next.js 是一个轻量级的 React 服务端渲染应用框架。
服务端渲染,顾名思义,就是在服务器上就把网页渲染好了,你请求时,直接发给你渲染好的页面。知道了原理,ssr的优势劣势也就很明显了:
与客户端渲染比较
优势:
1. 更利于SEO,便于搜索引擎收录。因为大多数爬虫只会爬源码,不会爬脚本,当服务端返回渲染好的数据后爬虫便能直接爬取了。
2. 利于首屏加载。这个简单,服务端发过来的就是渲染好的,客户端就省事了,加载也就快了。
劣势就是:1. 服务器的压力大了(多了活要干);2. 对开发人员要求也高了
(深有体会,量发而行!这已经不属纯正血统的前端范畴了,因为要成功部署的话,你还不得不心甘情愿地去学点服务器知识、Linux运维、nginx部署等)
特别注意
- 当服务端渲染时,服务端是没有window、document对象的(浏览器端才有),直接调用会报错。想用这俩对象的话最好放在didAmount周期函数里,等组件挂载后再调用(其实从这一点来说,这只能算是半服务端渲染......扯远了)
- 另外,Nextjs自带的服务器专注于处理ssr部分,但后端往往还需要处理文件、连接数据库等功能,此时还需要借助其他的node服务器,我这选用了koa2,然后让nextjs作为koa的一个专门负责ssr的中间件。
二、搭建Nextjs项目
实践出真知。介绍完了,作为正经的程序员,先上手一个再慢慢研究。我们按照官网的节奏,一步步搭建项目,再一步步解释每个文件和目录的作用
1. 搭建环境
- 随便创建个项目目录,如:demo
- 进入该目录,npm初始化生成package.json:
npm init
- npm安装nextjs和其依赖:
npm install next react react-dom
- 之后再修改package.json的scripts模块:
{"scripts": {"dev": "next","build": "next build","start": "next start"}
}
说明:和react项目类似,这里的命令build即部署时的打包命令,start运行打包后的文件命令,dev则是开发环境下启动nextjs服务器。
到这里,环境就算搭建好了。
2. 创建pages目录
Nextjs的路由系统非常简单,所有的路由页面全部存放在pages目录下,nextjs会自动对应page目录的文件路径生成对应路由。
如,我们在pages创建demo.js:
export default () => <div>This is the demo page</div>
然后启动next服务器:npm run dev
命令行显示如下时表示启动成功了
D:webdemo>npm run dev> next_test@1.0.0 dev D:webdemo
> next[ wait ] starting the development server ...
[ info ] waiting on http://localhost:3000 ...
此时打开浏览器,进入localhost:3000/demo,便能看到页面了
3._app.js和 _document.js
特别的,pages下有两个特殊文件:_app.js和_document.js。这两个文件默认是隐藏的,新建的话就会覆盖之前的文件。他们分别用来自定义APP和自定义Document
自定义?什么意思?有什么用?简单来说就是用来定义一些页面共用的属性(如设置全局css,通用布局等),或者定义一些通用的动作(如获取、传递数据等), 这个结合nextjs的getInitialProps函数会更好说明,就留着后续讲getInitialProps数据获取和页面布局的时候再一并解释吧。
这篇文章我们主要关注路由系统。
三、路由系统
保证pages目录的干净
对于现在的组件化开发,我们通常会有两种组件,一种作为某个独立功能的小组件,一种是作为页面显示的页面组件(通常结合了多个小组件),Nexjs同样适用, 但小组件不能存储在pages目录下,这会导致路由系统混乱。我们可以新建一个components目录(根据自己喜好自定义名字)来存储小组件,在需要时从该目录下import使用即可。
多级路由:
如果路由有多级,直接在pages下创立父级目录,再把最终路由文件放入目录下即可实现多级路由。如在pages目录下创建user目录,user下再创建index.js和home.js,那么路由/user就对应index.js文件,/user/home就对应home.js文件
路由跳转
Nextjs官方推荐了两种跳转方式,一种是Link组件包裹,一种使用Router,我个人是不推荐Link的,原理也是用Router实现的,使用也简单,但用起来总感觉很冗余。我这里主要介绍Router,想了解Link的老铁得麻烦移步官网了。
Nextjs提供了一个'next/router'的包,专门用来处理路由。Router便是其中一个对象,Router.push('url')进行跳转。
实践一下:
1. pages目录下,创建index.js文件
import React from 'react'
import Router from 'next/router'export default () => {return(<><button onClick={()=>Router.push('/demo')} >送我去demo页</button><div>这里是主页</div></>)
}
2. 修改demo.js文件
import React from 'react'
import Router from 'next/router'export default () => {return(<><button onClick={()=>Router.push('/')} >送我去主页</button><div>这里是demo页</div></>)
}
3. 运行结果
点击“送我去demo页”按钮后
路由参数
注意!注意!Nextjs不能使用params传参数!只能通过query!
像这样
Router.push('url?id=1')
等价
Router.push({pathname:'url',query:{id:1}})
另外,前面说过,服务端渲染时没有window对象的,自然不能通过传统途径获取url参数,这里'next/router'里提供了一个withRouter对象,用它包裹组件后,组件会多出router的参数,通过router就能获取query参数了
import React from 'react'
import Router,{ withRouter } from 'next/router'const Demo = (props) => {return(<><button onClick={()=>Router.push('/')} >送我去主页</button><div>这里是demo页</div><div>{props.router.query.id}</div></>)
}
export default withRouter(Demo);
路由映射
我们常看到的url是这样的/url?id=1,而路由映射可以帮我们显示成为这样 /url/1,比较美观友好(其实也就好一点点),这小节讲解下路由映射,会涉及到koa使用,不感兴趣的小伙伴直接跳过吧,毕竟也不是什么特别重要的
"表面上"的实现方法:
1. Router.push({ pathname: '/demo', query: { id: 1 } },'/demo/1')Router.push的第三个参数即可指定别名
2. Link组件中的as属性<Link href='/demo?id=1' as='/demo/1'>
这样看上去的确可以了,初始时也能访问,但页面一刷新就会404,为什么?因为当我们点击按钮在浏览器端跳转时,是浏览器去找页面,它通过路由映射可以找到。而刷新的时候,是服务器去找,而我们的pages页面里面没有/demo/1的文件,所以就报404了。
解决办法,利用koa处理:
router.get('/demo/:id',async (ctx)=>{cosnt id = ctx.params.id await handle(ctx.req,ctx.res,{pathname:'/demo',query: {id}}),ctx.respond = false
})
其实就是在服务器处又将路由转换回来而已。
路由钩子
Router中还定义了几个钩子函数用来获取路由转变时的状态,方便我们在转换路由时进行操作
// routeChangeStart history模式路由改变刚开始
// routeChangeComplete history模式路由改变结束
// routeChangeError 路由改变失败
// hashChangeStart hash模式路由改变刚开始
// hashChangeComplete hash模式路由改变结束Router.events.on(event,func()) //event即监听的事件(以上5种),func回调函数
Router.events.off(event,func()) //取消监听
好了,路由系统就讲解到这了,下一篇文章会讲解到nextjs的布局和getInitialProps()函数,顺带会把这期遗留的_app.js和_document.js一并解释了。
喜欢的话欢迎分享,欢迎讨论
欢迎关注我的其他平台账号: 【知乎】均远 【公众号】佛系前端 【个人博客】http://xujunyuan.com 【GitHub】JunYuanHub