Next.js + Typescript + ant-design實(shí)現(xiàn)React的服務(wù)端渲染,怎么簡單怎么來

選擇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)用的工具

花了點(diǎn)時(shí)間看了一遍nextjs的源碼

Next.js中文文檔

那為什么要選擇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

image.png

開始只用到dev這個(gè)命令 其他的后面會(huì)用到
建好之后在命令行敲入

npm run dev  // or yarn dev

默認(rèn)是在:3000端口撼嗓,打開瀏覽器在瀏覽器輸入http://localhost:3000就能看到hello nextjs的文案啦且警。

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)最終展示如下


項(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

丑的url

參數(shù)少一點(diǎn)還好诚隙,要是很多呢地消,那就不太好看了。所以這里我用了as={`/helps/${help.id}`}這個(gè)屬性,裝飾了一下url嗦随,所以可以叫路由裝飾,裝飾之后你訪問同樣的地址你會(huì)看到瀏覽器的url變成
漂亮的url

漂亮了許多 不是嗎砂吞。
但是這里會(huì)有一個(gè)問題當(dāng)你采用了路由裝飾之后你刷新這個(gè)頁面會(huì)出現(xiàn)404頁面
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ù)。

正確數(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
都有說插件的用法哦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末须教,一起剝皮案震驚了整個(gè)濱河市皿渗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌轻腺,老刑警劉巖乐疆,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異贬养,居然都是意外死亡挤土,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門误算,熙熙樓的掌柜王于貴愁眉苦臉地迎上來仰美,“玉大人,你說我怎么就攤上這事儿礼】г樱” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵蚊夫,是天一觀的道長诉字。 經(jīng)常有香客問我,道長知纷,這世上最難降的妖魔是什么壤圃? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮琅轧,結(jié)果婚禮上伍绳,老公的妹妹穿的比我還像新娘。我一直安慰自己乍桂,他們只是感情好墨叛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著模蜡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扁凛。 梳的紋絲不亂的頭發(fā)上忍疾,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音谨朝,去河邊找鬼卤妒。 笑死甥绿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的则披。 我是一名探鬼主播共缕,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼士复!你這毒婦竟也來了图谷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤阱洪,失蹤者是張志新(化名)和其女友劉穎便贵,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體冗荸,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡承璃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蚌本。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盔粹。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖程癌,靈堂內(nèi)的尸體忽然破棺而出舷嗡,到底是詐尸還是另有隱情,我是刑警寧澤席楚,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布咬崔,位于F島的核電站,受9級(jí)特大地震影響烦秩,放射性物質(zhì)發(fā)生泄漏垮斯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一只祠、第九天 我趴在偏房一處隱蔽的房頂上張望兜蠕。 院中可真熱鬧,春花似錦抛寝、人聲如沸熊杨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晶府。三九已至,卻和暖如春钻趋,著一層夾襖步出監(jiān)牢的瞬間川陆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國打工蛮位, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留较沪,地道東北人鳞绕。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像尸曼,于是被迫代替她去往敵國和親们何。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353