SSR之next.js--篇一

一、名詞解釋

       SSR(server-side-render),根據名稱咱們都知道叫作服務端渲染,我相信對於工做幾年的前端開發者確定很熟悉這個名詞,多少也都用過,在這裏,我想說的是針對我們剛剛踏入前端圈的朋友們的一點總結和感悟,但願咱們可以不斷的提升對next的認知,相互進步,相互成長!這就是咱們的目標~
  • 服務端渲染就是頁面的渲染和生成由服務器來完成,並將渲染好的頁面返回客戶端
  • 客戶端渲染就是頁面的生成和數據的渲染過程是在客戶端(或瀏覽器或APP)完成

其中掘金就使用vue的ssr功能作了全棧服務端渲染,切效果良好。前端

那咱們用next.js的優勢是什麼呢?

二、next.js的特性

  • 默認服務端渲染模式,以文件系統爲基礎的客戶端路由
  • 代碼自動分割使得頁面加載更快
  • (以頁面爲基礎)的簡介的客戶端路由
  • 以webpack的熱替換爲基礎的開發環境
  • 使用React的JSX和ES6的module,模塊化和維護更方便
  • 能夠運行在Express和其餘node.js的http服務器上
  • 能夠定製化專屬的babel和webpack配置
那咱們開始上手吧,hello world!

三、hello world

照作如下步驟吧~複製代碼


接着打開package.json添加以下代碼,以下:vue

{
  "scripts": {
    "dev": "next"
}}複製代碼

到這裏位置項目的準備工做已完成,運行如下命令開啓項目服務器node

npm run dev複製代碼

執行完畢後,在瀏覽器打開localhost:3000,就會看到如下頁面:


這是next.js默認生成的404頁面,兒開啓服務後訪問的之因此是如今的404頁面,是由於咱們尚未設置項目主頁。

那接下來就是建立頁面了~~~啦啦啦~~~我學next我開心~~~

四、建立頁面

next.js是從服務器生成頁面,再返回給前端展現。

重點內容開始了哦~~~~複製代碼

  • next.js默認從pages目錄下取頁面進行渲染返回給前端展現,並默認取pages/index.js爲系統是首頁進行展現
  • 注意,pages是默認存放頁面的目錄,路由的根路也是pages目錄。

咱們在pages/index.js中建立一個React函數式組件:react

const Index = () => (
    <div>
        <p>小英 study next.js</p>
    </div>
)
export default Index複製代碼

next.js默認使用webpack構建項目,webpack的熱部署功能同樣能提高開發效率。建立完pages/index.js後,再訪問http://localhost:3000便可看到設置好的頁面。webpack


很好,不在是咱們不喜歡的404了,看到內容了啊~~ios

再來構建下多頁面的吧~

使用next.js的目的就是構建非SPA的多頁面項目,下面開始建立第二個頁面。

在pages目錄下建立文件pages/content.jsweb

export default () => (  
  <div>    
      <p>這是content 頁面</p> 
   </div>
)複製代碼

打開路由localhost:3000/content,看到咱們新建的第二個頁面了。ajax


因此如今咱們明白全部的路由都是經過後端服務器來控制的 ,要想實現客戶端路由,須要藉助next.js的 Link API,因此接下來咱們看下Link API吧~~

五、Link API

從next/link中能夠引用到Link組件。在pages/index.js文件中引用Link,修改以下:express

import Link from "next/link"
const Index = () => (  
    <div>    
        <Link href="/content">     
             <a>去往content頁面</a>    
        </Link>    
        <p>小英 study next.js</p>  
    </div>)
export default Index複製代碼

咱們使用的Link組件,其實能夠當作a標籤使用,最終渲染的時候也是a標籤,頁面渲染以下:npm


點擊超連接,便可進入到content頁面。下圖是實際渲染的結果:


結論:link組件是經過location.history的瀏覽器API保存歷史路由,因此,能夠經過瀏覽器左上角的前進後退按鈕來切換歷史路由。而在開發過程當中,咱們不須要單獨寫客戶端路由的配置。


六、組件複用

next.js是以多頁面爲中心,只要將頁面文件放在pages目錄下,就能夠經過瀏覽器上以文件名爲路由名來訪問到,相反,只要不想讓用戶經過頁面直接訪問的組件,都不放在pages目錄下,開發者想放在哪一個目錄自定義便可。

接下來,咱們建一個components目錄,裏面建一個公共的Header組件,用於頭部導航,經過導航能夠再頁面間切換。

import Link from 'next/link'
const linkStyle = {  marginRight: 15}
const Header = () => (  
    <div>    
        <Link href="/">     
             <a style={linkStyle}>首頁</a>    
        </Link>    
        <Link href="/content">      
            <a style={linkStyle}>內容</a>    
        </Link>  
    </div>)
export default Header複製代碼

好了,定義完組件,咱們在pages/index.js裏面引用看一下:

