1、名词解释
SSR(server-side-render),根据名称我们都知道叫做服务端渲染,我相信对于工作几年的前端开发者肯定很熟悉这个名词,多少也都用过,在这里,我想说的是针对咱们刚刚踏入前端圈的朋友们的一点总结和感悟,希望我们能够不断的提高对next的认知,相互进步,相互成长!这就是我们的目标~
- 服务端渲染就是页面的渲染和生成由服务器来完成,并将渲染好的页面返回客户端
- 客户端渲染就是页面的生成和数据的渲染过程是在客户端(或浏览器或APP)完成
其中掘金就使用vue的ssr功能做了全栈服务端渲染,切效果良好。
那我们用next.js的优点是什么呢?
2、next.js的特性
- 默认服务端渲染模式,以文件系统为基础的客户端路由
- 代码自动分割使得页面加载更快
- (以页面为基础)的简介的客户端路由
- 以webpack的热替换为基础的开发环境
- 使用React的JSX和ES6的module,模块化和维护更方便
- 可以运行在Express和其他node.js的http服务器上
- 可以定制化专属的babel和webpack配置
那我们开始上手吧,hello world!
3、hello world
照做以下步骤吧~复制代码
接着打开package.json添加如下代码,如下:
{
"scripts": {
"dev": "next"
}}复制代码
到这里位置项目的准备工作已完成,运行以下命令开启项目服务器
npm run dev复制代码
执行完毕后,在浏览器打开localhost:3000,就会看到以下页面:
这是next.js默认生成的404页面,儿开启服务后访问的之所以是现在的404页面,是因为我们还没有设置项目主页。
那接下来就是创建页面了~~~啦啦啦~~~我学next我开心~~~
4、创建页面
next.js是从服务器生成页面,再返回给前端展示。
重点内容开始了哦~~~~复制代码
- next.js默认从pages目录下取页面进行渲染返回给前端展示,并默认取pages/index.js为系统是首页进行展示
- 注意,pages是默认存放页面的目录,路由的根路径也是pages目录。
我们在pages/index.js中创建一个React函数式组件:
const Index = () => (
<div>
<p>小英 study next.js</p>
</div>
)
export default Index复制代码
next.js默认使用webpack构建项目,webpack的热部署功能一样能提升开发效率。创建完pages/index.js后,再访问http://localhost:3000即可看到设置好的页面。
很好,不在是我们不喜欢的404了,看到内容了啊~~
再来构建下多页面的吧~
使用next.js的目的就是构建非SPA的多页面项目,下面开始创建第二个页面。
在pages目录下创建文件pages/content.js
export default () => ( <div> <p>这是content 页面</p> </div>)复制代码
打开路由localhost:3000/content,看到我们新建的第二个页面了。
所以现在我们明白所有的路由都是通过后端服务器来控制的 ,要想实现客户端路由,需要借助next.js的Link API,所以接下来我们看下Link API吧~~
4、Link API
从next/link中可以引用到Link组件。在pages/index.js文件中引用Link,修改如下:
import Link from 'next/link'const Index = () => ( <div> <Link href="/content"> <a>去往content页面</a> </Link> <p>小英 study next.js</p> </div>)export default Index复制代码
我们使用的Link组件,其实可以当做a标签使用,最终渲染的时候也是a标签,页面渲染如下:
点击超链接,即可进入到content页面。下图是实际渲染的结果:
结论:link组件是通过location.history的浏览器API保存历史路由,所以,可以通过浏览器左上角的前进后退按钮来切换历史路由。而在开发过程中,我们不需要单独写客户端路由的配置。
5、组件复用
next.js是以多页面为中心,只要将页面文件放在pages目录下,就可以通过浏览器上以文件名为路由名来访问到,相反,只要不想让用户通过页面直接访问的组件,都不放在pages目录下,开发者想放在哪个目录自定义即可。
接下来,我们建一个components目录,里面建一个公共的Header组件,用于头部导航,通过导航可以再页面间切换。
import Link from 'next/link'const linkStyle = { marginRight: 15}const Header = () => ( <div> <Link href="/"> <a style={linkStyle}>首页</a> </Link> <Link href="/content"> <a style={linkStyle}>内容</a> </Link> </div>)export default Header复制代码
好了,定义完组件,我们在pages/index.js里面引用看一下:
import Header from '../components/Header.js'const Index = () => ( <div> <Header /> <p>小英 study next.js</p> </div>)export default Index复制代码
在 pages/content.js 中同样引入 Header 组件,在浏览器上通过点击导航切换页面。
import Header from '../components/Header.js'export default () => ( <div> <Header/> <p>这是content 页面</p> </div>)复制代码
这样就可以再index和content两个页面来回切换了
进一步我们在封装下Header组件,Layout组件,包含我们的Header和页面content的组件;
/**Layout**组件/
import Header from './Header'const layoutStyle = { margin: 0, padding: 0, border: '1px solid #DDD'}const Layout = (props) => ( <div style={layoutStyle}> <Header/> {props.children} </div>)export default Layout复制代码
/**在index中使用Layout组件**/
import Layout from '../components/Layout.js'import Link from 'next/link'const linkArr = [ '我是链接1', '我是链接2', '我是链接3', '我是链接4', '我是链接5', '我是链接6',]const LinkContent = (props) => ( <li> <Link href={`tolink?title=${props.title}`}> <a>{props.title}</a> </Link> </li>)const Index = () => ( <Layout> <h1>这是我的地盘</h1> <ul> { linkArr.map(item => { return <LinkContent title={item} /> }) } </ul> </Layout>)export default Index复制代码
页面渲染如下:(以下相当于是列表页,点击列表中的链接相当于到详情页,以下就形成了动态页面之间的跳转)
/**详情页**/
import Layout from '../components/Layout.js'export default (props) => ( <Layout> <h1>{props.url.query.title}</h1> <p>这是不同title对应的详情页</p> </Layout>)复制代码
总结:使用next.js创建动态页面,与使用React和Vue创建一个spa的页面大体相同,区别就是页面的渲染主题不同。
- 前者是nodejs服务器获取到后端数据渲染完页面后再返回给前端展示
- 后者是前端先获取页面主体架构,再通过ajax的方式请求后端的数据,在前端渲染展示
6、Route Masking
next.js提供一个独特的特性:路由遮盖(Route Masking)。它可以使得在浏览器上显示的是路由A,而在app内部实际显示的是路由B。这个特性可以使我们的路由简洁,以上边为例,地址栏显示的是http://localhost:3000/tolink?title=%E6%88%91%E6%98%AF%E9%93%BE%E6%8E%A56,这个地址含有个title参数,看着很不整洁吗,接下来我们就要next改造路由,目标是改成http://localhost:3000/p/xiaoying
这里我们要用一个Link组件的as属性,并给组件添加一个id属性:(以index为例)
import Layout from '../components/Layout.js'import Link from 'next/link'const linkArr = [ '我是链接1', '我是链接2', '我是链接3', '我是链接4', '我是链接5', '我是链接6',]const LinkContent = (props) => ( <li> <Link as={`p/${props.id}`} href={`tolink?title=${props.title}`}> <a>{props.title}</a> </Link> </li>)const Index = () => ( <Layout> <h1>这是我的地盘</h1> <ul> { linkArr.map((item,index) => { return <LinkContent id={index} title={item} /> }) } </ul> </Layout>)export default Index复制代码
But。。。。我们刷新下页面,咦怎么404了呢???
结论:
- 当在 Link 组件上使用 as 属性时,浏览器上显示的是 as 属性的值,走的是客户端路由,而服务器真正映射的是 href 属性的值,走的是服务端路由。
- 显示 404页面,是因为路由遮盖默认只在客户端路由中有效,要想在服务端也支持路由遮盖,需要在服务端单独设置路由解析的方法。
7、服务端支持路由遮盖
下面以express为例创建后端服务器讲解如何设置服务器来支持路由遮盖。
先来安装一个express
npm install --save express复制代码
在项目目录下创建server.js文件
const express = require('express')const next = require('next')const dev = process.env.NODE_ENV !== 'production'const app = next({ dev })const handle = app.getRequestHandler()app.prepare().then(() => { const server = express() server.get('/p/:id', (req, res) => { const actualPage = '/tolink' const queryParam = { title: req.param.id } app.render(req, res, actualPage, queryParam) }) server.get('*', (req, res) => { return handle(req, res) }) server.listen(3000, (err) => { if(err) throw err console.log('> ready on http://localhost:3000') })}).catch(ex => { console.log(ex.stack) process.exit(1)})复制代码
/**index.js**/
import Layout from '../components/Layout.js'import { withRouter } from "next/router";class ToLink extends React.Component {
// 没有该函数是不能实现服务端路由遮盖的 static getInitialProps ({ query: { title } }) { return { title } } render() { let { title } = this.props return ( <Layout> <h1>{title}</h1> <p>这是不同title对应的详情页</p> </Layout> ) }} export default withRouter(ToLink)复制代码
这时在tolink页面刷新的时候就能够正常显示页面了,这里强调一点,我在参考一些文章的时候,没有提到加 getInitialProps这个方法,就说页面刷新就能正常显示是不正确的,本人多次试验,页面本身的内容是可以正常显示的,但是根据路由获取的title参数是一直拿不到的,加了这个参数就会先去获取参数,这样页面才能完整显示~~~以下提供截图证明:
所以,接下来我们就说说这个静态的方法:
8、接口请求
next.js在react的基础上为组件新增了一个特性:getInitialProps, 同于获取并处理组件的属性,返回组件的默认属性。我们可以在该方法中请求数据,获取页面需要的数据并渲染返回给前端页面。(上边的服务端路由遮盖也是用了这个方法,原理就是获取组件的默认属性)
引入一个支持在客户端和服务端发送fetch请求的插件isomorphic-unfrtch, 当然我们也可以用axios等其他工具。
npm install --save isomorphic-unfetch复制代码
然后修改pages/index.js:
import Layout from '../components/Layout.js'import { withRouter } from "next/router";import fetch from 'isomorphic-unfetch'import Link from 'next/link'const LinkContent = (props) => { console.log(props) return ( <li> <Link as={`/p/${props.id}`} href={{pathname: '/tolink', query: {title: props.title}}}> <a>{props.title}</a> </Link> </li> )}class Index extends React.Component { static async getInitialProps(){ const res = await fetch('https://api.tvmaze.com/search/shows?q=batman') const data = await res.json() console.log(`Show data fetched. Count: ${data.length}`) return { shows: data } } render() { const { shows } = this.props return( <Layout> <h1>这是我的地盘</h1> <ul> { shows.map(( { show } ) => { return <LinkContent key={show.id} id={show.id} title={show.name} /> }) } </ul> </Layout> ) }}export default withRouter(Index)复制代码
在getInitialProps中我们使用async await关键字异步获取我们需要的参数shows,这样数据就是动态获取的,从而实现动态渲染。注意:getInitialProps不能使用在子组件中。只能使用在pages页面中。
结论如下:
- 当页面渲染时加载数据,我们使用了一个异步方法getInitialProps。它能异步获取 JS 普通对象,并绑定在props上
- 当服务渲染时,getInitialProps将会把数据序列化,就像JSON.stringify。所以确保getInitialProps返回的是一个普通 JS 对象,而不是Date, Map 或 Set类型。
- 当页面初始化加载时,getInitialProps只会加载在服务端。只有当路由跳转(Link组件跳转或 API 方法跳转)时,客户端才会执行getInitialProps。
结论3在这里给大家做个验证:
servert.js, 这是我在server.js中添加的一个测试id,只有在服务端渲染的时候,我们的tolink页面才会获取的这个id
pages/tolink.js,我们在该页面的 getInitialProps方法中打印一下id
这个结果是我在刷新tolink页面,或者初始化加载的时候,在终端(服务端)打印的结果,有“测试id”这几个字;
继续往下看:
这是我通过首页的列表Link的方式点击进来这个页面,在浏览器控制台打印的结果,很明显这是客户端走的这个方法,是没有”测试id“四个字的,而是undefined,所以这也就验证了结论3的正确性~~~~,(其实我刚开始也是不懂这个意思,现在明白了,希望正在看的你也能明白哦~~)
9、部署
Next.js 项目的部署,需要一个 Node.js的服务器,可以选择 Express, Koa 或其他 Nodejs 的Web服务器。本文中以 Express 为例来部署 Next 项目。
为了区分部署环境,我们需要在 package.json 中修改 script 属性如下:
"scripts": {
"build": "next build",
"start": "NODE_ENV=production node server.js -",
"dev": "NODE_ENV=dev node server.js"
}复制代码
其中,build 命令是用于打包项目,start 命令是用于生产环境部署,dev 命令是用于本地开发。
执行如下命令即可将 Next项目 部署到服务器:
npm run build
npm run start
复制代码
执行完命令后,可在 http://host:3000 访问。其中,host 是指服务器的IP地址。
总结:next.js部署生成环境,必须用build命令打包构建,然后再用start命令部署。
next.js还有很多内容需要我们不断总结和采坑,未完待续。。。。。。😃😄