選擇Next.js來實(shí)現(xiàn)React的服務(wù)端渲染主要是我怕麻煩拦宣,作為前端菜鳥的我來說一些要配置的我都是拒絕的垮刹!因?yàn)镹extJs能夠簡單的實(shí)現(xiàn)服務(wù)端的渲染。
自己寫了個(gè)構(gòu)建nextjs應(yīng)用的腳手架工具:
這是一個(gè)快速構(gòu)建nextjs應(yīng)用的工具
那為什么要選擇Typescript呢种蝶,因?yàn)樗宩avascript更嚴(yán)謹(jǐn)了,對(duì)構(gòu)建大型應(yīng)用是友好的酗电。
Typescript中文網(wǎng)
為什么都是中文網(wǎng)呢撵术,因?yàn)槲矣⑽牟缓谩?/p>
想看Typescript+antd+nextjs點(diǎn)擊這里已經(jīng)上傳到github上了
編寫的nextjs網(wǎng)站
可達(dá)校園官網(wǎng)
莆田搜鞋網(wǎng)
大虎鞋業(yè)
成之恒人力資源
上車了
1. 創(chuàng)建項(xiàng)目文件夾
mkdir nextjs-demo
cd nextjs-demo
然后進(jìn)入項(xiàng)目文件夾
2. 安裝需要的依賴
在安裝依賴前先npm init -y
添加包描述文件package.json
然后安裝依賴
npm i --save next react react-dom @zeit/next-typescript typescript
npm i --save-dev express
就能在package.json
中看到如下的依賴
3. 先來個(gè)簡單的栗子讓它跑起來
使用你順手的編輯器打開這個(gè)項(xiàng)目寝姿,在項(xiàng)目的根目錄下新建一個(gè)名為pages
的文件夾埃篓,一定要叫這個(gè)名字呢架专,因?yàn)镹extjs只會(huì)識(shí)別根目錄下的pages
里面的js|jsx|ts|tsx
文件來做路由胶征。不過 typescript文件是后面我配置的案狠。然后在pages里面新建一個(gè)index.js
文件骂铁,一開始還是只使用javascript文件灿椅,等到后面配置了typescript再改為.tsx
后綴的文件茫蛹。
export default () => (<div>hello nextjs</div>)
為了讓這個(gè)demo跑起來要在package.json
中的scripts新建如下的npm script
開始只用到
dev
這個(gè)命令 其他的后面會(huì)用到建好之后在命令行敲入
npm run dev // or yarn dev
默認(rèn)是在:3000
端口撼嗓,打開瀏覽器在瀏覽器輸入http://localhost:3000
就能看到hello nextjs
的文案啦且警。
別忘了肩刃,
nextjs
這玩意兒可是服務(wù)端渲染树酪,查看一下頁面源碼是的 hello nextjs 這段文案在源碼中顯示了垂谢。爬蟲就可以爬到它了滥朱。
好了 在稍微了解一點(diǎn)nextjs后來玩一些稍稍復(fù)雜一點(diǎn)的東西配上Typescript來實(shí)現(xiàn)一個(gè)簡單demo
剩下的頁面查看我部署到服務(wù)器上面的吧 nextj-demo
中途換乘
1. 配置Typescript
1.1 在根目錄下添加.babelrc
文件在里面敲入如下代碼
{
"presets": [
"next/babel",
"@zeit/next-typescript/babel"
]
}
babel方面的知識(shí)這里就不介紹哈。
1.2 在根目錄下添加tsconfig.json
文件缰犁,敲入如下代碼
{
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"jsx": "preserve",
"lib": ["dom", "es2017"],
"module": "esnext",
"moduleResolution": "node",
"noEmit": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"preserveConstEnums": true,
"removeComments": false,
"skipLibCheck": true,
"sourceMap": true,
"strict": false,
"target": "esnext"
}
}
這是typescript的配置文件帅容。
1.3 再在根目錄下新建一個(gè)next.config.js
的文件這個(gè)是nextjs的自定義配置文件并徘。具體查看nextjs自定義配置
// next.config.js
const withTypescript = require('@zeit/next-typescript')
// const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin')
/**
* next的配置文件,支持配置嵌套
*/
module.exports = withTypescript({
webpack(config, options) {
// 這里面還可以再配置哦 最后一個(gè)要return
// if (options.isServer) config.plugins.push(new ForkTsCheckerWebpackPlugin())
return config
}
})
好了,到這里Typescript已經(jīng)可以用啦简肴,打包什么的也不會(huì)出現(xiàn)問題
2.demo項(xiàng)目結(jié)構(gòu)
所以呢這個(gè)demo的項(xiàng)目結(jié)構(gòu)最終展示如下
components
:存放那種復(fù)用的組件
layouts
:存放布局的組件佣渴,比如大部分頁面采用的布局就是一個(gè) header 和一個(gè)footer辛润,具體的頁面內(nèi)容展示在header和footer之間真椿。
pages
:我們要路由的頁面哈测摔。
static
:存放靜態(tài)文件的地方,一定要叫static挟纱!這里我存放了一張圖片胸竞。
server.js
:一個(gè)等下測(cè)試用服務(wù)器
其他的之前也有介紹過了
3.代碼編寫
改造一下pages下面的index.js入口文件
// pages/index.js
import Home from './Home/home'
export default () => {
return (
<div>
<Home></Home>
{/* hello nextjs */}
</div>
)
}
里面我引入了一個(gè)home
的組件
// pages/Home/home.tsx
import HeaderFooter from '../../layouts/HeaderFooter'
import dynamic from 'next/dynamic'
const Olddriver = dynamic(import('../../components/OldDriver'),{
loading: () => <p>loading...</p>
})
export default () => {
return (
<HeaderFooter active="home">
<div id="homepage">
<Olddriver></Olddriver>
</div>
<style>{`
#homepage {
width: 100%;
height:600px;
background-color: #f7f7f7;
display: flex;
justify-content: center;
align-items: center
}
`}</style>
</HeaderFooter>
)
}
引入了布局組件HeaderFooter
這個(gè)組件等下來講解拧揽,這里面還用了nextjs的一個(gè)導(dǎo)入動(dòng)態(tài)組件的函數(shù)
// layouts/HeaderFooter.tsx
import Header from '../components/Header'
import Footer from '../components/Footer'
export default (props) => {
return (
<div>
<Header active={props.active}></Header>
<div id="LayoutContainer">
{props.children}
</div>
<Footer></Footer>
<style>{`
#LayoutContainer {
min-height: 600px;
background-color: #f7f7f7
}
`}</style>
<style global jsx> {`
* {
margin: 0;
padding: 0;
}
li {
list-style: none
}
`}
</style>
</div>
)
}
分別把Header和Footer組件引進(jìn)來痒谴,在代碼中<Header active={props.active}></Header>
中的active是我用來實(shí)現(xiàn)頭部幾個(gè)菜單在對(duì)應(yīng)頁面高亮的實(shí)現(xiàn)。css方面則使用了styled-jsx來生成獨(dú)立作用域的css也很好用尽爆。
下面是Header和Footer 中的代碼
// components/Header.tsx
import Link from 'next/link'
export default (props) => {
return (
<div>
<div className="header">
<div className="header-inner">
<Link href="/">
<h2 style={{cursor: 'pointer'}} className="logo">YoPo</h2>
</Link>
<div className="nav-bar">
<Link href="/Home/home">
<div className="bar" style={props.active === 'home' ? {color: '#09c'} : {}}>Home</div>
</Link>
<Link href="/Aboutus/aboutus">
<div className="bar" style={props.active === 'aboutus' ? {color: '#09c'} : {}}>AboutUs</div>
</Link>
<Link href="/Helps/helps">
<div className="bar" style={props.active === 'helps' ? {color: '#09c'} : {}}>Helps</div>
</Link>
</div>
</div>
</div>
<style>{`
.header {
width: 100%;
border-bottom: 1px solid #eee;
padding: 0 70px;
box-sizing: border-box
}
.header .header-inner {
width: 100%;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
height: 50px;
}
.logo {
font-size: 32;
}
.nav-bar {
display: flex;
aligin-items: center
}
.nav-bar .bar {
padding: 0 10px;
height: 50px;
line-height: 50px;
text-align: center;
font-size: 13px;
color: #555555;
cursor: pointer
}
.nav-bar .bar:hover {
color: #0099cc
}
`}
</style>
</div>
)
}
// components/Footer.tsx
export default () => {
return (
<div>
<div className="footer">
<div className="footer-inner">
Copyright ? 2018-∞ WWW.GETLAID.CN All Rights Reserved 版權(quán)歸YoPo所有
</div>
</div>
<style>{`
.footer {
width: 100%;
padding: 0 70px;
border-top: 1px solid #eee;
box-sizing: border-box
}
.footer .footer-inner {
width: 100%;
display: flex;
flex: 1;
justify-content: center;
height: 50px;
align-items: center;
font-size: 12px;
color: #999999
}
`}</style>
</div>
)
}
在home組件中動(dòng)態(tài)導(dǎo)入的組件Olddriver,代碼很簡單,就是引用了靜態(tài)文件夾/static
下面的圖片
// componetns/OldDriver.tsx
export default () => (<img src="/static/images/yq.jpg"></img>)
前面編寫的都是一些無狀態(tài)的組件鸠儿,那么來一個(gè)有狀態(tài)的組件腐巢,然后實(shí)現(xiàn)初始化數(shù)據(jù)
// pages/Helps/helps
import React from 'react'
import HeaderFooter from '../../layouts/HeaderFooter'
import Link from 'next/link'
export default class Helps extends React.Component {
constructor (props) {
super(props)
}
static async getInitialProps() {
const hlist = await new Promise((resolve) => {
const hlist = [
{
title: 'title1',
id: '1'
},{
title: 'title2',
id: '2'
},{
title: 'title3',
id: '3'
},{
title: 'title4',
id: '4'
},{
title: 'title5',
id: '5'
},
]
resolve(hlist)
})
return {hlist}
}
render () {
return (
<HeaderFooter active="helps">
<div id="helpspage">
<ul>
{
this.props.hlist.map((help) => {
return <li key={help.id}>
<Link as={`/helps/${help.id}`} href={`/Help/help?id=${help.id}&title=${help.title}`}>
<a>{help.title}</a>
</Link>
</li>
})}
</ul>
</div>
<style>{`
#helpspage {
display: flex;
justify-content: center;
align-items: center;
height: 600px;
width: 100%
}
`}</style>
</HeaderFooter>
)
}
}
這里利用了一個(gè)異步的getInitialProps
方法獲取js普通對(duì)象胃惜,然后綁定在組件的props上面這樣我們就可以在組件中通過this.props.xxx
來取到獲取到數(shù)據(jù)鲫趁,具體可以查看獲取數(shù)據(jù)以及組件生命周期糠惫。
這個(gè)組件里面也許有個(gè)地方引起了你的注意,那就是<Link></Link>
組件,這個(gè)組件就是nextjs自己的路由跳轉(zhuǎn),不需要用到React Router
法瑟,<Link>
組件是通過next/link
引進(jìn)來的麻掸。當(dāng)然在這個(gè)地方的link你可以簡單的這樣<Linkhref={`/Help/help?id=${help.id}&title=${help.title}`}></Link>
然后在瀏覽器上你會(huì)看到這樣一個(gè)url
參數(shù)少一點(diǎn)還好诚隙,要是很多呢地消,那就不太好看了。所以這里我用了
as={`/helps/${help.id}`}
這個(gè)屬性,裝飾了一下url嗦随,所以可以叫路由裝飾,裝飾之后你訪問同樣的地址你會(huì)看到瀏覽器的url變成漂亮了許多 不是嗎砂吞。
但是這里會(huì)有一個(gè)問題當(dāng)你采用了路由裝飾之后你刷新這個(gè)頁面會(huì)出現(xiàn)404頁面
這是因?yàn)檫@個(gè)url是經(jīng)過裝飾之后的實(shí)際上是沒有對(duì)應(yīng)的路由的署恍,以這個(gè)url去請(qǐng)求服務(wù)器當(dāng)然找不到本來就沒有的頁面了。當(dāng)然這個(gè)錯(cuò)誤頁面也可以自定義的自定義錯(cuò)誤處理蜻直。
因?yàn)槲覀冮_發(fā)的時(shí)候是用
npm run dev
啟動(dòng)的是nextjs內(nèi)置的服務(wù)器盯质,這時(shí)候你可以自己配置一個(gè)服務(wù)器袁串,在服務(wù)器里面處理這些被裝飾后的url讓他指向正確的url,編寫根目錄下的server.js
// server.js
const { createServer } = require('http')
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true)
const { pathname, query } = parsedUrl
if (pathname.match(/^\/helps\/.+/)) {
const reg = /[^\/helps\/]/
const id = reg.exec(pathname)[0]
app.render(req, res, '/Help/help', {id})
} else {
handle(req, res, parsedUrl)
}
}).listen(3000, err => {
if (err) throw err
console.log('> Ready on http://localhost:3000')
})
})
因?yàn)槲覍?duì)服務(wù)端的不是很熟呼巷,所以我直接改了他們文檔里面的自定義服務(wù)端路由里面的代碼囱修,現(xiàn)在想想 好像沒用到依賴?yán)锩娴?code>express哈哈。這段代碼的意思就是王悍,當(dāng)我們使用裝飾后的url去訪問服務(wù)器的時(shí)候破镰,服務(wù)端匹配到/helps/*
這樣規(guī)則的路由的時(shí)候,就說明他是由/Help/help?id=xxx&xxx
這樣的路由裝飾來的压储,取到參數(shù)后鲜漩,用next的render方法去請(qǐng)求正確的url來返回正確的數(shù)據(jù)。
當(dāng)然 要用自己的服務(wù)器去提供這個(gè)demo的服務(wù)的話集惋,要先使用
npm run build
去打包這個(gè)項(xiàng)目孕似,構(gòu)建后會(huì)默認(rèn)放在當(dāng)前目錄下的.next
文件夾里面,也可以通過next.config.js
去改構(gòu)建導(dǎo)出的目錄芋膘。構(gòu)建完后鳞青,就可以命令行輸入npm run starts
運(yùn)行啦。不過我們demo還沒寫完呢为朋,還有一個(gè)help
頁面
// pages/Help/help.tsx
import HeaderFooter from '../../layouts/HeaderFooter'
// import Router from 'next/router'
const Help = (props) => {
return (
<HeaderFooter active="helps">
<div id="helppage">
id:{props.id}<br/>
title:{props.title}
</div>
<style>{`
#helppage {
display: flex;
justify-content: center;
align-items: center;
height: 600px;
width: 100%
}
`}</style>
</HeaderFooter>
)
}
Help.getInitialProps = async function ({query}) {
query.title = getTitleById(query.id)
return query
}
function getTitleById (id: string) {
return `title${id}`
}
export default Help
還記得前面被裝飾的url訪問的就是這個(gè)頁面臂拓,所以這個(gè)頁面是在服務(wù)端,讓裝飾url指向正確的url后再渲染的习寸,所以在這個(gè)組件里面不能使用通過導(dǎo)入的Router去取得參數(shù)import Router from 'next/router'
胶惰,這里的路由功能也很強(qiáng)大的,可以拿到query霞溪,可以路由攔截孵滞,可以命令式跳轉(zhuǎn)頁面等等,具體查看路由鸯匹。
所以這邊如果不采用裝飾路由的話是可以通過Router.query
拿到查詢參數(shù)的坊饶,但是這邊我們采用了路由裝飾,不過可以在之前提到的getInitialProps({query})
方法拿到查詢參數(shù)殴蓬。
getInitialProps
入?yún)?duì)象的屬性如下:
-
pathname
- URL 的 path 部分 -
query
- URL 的 query 部分匿级,并被解析成對(duì)象 -
asPath
- 顯示在瀏覽器中的實(shí)際路徑(包含查詢部分),為String
類型 -
req
- HTTP 請(qǐng)求對(duì)象 (只有服務(wù)器端有) -
res
- HTTP 返回對(duì)象 (只有服務(wù)器端有) -
jsonPageRes
- 獲取數(shù)據(jù)響應(yīng)對(duì)象 (只有客戶端有) -
err
- 渲染過程中的任何錯(cuò)誤
這里我們也知道無狀態(tài)組件也是可以有getInitialProps
的染厅《灰铮可以在里面進(jìn)行api請(qǐng)求什么的。
組件里面的
function getTitleById (id: string) {
return `title${id}`
}
里面用到了typescript的類型驗(yàn)證哈肖粮。demo比較小孤页,強(qiáng)行寫了一個(gè)。
Nextjs還支持導(dǎo)出靜態(tài)頁面涩馆。要導(dǎo)出的話就可以在命令行敲入
npm run export
之前在package.json
中已經(jīng)添加了行施,分兩步第一步先構(gòu)建代碼允坚,然后在導(dǎo)出靜態(tài)頁面
好啦好啦,已經(jīng)大部分編寫完啦悲龟。
下車
運(yùn)行npm run dev
或者 先運(yùn)行npm run build
再運(yùn)行npm run starts
就可以看到成果啦
自己寫了個(gè)構(gòu)建nextjs應(yīng)用的腳手架工具:
這是一個(gè)快速構(gòu)建nextjs應(yīng)用的工具
DEMO也已經(jīng)上傳在github上了有錯(cuò)的地方請(qǐng)指正哦屋讶。
最后在附上next插件的github地址 zeit/next-plugins
都有說插件的用法哦。