import Header from '../components/Header.js'
const Index = () => (  
    <div>    
        <Header />    
        <p>小英 study next.js</p>  
    </div>)
export default Index複製代碼

在 pages/content.js 中一樣引入 Header 組件,在瀏覽器上經過點擊導航切換頁面。

import Header from '../components/Header.js'
export default () => (  
  <div>    
    <Header/>    
    <p>這是content 頁面</p> 
 </div>)複製代碼

這樣就能夠再index和content兩個頁面來回切換了


進一步咱們在封裝下Header組件,Layout組件,包含咱們的Header和頁面content的組件;

/**Layout**組件/
import Header from './Header'
const layoutStyle = {  margin: 0,  padding: 0,  border: '1px solid #DDD'}
const Layout  = (props) => (  
    <div style={layoutStyle}>    
        <Header/>    
        {props.children}  
    </div>)
export default Layout複製代碼

/**在index中使用Layout組件**/
import Layout from '../components/Layout.js'
import Link from 'next/link'
const linkArr = [  
    '我是連接1',  
    '我是連接2',  
    '我是連接3',  
    '我是連接4',  
    '我是連接5',  
    '我是連接6',]
const LinkContent = (props) => ( 
     <li>    
        <Link href={`tolink?title=${props.title}`}>    
            <a>{props.title}</a>    
        </Link> 
     </li>)
const Index = () => (  
    <Layout>    
        <h1>這是個人地盤</h1>    
        <ul>    
            {      
                linkArr.map(item => {        
                    return  <LinkContent title={item} />      
                })   
             }    
        </ul>  
    </Layout>)
export default Index複製代碼

頁面渲染以下:(如下至關因而列表頁,點擊列表中的連接至關於到詳情頁,如下就造成了動態頁面之間的跳轉)


/**詳情頁**/
import Layout from '../components/Layout.js'export default (props) => (  <Layout>    <h1>{props.url.query.title}</h1>    <p>這是不一樣title對應的詳情頁</p>  </Layout>)複製代碼


總結:使用next.js建立動態頁面,與使用React和Vue建立一個spa的頁面大致相同, 區別就是頁面的渲染主題不一樣
  • 前者是nodejs服務器獲取到後端數據渲染完頁面後再返回給前端展現
  • 後者是前端先獲取頁面主體架構,再經過ajax的方式請求後端的數據,在前端渲染展現

七、Route Masking

next.js提供一個獨特的特性:路由遮蓋(Route Masking)。它可使得在瀏覽器上顯示的是路由A,而在app內部實際顯示的是路由B。這個特性可使咱們的路由簡潔,以上邊爲例,地址欄顯示的是 http://localhost:3000/tolink?title=%E6%88%91%E6%98%AF%E9%93%BE%E6%8E%A56,這個地址含有個title參數,看着很不整潔嗎,接下來咱們就要next改造路由,目標是改爲 http://localhost:3000/p/0

這裏咱們要用一個Link組件的as屬性,並給組件添加一個id屬性:(以index爲例)

import Layout from '../components/Layout.js'
import Link from 'next/link'
const linkArr = [  '我是連接1',  '我是連接2',  '我是連接3',  '我是連接4',  '我是連接5',  '我是連接6',]
const LinkContent = (props) => (  
    <li>    
        <Link     
           as={`p/${props.id}`}    
           href={`tolink?title=${props.title}`}>   
               <a>{props.title}</a>   
         </Link>  
    </li>)
const Index = () => (  
    <Layout>   
         <h1>這是個人地盤</h1>    
         <ul>    
            {     
             linkArr.map((item,index) => {        
                return  <LinkContent id={index} title={item} />     
             })    
            }    
        </ul>  
    </Layout>)
export default Index複製代碼


But。。。。咱們刷新下頁面,咦怎麼404了呢???


結論:

  • 當在 Link 組件上使用 as 屬性時,瀏覽器上顯示的是 as 屬性的值,走的是客戶端路由,而服務器真正映射的是 href 屬性的值,走的是服務端路由。
  • 顯示 404頁面,是由於路由遮蓋默認只在客戶端路由中有效,要想在服務端也支持路由遮蓋,須要在服務端單獨設置路由解析的方法。

八、服務端支持路由遮蓋

下面以express爲例建立後端服務器講解如何設置服務器來支持路由遮蓋。

先來安裝一個express

npm install --save 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()     
    server.get('/p/:id', (req, res) => {    
        const actualPage = '/tolink'    
        const queryParam = {      title: req.param.id    }    
        app.render(req, res, actualPage, queryParam)  
    })  
    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.log(ex.stack)  
        process.exit(1)
    })   
複製代碼

/**index.js**/
import Layout from '../components/Layout.js'
import { withRouter } from "next/router"
class ToLink extends React.Component {
    // 沒有該函數是不能實現服務端路由遮蓋的  
    static getInitialProps ({ query: { title } }) {  
      return { title } 
     }  
    render() {    
        let { title } = this.props    
        return (    
           <Layout>        
                <h1>{title}</h1>        
                <p>這是不一樣title對應的詳情頁</p>      
           </Layout>    
        )  
    }
} 
export default withRouter(ToLink)複製代碼

