在瞭解Next.js之前,需要掌握React的基本使用方法。
參考代碼:https://github.com/chkui/nextjs-getting-started 。
搭建
安裝
# 創建項目目錄 mkdir you_project # 進入項目目錄 cd you_project # 初始化package.json npm init -y # 安裝依賴包 npm install --save react react-dom next # 創建一個pages文件夾 mkdir pages
依次執行以上命令之後,Next.js運行所需的最基本的目錄和依賴就創建好了。
運行
將package.json裏的“scripts"字段修改爲:
{ "scripts": { "dev": "next", "build": "next build", "start": "next start" } }
運行以下命令啓動Next.js:
npm run dev
在瀏覽器打開http://localhost:3000/ 看到輸出"404 - This page could not be found",表示Next.js安裝成功。
添加頁面
./pages是Next.js默認的網頁路徑,其中的index.js就代表整個網站的主頁。創建一個*./pages/index.js*組件:
const Index = () => ( <div> <p>Hello World!</p> </div> ) export default Index
添加*./pages/index.js*後網站會自動刷新,呈現"Hello World!"。
頁面與導航欄
頁面
添加http://localhost:3000/about 路徑下的頁面。
創建*./pages/about.js*文件,添加以下內容:
export default () => ( <div> <p>About page</p> </div> )
然後在瀏覽器輸入http://localhost:3000/about 即可看到新增的About。
導航欄
對*./pages/index.js*稍加修改引入導航欄功能:
import Link from 'next/link' const Index = () => ( <div> <Link href="/about"> <a style={{fontSize: 20}}>About Page</a> </Link> <p>Hello Next.js</p> </div> ) export default Index
注意:使用了Next.js作爲服務端渲染工具,切記僅使用next/link中的Link組件。
除了<a>
標籤,<button>
或自定義的組件都可以被Link
包裝,只要傳遞Click事件即可,將上面的代碼稍作修改實驗這個效果:
import Link from 'next/link' const Index = () => ( <div> <Link href="/about"> <button>Click Me</button> </Link> <Link href="/about"> <A/> </Link> <p>Hello Next.js</p> </div> ) export default Index const A = props => (<div onClick={e => { props.onClick(e) }}>Click Me</div>)
關於Next.js路由管理相關的細節內容,可以到這裏查看
頁面、資源與組件
./pages是一個保留路徑,在*/pages*路徑下任何js文件中導出的默認React組件都被視作一個頁面。
除了*./pages*,Next.js還有一個保留路徑是*./static*,它用來存放圖片等靜態資源。
Next.js會對*./pages中的React組件進行“包裝",所以./pages*內外的React組件在呈現結果上有一些差異,看下面的例子。
創建網站結構
在工程根目錄創建*/components*文件夾,然後添加以下組件:
import Link from 'next/link' const linkStyle = { marginRight: 15 } const Header = () => ( <div> <Link href="/"> <a style={linkStyle}>Home</a> </Link> <Link href="/about"> <a style={linkStyle}>About</a> </Link> </div> ) export default Header
然後將Header
整合到about.js
和index.js
中:
import Header from '../components/Header' export default () => ( <div> <Header /> <p>Hello Next.js</p> </div> )
再次進行頁面操作,就會出現表頭靜止頁面變換的效果。
網站佈局
通常情況下,開發一個網站先制定一個通用的佈局(尤其是響應式佈局的網站),然後再向佈局中的添加各個部分的內容。使用Next.js可以通過組件的方式來設計一個佈局,看下面的例子。 在*/components*中增加Layout
和Footer
組件:
// componments/layout.js import Header from './header' import Footer from './footer' const layoutStyle = { margin: 20, padding: 20, border: '1px solid #DDD' } const Layout = (props) => ( <div style={layoutStyle}> <Header /> {props.children} <Footer /> </div> ) export default Layout
// components/footer.js const Footer = () => ( <div> <p style={{color:'blue'}}>Footer</p> </div> ) export default Footer
然後將*/pages/index.js*修改爲:
import Layout from '../components/layout' export default () => ( <Layout> <p>Hello Next.js</p> </Layout> )
這樣,頁面的內容和佈局就完全隔離開了。
頁面跳轉
傳遞參數
在實際應用中,經常需要在頁面間傳遞參數,可以使用高階組件withRouter
來實現。 下面的代碼對*/pages/index.js進行了一些修改,使其在跳轉時攜帶query*參數:
const SubLink = props => ( <li> <Link href={`/post?title=${props.title}`}> <a>{props.title}</a> </Link> </li> ) export default () => ( <Layout> <h2>Information</h2> <SubLink title="First Post"/> <SubLink title="Second Post"/> <SubLink title="Third Post"/> </Layout> )
點擊First Post之後瀏覽器的URL會出現這樣的路徑:“http://localhost:3000/post?title=First%20Post” 。接下來利用withRouter
來獲取這個參數。創建*./pages/post.js*的文件:
import {withRouter} from 'next/router' import Layout from '../components/layout' const Page = withRouter((props) => ( <Layout> <h3>Post Page</h3> <p>Info:{props.router.query.title}</p> </Layout> )) export default Page
現在點擊First Post鏈接之後,跳轉的頁面會顯示First Post。
路徑隱藏
Next.js提供了一個讓URL更加清晰乾淨的特性功能——URL隱藏(官網直譯的話應該叫“URL遮擋”),他的作用是可以隱藏原來比較複雜的URL,讓網站路徑更加清晰,有利於SEO等。實現這個特性非常簡單,在使用Link
組件時傳遞一個as參數。下面將繼續修改*./pages/index.js*中的內容以實現這個特性:
const SubLink = props => ( <li> <Link as={`p/${props.as}`} href={`/post?title=${props.title}`}> <a>{props.title}</a> </Link> </li> ) export default () => ( <Layout> <h2>Information</h2> <SubLink as="first-post" title="First Post"/> <SubLink as="first-post" title="Second Post"/> <SubLink as="first-post" title="Third Post"/> </Layout> )
注意觀察SubLink
組件中的修改,爲Link
增加了一個as參數,這個參數傳遞的內容將會在瀏覽器的地址欄顯示。例如點擊FIrst Post後,瀏覽器的地址欄會顯示http://localhost:3000/p/first-post ,但是我們通過withRouter
組件獲取的URL還是href傳遞的路徑。
服務端渲染
只要運行了Next.js,他時時刻刻都在執行服務端渲染,可以通過刷新頁面看到效果。如果沒有太多需求,不進行任何調整Next.js能爲我們完成靜態頁面的服務端渲染,但是通常情況下,還需要處理異步請求等等情況。
二次服務端渲染
前面介紹了在Link
組件上使用as參數可以設置瀏覽器路徑欄上顯示的內容。但是這個時候僅僅支持客戶端跳轉,如果進行頁面刷新會出現404頁面。導致這個問題出現的原因是在服務端並不知道*/p/first-post對應/pages*文件夾中的哪個文件。爲了解決這個問題,需要在服務端進行二次渲染。
首先需要添加Express服務:
npm install --save express
安裝完成之後在根目錄添加一個server.js文件,其內容如下:
const express = require('express') const next = require('next') // 不等於'production'則表示運行的是開發環境 const dev = process.env.NODE_ENV !== 'production' // 創建一個服務端運行的Next app const app = next({dev}) // 請求處理器 const handle = app.getRequestHandler() app.prepare() .then(() => { const server = express() server.get('/p/:id', (req, res) => { //將/p/:id的路徑切換成/post?title=req.params.id的路徑 app.render(req, res, '/post', {title: req.params.id}) }) 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) })
然後修改package.json的“scripts"字段,將啓動方式方式指向server.js:
"scripts": { "dev": "node server.js", "build": "next build", "start": "NODE_ENV=production node server.js" }
完成這2步網站服務端也可以正常跳轉,實現功能的位置是這段代碼:
server.get('/p/:id', (req, res) => { app.render(req, res, '/post', {title: req.params.id}) })
他將原來的請求“/p/:id”轉換爲請求"/post?title=id"。
更多的服務端渲染的配置說明請看這裏。
數據異步請求
對於一個前後端分離的系統來說,異步數據請求是幾乎每個頁面都需要的。Next.js通過getInitialProps
來實現。 下面的示例數據來自https://www.tvmaze.com/api 。創建*./pages/tvshows.js*的文件:
import Layout from '../components/layout.js' import Link from 'next/link' import fetch from 'isomorphic-unfetch' const TvShow = (props) => ( <Layout> <h1>Batman TV Shows</h1> <ul> {props.shows.map(({show}) => ( <li key={show.id}> <Link href={`/tv?id=${show.id}`}> <a>{show.name}</a> </Link> </li> ))} </ul> </Layout> ) TvShow.getInitialProps = async function() { //contxt是銜接Next.js包裝組件和自定義主鍵的上下文,包含的參數有asPath、pathname、query // 發送異步請求 const res = await fetch('https://api.tvmaze.com/search/shows?q=batman') // 從response中異步讀取數據流 const data = await res.json() console.log(`Show data fetched. Count: ${data.length}`) // 返回已獲取的數據 return { shows: data } } export default TvShow
TvShow
組件的作用是異步請求數據並組裝成列表展示。
然後再創建一個查看詳情的頁面——./pages/tv.js,實現過程和上面一樣:
import Layout from '../components/layout' import fetch from 'isomorphic-unfetch' const Tv = (props) => ( <Layout> <h1>{props.show.name}</h1> <p>{props.show.summary.replace(/<[/]?p>/g, '')}</p> <img src={props.show.image.medium}/> </Layout> ) Tv.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 Tv
按照這個套路可以解決絕大部分數據異步請求的問題。不過如果數據組裝過慢,會出頁面現卡頓的問題,可以通過服務端緩存或異步頁面加載實現,後續的篇幅會介紹。
樣式
源生添加樣式
一個頁面永遠離不開樣式,在Next.js中推薦一種簡介高效的方法——<style jsx>
。
爲的主頁添加一些樣式:
( <Layout> <h2>Information</h2> <SubLink as="first-post" title="First Post"/> <SubLink as="first-post" title="Second Post"/> <SubLink as="first-post" title="Third Post"/> <style jsx>{` h2{ font-family: "Arial"; } `}</style> <style jsx global>{` .list{ list-style: none; margin: 5px 0; } `}</style> </Layout> )
<style jsx>
的作用就是爲當前組件聲明樣式,需要注意的是在這個標籤內聲明的樣式只能覆蓋當前組件,子組件是不會出現層疊效果的。而<style jsx global>
標籤的效果則是和標準的css層疊效果一致,在這個標籤中聲明的樣式會影響到子組件。
Loader添加載樣式
Next.js可以加載各種樣式文件,下面以Sass/Scss爲例。
首先添加相關依賴:
npm install --save @zeit/next-sass node-sass
在項目根目錄添加next.config.js文件,用於指示Next加載對用的功能:
const withSass = require('@zeit/next-sass') module.exports = withSass()
現在就可以加載*.scss文件了,添加一個/pages/post.scss*文件:
$font-size: 50px; .header{ font-size: $font-size; color:red; }
修改*/pages/post.js*加載樣式:
import {withRouter} from 'next/router' import Layout from '../components/layout' //加載樣式 import './post.scss' const Page = withRouter((props) => ( <Layout> <h3 className="header">Post Page</h3> <p>Info:{props.router.query.title}</p> </Layout> )) export default Page
由於是使用的webpack的Loader,可以根據需要在next.config.js文件中進行一些相關的設置:
module.exports = withSass({ cssModules: true, cssLoaderOptions: { importLoaders: 1, localIdentName: "[local]___[hash:base64:5]", } })
然後在組件中直接以對象的方式使用:
import style from './post.scss' const Page = withRouter((props) => { console.log(style) return ( <Layout> <h3 className={style.header}>Post Page</h3> <p>Info:{props.router.query.title}</p> </Layout> ) })
更多關於cssLoaderOptions的參數說明可以查看webpack裏css-loader的options說明。除了scss,Next.js還支持css、less、post css的Loader。
發佈
在瞭解以上內容之後,已經可以開發一個網站了,接下來介紹如何發佈生產包。
package.json中的“scripts"字段可以設置打包和生產運行方式:
"scripts": { "dev": "node server.js", "build": "next build", "start": "NODE_ENV=production node server.js" }
首先進行打包:
npm run build
打包完畢之後可以啓動生產環境:
npm start
現在用瀏覽器打開http://localhost:3000/ 地址可以發現運行的是生產環境(可以使用React工具查看,也可以打開開發人員模式)。 由於之前了在server.js中引入了Express,所以現在啓動的是一個Express服務器。打包之後的文件都在*./.next* 路徑下,可以僅僅拷貝依賴包(node_module)、package.json、server.js以及**./.next**來運行生產環境。
除了使用Express這一類第三方nodejs服務器,Next.js還提供了許多其他方式來部署和方法