乐趣区

Next.js 入门

欢迎关注我的公众号睿 Talk,获取我最新的文章:
一、前言
当使用 React 开发系统的时候,常常需要配置很多繁琐的参数,如 Webpack 配置、Router 配置和服务器配置等。如果需要做 SEO,要考虑的事情就更多了,怎么让服务端渲染和客户端渲染保持一致是一件很麻烦的事情,需要引入很多第三方库。针对这些问题,Next.js 提供了一个很好的解决方案,使开发人员可以将精力放在业务上,从繁琐的配置中解放出来。下面我们一起来看看它的一些特性。
二、特性介绍
Next.js 具有以下几点特性:

默认支持服务端渲染
自动根据页面进行代码分割
简洁的客户端路由方案(基于页面)
基于 Webpack 的开发环境,支持热模块替换
可以跟 Express 或者其它 Node.js 服务器完美集成
支持 Babel 和 Webpack 的配置项定制

三、Hello World
执行以下命令,开始 Next.js 之旅:
mkdir hello-next
cd hello-next
npm init -y
npm install –save react react-dom next
mkdir pages
在 package.json 中输入以下内容:
{
“scripts”: {
“dev”: “next”,
“build”: “next build”,
“start”: “next start”
}
}
在 pages 文件夹下,新建一个文件 index.js:
const Index = () => (
<div>
<p>Hello Next.js</p>
</div>
)

export default Index
在控制台输入 npm run dev,这时候在浏览器输入 http://localhost:3000,就能看到效果了。

四、路由
Next.js 没有路由配置文件,路由的规则跟 PHP 有点像。只要在 pages 文件夹下创建的文件,都会默认生成以文件名命名的路由。我们新增一个文件看效果 pages/about.js
export default function About() {
return (
<div>
<p>This is the about page</p>
</div>
)
}
在浏览器输入 http://localhost:3000/about,就能看到相应页面了。

如果需要进行页面导航,就要借助 next/link 组件,将 index.js 改写:
import Link from ‘next/link’

const Index = () => (
<div>
<Link href=”/about”>
<a>About Page</a>
</Link>
<p>Hello Next.js</p>
</div>
)

export default Index
这时候就能通过点击链接进行导航了。
如果需要给路由传参数,则使用 query string 的形式:
<Link href=”/post?title=hello”>
<a>About Page</a>
</Link>
取参数的时候,需要借助框架提供的 withRouter 方法,参数封装在 query 对象中:
import {withRouter} from ‘next/router’

const Page = withRouter(props => (
<h1>{props.router.query.title}</h1>
))

export default Page
如果希望浏览器地址栏不显示 query string,可以使用 as 属性:
<Link as={`/p/${props.id}`} href={`/post?id=${props.id}`}
<a>{props.title}</a>
</Link>
这时候浏览器会显示这样的 url:localhost:3000/p/12345
五、SSR
Next.js 对服务端渲染做了封装,只要遵守一些简单的约定,就能实现 SSR 功能,减少了大量配置服务器的时间。以上面这个 url 为例子,直接在浏览器输入 localhost:3000/p/12345 是会返回 404 的,我们需要自己实现服务端路由处理的逻辑。下面以 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()

// 处理 localhost:3000/p/12345 路由的代码
server.get(‘/p/:id’, (req, res) => {
const actualPage = ‘/post’
const queryParams = {title: req.params.id}
app.render(req, res, actualPage, queryParams)
})

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.error(ex.stack)
process.exit(1)
})
当遇到 /p/:id 这种路由的时候,会调用 app.render 方法渲染页面,其它的路由则调用 app.getRequestHandler 方法。
无论是服务端渲染还是客户端渲染,往往都需要发起网络请求获取展示数据。如果要同时考虑 2 种渲染场景,可以用 getInitialProps 这个方法:
import Layout from ‘../components/MyLayout.js’
import fetch from ‘isomorphic-unfetch’

const Post = props => (
<Layout>
<h1>{props.show.name}</h1>
<p>{props.show.summary.replace(/<[/]?p>/g, ”)}</p>
<img src={props.show.image.medium} />
</Layout>
)

