参加或留意了最近举行的JSConf CN 2017的同窗,想必对 Next.js 再也不陌生, Next.js 的做者之一到场进行了精彩的演讲。其实在更早些时候,由 Facebook 举办的 React Conf 2017,他就到场并有近40分钟的分享。但两次分享带来的 demo 都是 hacker news。我观察 Next.js 时间较长,看着它从1.x 版本一直到了今天的 3.x,终于决定写一篇入门级的新手指导文章。而这篇文章试图经过一个全新的例子,来让你们了解 Next.js 究竟是如何与 React 配合,达到服务端渲染的。css
“React universal” 是社区上形容基于 React 构建 web 应用,并采用“服务端渲染”方式的一个词语。也许不少人对 “isomorphic” 这个单词更加熟悉,其实这两个词语想要表达的概念相似。今天这篇文章显然不是讨论这两个词语的,咱们要尝试使用最新版 Next.js,构件一个简单的服务端渲染 React 应用。前端
最终项目地址能够点击这里查看。node
React app 实现了虚拟 DOM,来实现对真实 DOM 的抽象。这样的设计迅速引领了前端开发浪潮。可是 “Every great thing comes with a price”,虚拟 DOM 一样带来了一些弊端,好比在先后端分离的开发模式下,SEO就成了问题;一样首屏加载时间变长,各类 loading 消磨人的耐心。就像下面截图所展示的那样:react
Universal 应用架构能够简单粗暴先而片面的理解成应用将在客户端和服务端共同完成渲染。这样取代了彻底由客户端渲染(先后端分离方式)模式。在 React 场景下,咱们可使用 React 自身的 renderToString 完成服务端初次渲染。可是若是咱们每次手动来完成这些过程,手动实现服务端繁琐配置,不免使人头大心烦。webpack
Next.js 的出现,就是为你解决这种恼人的问题。咱们先来认识一下它的几个原则和思想:ios
其实关于更多的 Next.js 设计理念我不想再赘述了,读者均可以在其官网找到丰富的内容。下面,我将使用 Football Data API 来简单开发一个基于 Next.js 的应用,这个应用将展示英超联赛的实时积分榜。同时包含了简单的路由开发和页面跳转。git
相信全部的开发者都厌恶超长时间的安装和各类依赖、插件配置。不要担忧,Next.js 做为一个独立的 npm package 最大限度的替你完成了不少耗时且无趣的工做。咱们首先须要进行安装:github
# Start a new project
npm init
# Install Next.js
npm install next --save复制代码
安装结束后,咱们就能够开启脚本:web
"scripts": {
"start": "next"
},复制代码
Next 安装的同时,也会安装 React,因此无需本身费心。接下来所须要作的很简单,就是在根目录下建立一个 pages 文件夹,并在其下新建一个 index.js 文件:ajax
// ./pages/index.js
// Import React
import React from 'react'
// Export an anonymous arrow function
// which returns the template
export default () => (
<h1>This is just so easy!</h1>
)复制代码
好了,如今就能够直接看到结果:
# Start your app
npm start复制代码
验证一下它来自服务端渲染:
就是这么简单,清新。若是咱们本身手段实现这一切的话,除了 NodeJS 的种种繁琐不说,webpack 配置,node_modules 依赖,babel插件等等就够折腾半天的了。
在 ./pages/index.js 文件内,咱们能够添加页面 head 标签、meta 信息、样式资源等等:
// ./pages/index.js
import React from 'react'
// Import the Head Component
import Head from 'next/head'
export default () => (
<div>
<Head>
<title>League Table</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
</Head>
<h1>This is just so easy!</h1>
</div>
)复制代码
这个 head 固然不是指真实的 DOM,千万别忘了 React 虚拟 DOM 的概念。其实这是 Next 提供的 Head 组件,不过最终必定仍是被渲染成为真实的 head 标签。
Next 还提供了 getInitialProps 方法,这个方法支持异步选项,而且是服务端/客户端同构的。咱们可使用 async/await 方式,处理异步请求。请看下面的示例:
import React from 'react'
import Head from 'next/head'
import axios from 'axios';
export default class extends React.Component {
// Async operation with getInitialProps
static async getInitialProps () {
// res is assigned the response once the axios
// async get is completed
const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable');
// Return properties
return {data: res.data}
}
}复制代码
咱们使用了 axios 类库来发送 HTTP 请求。网络请求是异步的,所以咱们须要在将来某个合适的时候(请求结果返回时)接收数据。这里使用先进的 async/await,以同步的方式处理,从而避免了回调嵌套和 promises 链。
咱们将异步得到的数据返回,它将自动挂载在 props 上(注意 getInitialProps 方法名,顾名思义),render 方法里即可以经过 this.props.data 获取:
import React from 'react'
import Head from 'next/head'
import axios from 'axios';
export default class extends React.Component {
static async getInitialProps () {
const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable');
return {data: res.data}
}
render () {
return (
<div>
<Head>
......
</Head>
<div className="pure-g">
<div className="pure-u-1-3"></div>
<div className="pure-u-1-3">
<h1>Barclays Premier League</h1>
<table className="pure-table">
<thead>
<tr>
......
</tr>
</thead>
<tbody>
{this.props.data.standing.map((standing, i) => {
const oddOrNot = i % 2 == 1 ? "pure-table-odd" : "";
return (
<tr key={i} className={oddOrNot}>
<td>{standing.position}</td>
<td><img className="pure-img logo" src={standing.crestURI}/></td>
<td>{standing.points}</td>
<td>{standing.goals}</td>
<td>{standing.wins}</td>
<td>{standing.draws}</td>
<td>{standing.losses}</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="pure-u-1-3"></div>
</div>
</div>
);
}
}复制代码
这样,再访问咱们的页面,就有了:
也许你已经有所感知:咱们已经有了最基本的一个路由。Next 不须要任何额外的路由配置信息,你只须要在 pages 文件夹下新建文件,每个文件都将是一个独立的页面。
让咱们来新建一个 team 页面吧!新建 ./pages/details.js 文件:
// ./pages/details.js
import React from 'react'
export default () => (
<p>Coming soon. . .!</p>
)复制代码
咱们使用 Next 已经准备好的组件 来进行页面跳转:
// ./pages/details.js
import React from 'react'
// Import Link from next
import Link from 'next/link'
export default () => (
<div>
<p>Coming soon. . .!</p>
<Link href="/"><a>Go Home</a></Link>
</div>
)复制代码
这个页面不能老是 “Coming soon. . .!” 的信息,咱们来进行完善以展现更多内容,经过页面 URL 的 query id 变量,咱们来请求并展示当前相应队伍的信息:
import React from 'react'
import Head from 'next/head'
import Link from 'next/link'
import axios from 'axios';
export default class extends React.Component {
static async getInitialProps ({query}) {
// Get id from query
const id = query.id;
if(!process.browser) {
// Still on the server so make a request
const res = await axios.get('http://api.football-data.org/v1/competitions/426/leagueTable')
return {
data: res.data,
// Filter and return data based on query
standing: res.data.standing.filter(s => s.position == id)
}
} else {
// Not on the server just navigating so use
// the cache
const bplData = JSON.parse(sessionStorage.getItem('bpl'));
// Filter and return data based on query
return {standing: bplData.standing.filter(s => s.position == id)}
}
}
componentDidMount () {
// Cache data in localStorage if
// not already cached
if(!sessionStorage.getItem('bpl')) sessionStorage.setItem('bpl', JSON.stringify(this.props.data))
}
// . . . render method truncated
}复制代码
这个页面根据 query 变量,动态展示出球队信息。具体来看,getInitialProps 方法获取 URL query id,根据 id 筛选出(filter 方法)展现信息。有意思的是,由于一直球队的信息比较稳定,因此在客户端使用了 sessionStorage 进行存储。
完整的 render 方法:
// . . . truncated
export default class extends React.Component {
// . . . truncated
render() {
const detailStyle = {
ul: {
marginTop: '100px'
}
}
return (
<div>
<Head>
<title>League Table</title>
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<link rel="stylesheet" href="https://unpkg.com/purecss@0.6.1/build/pure-min.css" />
</Head>
<div className="pure-g">
<div className="pure-u-8-24"></div>
<div className="pure-u-4-24">
<h2>{this.props.standing[0].teamName}</h2>
<img src={this.props.standing[0].crestURI} className="pure-img"/>
<h3>Points: {this.props.standing[0].points}</h3>
</div>
<div className="pure-u-12-24">
<ul style={detailStyle.ul}>
<li><strong>Goals</strong>: {this.props.standing[0].goals}</li>
<li><strong>Wins</strong>: {this.props.standing[0].wins}</li>
<li><strong>Losses</strong>: {this.props.standing[0].losses}</li>
<li><strong>Draws</strong>: {this.props.standing[0].draws}</li>
<li><strong>Goals Against</strong>: {this.props.standing[0].goalsAgainst}</li>
<li><strong>Goal Difference</strong>: {this.props.standing[0].goalDifference}</li>
<li><strong>Played</strong>: {this.props.standing[0].playedGames}</li>
</ul>
<Link href="/">Home</Link>
</div>
</div>
</div>
)
}
}复制代码
注意下面截图中,同一页面不一样 query 值,分别展现了冠军🏆切尔西和曼联的信息。
别忘了咱们的主页(排行榜页面)index.js 中,也要使用相应的 sessionStorage 逻辑。同时,在 render 方法里加入一条连接到详情页的 :
<td><Link href={`/details?id=${standing.position}`}>More...</Link></td>复制代码
在 Next 中,咱们一样能够经过 error.js 文件定义错误页面。在 ./pages 下新建 error.js:
// ./pages/_error.js
import React from 'react'
export default class Error extends React.Component {
static getInitialProps ({ res, xhr }) {
const statusCode = res ? res.statusCode : (xhr ? xhr.status : null)
return { statusCode }
}
render () {
return (
<p>{
this.props.statusCode
? `An error ${this.props.statusCode} occurred on server`
: 'An error occurred on client'
}</p>
)
}
}复制代码
当传统状况下页面404时,获得:
在咱们设置 _ error.js 以后,便有:
这篇文章实现了一个简易 demo,只是介绍了最基本的 Next.JS 搭建 React 同构应用的基本步骤。
想一想你是否厌烦了 webpack 恼人的配置?是否对于 Babel 各类插件云里雾里?
使用 Next.js,简单、清新而又设计良好。这也是它在推出短短期以来,便迅速走红的缘由之一。
除此以外,Next 还有很是多的功能,很是多的先进理念能够应用。
最后,对于这些本文章没有演示到的功能是否有些手痒?感兴趣的读者能够关注本文 demo 的Github项目地址,本身手动尝试起来吧~
本文意译了Chris Nwamba的:React Universal with Next.js: Server-side React 一文,并对原文进行了升级,兼容了最新的 Next 设计。
个人其余关于 React 文章:
Happy Coding!