歡迎關注我的公衆號睿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 和組件懶加載等,大大提升了開發效率。
更詳細的使用介紹請看官方文檔。