這時在tolink頁面刷新的時候就可以正常顯示頁面了,這裏強調一點,我在參考一些文章的時候,沒有提到加 getInitialProps這個方法,就說頁面刷新就能正常顯示是不正確的,本人屢次試驗,頁面自己的內容是能夠正常顯示的,可是根據路由獲取的title參數是一直拿不到的,加了這個參數就會先去獲取參數,這樣頁面才能完整顯示~~~如下提供截圖證實:


因此,接下來咱們就說說這個靜態的方法。

九、接口請求

next.js在react的基礎上爲組件新增了一個特性:getInitialProps, 同於獲取並處理組件的屬性,返回組件的默認屬性。咱們能夠在該方法中請求數據,獲取頁面須要的數據並渲染返回給前端頁面。( 上邊的服務端路由遮蓋也是用了這個方法,原理就是獲取組件的默認屬性

引入一個支持在客戶端和服務端發送fetch請求的插件isomorphic-unfrtch, 固然咱們也能夠用axios等其餘工具。

npm install --save isomorphic-unfetch複製代碼

而後修改pages/index.js:

import Layout from '../components/Layout.js'
import { withRouter } from "next/router"
import fetch from 'isomorphic-unfetch'
import Link from 'next/link'

const LinkContent = (props) => {  
    console.log(props)  
    return (    
        <li>      
            <Link      
                 as={`/p/${props.id}`}      
                 href={{pathname: '/tolink', query: {title: props.title}}}> 
             <a>{props.title}</a>      
            </Link>   
         </li> 
   )
}
class Index extends React.Component {  
    static async getInitialProps(){    
        const res = await fetch('https://api.tvmaze.com/search/shows?q=batman')  
        const data = await res.json()      
        console.log(`Show data fetched. Count: ${data.length}`)      
        return {      
            shows: data    
        }  
     }  
    render() {    
        const { shows } = this.props    
            return(      
                <Layout>      
                    <h1>這是個人地盤</h1>     
                    <ul>     
                         {        
                            shows.map(( { show } ) => {   
                               return  <LinkContent key={show.id} id={show.id} title={show.name} />   
                             })      
                          }                          
                    </ul>   
                 </Layout>)    
   }
}
export default withRouter(Index)複製代碼

在getInitialProps中咱們使用async await關鍵字異步獲取咱們須要的參數shows,這樣數據就是動態獲取的,從而實現動態渲染。注意:getInitialProps不能使用在子組件中。只能使用在pages頁面中。

結論以下:
  • 當頁面渲染時加載數據,咱們使用了一個異步方法getInitialProps。它能異步獲取 JS 普通對象,並綁定在props上
  • 當服務渲染時,getInitialProps將會把數據序列化,就像JSON.stringify。因此確保getInitialProps返回的是一個普通 JS 對象,而不是Date, Map 或 Set類型。
  • 當頁面初始化加載時,getInitialProps只會加載在服務端。只有當路由跳轉(Link組件跳轉或 API 方法跳轉)時,客戶端纔會執行getInitialProps。


結論3在這裏給你們作個驗證:

servert.js, 這是我在server.js中添加的一個測試id,只有在服務端渲染的時候,咱們的tolink頁面纔會獲取的這個id


pages/tolink.js,咱們在該頁面的 getInitialProps方法中打印一下id


這個結果是我在刷新tolink頁面,或者初始化加載的時候,在終端(服務端)打印的結果,有「測試id」這幾個字;


繼續往下看:


這是我經過首頁的列表Link的方式點擊進來這個頁面,在瀏覽器控制檯打印的結果,很明顯這是客戶端走的這個方法,是沒有」測試id「四個字的,而是undefined,因此這也就驗證告終論3的正確性~~~~,(其實我剛開始也是不懂這個意思,如今明白了,但願正在看的你也能明白哦~~)

十、部署

Next.js 項目的部署,須要一個 Node.js的服務器,能夠選擇 Express, Koa 或其餘 Nodejs 的Web服務器。本文中以 Express 爲例來部署 Next 項目。

爲了區分部署環境,咱們須要在 package.json 中修改 script 屬性以下:

"scripts": {
  "build": "next build",
  "start": "NODE_ENV=production node server.js -",
  "dev": "NODE_ENV=dev node server.js"
}複製代碼

其中,build 命令是用於打包項目,start 命令是用於生產環境部署,dev 命令是用於本地開發。

執行以下命令便可將 Next項目 部署到服務器:

npm run build
npm run start
複製代碼

執行完命令後,可在 http://host:3000 訪問。其中,host 是指服務器的IP地址。

總結:next.js部署生成環境,必須用build命令打包構建,而後再用start命令部署。


next.js還有不少內容須要咱們不斷總結和採坑,未完待續。。。。。。😃😄

相關文章
相關標籤/搜索