Post.getInitialProps = async function(context) {
const {id} = context.query
const res = await fetch(`https://api.tvmaze.com/shows/${id}`)
const show = await res.json()

console.log(`Fetched show: ${show.name}`)

return {show}
}

export default Post
获取数据后,组件的 props 就能获取到 getInitialProps return 的对象,render 的时候就能直接使用了。getInitialProps 是组件的静态方法,无论服务端渲染还是客户端渲染都会调用。如果需要获取 url 带过来的参数,可以从 context.query 里面取。
六、CSS in JS
对于页面样式,Next.js 官方推荐使用 CSS in JS 的方式,并且内置了 styled-jsx。用法如下:
import Layout from ‘../components/MyLayout.js’
import Link from ‘next/link’

function getPosts() {
return [
{id: ‘hello-nextjs’, title: ‘Hello Next.js’},
{id: ‘learn-nextjs’, title: ‘Learn Next.js is awesome’},
{id: ‘deploy-nextjs’, title: ‘Deploy apps with ZEIT’}
]
}

export default function Blog() {
return (
<Layout>
<h1>My Blog</h1>
<ul>
{getPosts().map(post => (
<li key={post.id}>
<Link as={`/p/${post.id}`} href={`/post?title=${post.title}`}>
<a>{post.title}</a>
</Link>
</li>
))}
</ul>
<style jsx>{`
h1,
a {
font-family: ‘Arial’;
}

ul {
padding: 0;
}

li {
list-style: none;
margin: 5px 0;
}

a {
text-decoration: none;
color: blue;
}

a:hover {
opacity: 0.6;
}
`}</style>
</Layout>
)
}
注意 <style jsx> 后面跟的是模板字符串,而不是直接写样式。
七、导出为静态页面
如果网站都是简单的静态页面,不需要进行网络请求,Next.js 可以将整个网站导出为多个静态页面,不需要进行服务端或客户端动态渲染了。为了实现这个功能,需要在根目录新建一个 next.config.js 配置文件:
module.exports = {
exportPathMap: function() {
return {
‘/’: {page: ‘/’},
‘/about’: {page: ‘/about’},
‘/p/hello-nextjs’: {page: ‘/post’, query: { title: ‘Hello Next.js’} },
‘/p/learn-nextjs’: {page: ‘/post’, query: { title: ‘Learn Next.js is awesome’} },
‘/p/deploy-nextjs’: {page: ‘/post’, query: { title: ‘Deploy apps with Zeit’} }
}
}
}
这个配置文件定义了 5 个需要导出的页面,以及这些页面对应的组件和需要接收的参数。然后在 package.json 定义下面 2 个命令,然后跑一下:
{
“scripts”: {
“build”: “next build”,
“export”: “next export”
}
}

npm run build
npm run export
跑完后根目录就会多出一个 out 文件夹,所有静态页面都在里面。

八、组件懒加载
Next.js 默认按照页面路由来分包加载。如果希望对一些特别大的组件做按需加载时,可以使用框架提供的 next/dynamic 工具函数。
import dynamic from ‘next/dynamic’

const Highlight = dynamic(import(‘react-highlight’))

export default class PostPage extends React.Component {
renderMarkdown() {
if (this.props.content) {
return (
<div>
<Highlight innerHTML>{this.props.content}</Highlight>
</div>
)
}

return (<div> no content </div>);
}

render() {
return (
<MyLayout>
<h1>{this.props.title}</h1>
{this.renderMarkdown()}
</MyLayout>
)
}
}
}
当 this.props.content 为空的时候,Highlight 组件不会被加载,加速了页面的展现,从而实现按需加载的效果。
九、总结
本文介绍了 Next.js 的一些特性和使用方法。它最大的特点是践行约定大于配置思想,简化了前端开发中一些常用功能的配置工作,包括页面路由、SSR 和组件懒加载等,大大提升了开发效率。
更详细的使用介绍请看官方文档。

退出移动版