SSR服務器端渲染(Next.js總結和豆瓣電影項目)

一.前言

先解釋一下Nuxt.js和Next.js雖然只有一個字母之差,但它們是不同的兩個服務端渲染框架.

什麼是Next.js?

引用Next中文官網的一句話:
Next.js 是一個輕量級的 React 服務端渲染應用框架。

Next.js帶來了很多好的特性:

  • 默認服務端渲染模式,以文件系統爲基礎的客戶端路由(注意:沒有專門路由)
  • 代碼自動分割使頁面加載更快
  • 以webpack的熱替換(HMR)爲基礎的開發環境
  • 使用React的JSX和ES6的module,模塊化和維護更方便
  • 可以運行在Express和其他Node.js的HTTP 服務器上
  • 可以定製化專屬的babel和webpack配置

使用Next服務器端渲染好處:

  • 對SEO友好
  • 提升在手機及低功耗設備上的性能
  • 快速顯示首頁

二.Next.js學習

按照國際慣例,先來一個hello world應用

先執行創建命令:

mkdir 項目名
cd 項目名
npm init -y
npm i react react-dom next --save
mkdir pages//一定要叫這個名,不能更改

配置package.json中的scripts屬性

 "scripts": {
    "dev":"next",
    "build":"next build",
    "start":"next start"
  },

在pages文件夾裏創建一個index.js頁面,簡單寫點內容:

export default () => {
  return (
    <div>
      <p>hello world</p>
    </div>
  )
}

執行npm run dev,出來效果:

在這裏插入圖片描述

頁面導航

路由跳轉(組件跳轉和事件跳轉)
Link組件跳轉:

1,引入Link組件

import Link from 'next/link';

2.使用
注意點:
路徑是用href;
文字裏面要用標籤包裹(標籤可以是a標籤或者其他標籤都可以,但Link標籤裏只能寫一個其他標籤);
給Link標籤設置style樣式是無效的,因爲Link是一個高階組件(HOC),但我們可以給子元素設置樣式.
在這裏插入圖片描述
href屬性也可以改爲對象寫法:

<Link href={{pathname:"/next-route/teacher"}} >
        <button style={{color:'red'}}>去教師頁面</button>
      </Link>

同時對象寫法可以傳遞query參數過去
代碼:

<Link href={{pathname:"/next-route/teacher",query:{id:1}}} >
        <button style={{color:'red'}}>去教師頁面</button>
</Link>

顯示效果:
在這裏插入圖片描述

事件路由跳轉

第一步:引入Router對象

import Router from "next/router";

第二步:添加跳轉事件

在這裏插入圖片描述
留意一下:瀏覽器輸入網址的請求跳轉方式network裏會請求頁面和js,但通過點擊跳轉的方式只有js,沒有再次請求頁面.

自定義404頁面

直接在pages文件夾裏創建一個_error.js頁面(只能叫這個名字)
在這裏插入圖片描述

創建公共導航組件(components文件夾)

不要寫在pages有路由的文件夾裏,在根目錄裏我們要創建一個單獨的components文件夾,寫代碼如下:

import Link from "next/link"
const Mynav=()=>{
    return (
        <div>
          <Link href={{pathname:"/next-route/teacher",query:{}}} >
            <button style={{color:'red'}}>去教師頁面</button>
          </Link>
          <Link href={{pathname:"/next-route/student",query:{}}} >
            <button style={{color:'red'}}>去學生頁面</button>
          </Link>
       
        </div>
      )
}
export default Mynav

在路由主頁中引入使用

在這裏插入圖片描述
導航效果就出來了:
在這裏插入圖片描述
student和teacher頁面引入方式和上面一樣.

佈局組件的使用(layouts文件夾)

第一步:創建佈局組件
在根目錄裏創建一個layouts文件夾,裏面寫我的佈局組件,上面導航是共用的,但是下面主體內容會動態變化,怎麼實現呢?直接使用react裏面的this.props.children屬性即可動態渲染主體內容
在這裏插入圖片描述
第二步:使用佈局組件(核心:把佈局組件寫成雙標籤形式,在雙標籤裏放入要顯示的動態內容即可)
在這裏插入圖片描述
效果:
在這裏插入圖片描述
同樣方式,在teacher和student頁面也把Mynav組件去掉,也改成佈局組件Mylayout動態內容顯示方式(這樣Mynav組件就只有在Mylayout裏引入一次,這樣就實現了佈局組件來佈局)

在這裏插入圖片描述
在這裏插入圖片描述
這樣在路由主頁,教師頁面和學生頁面都採用了佈局組件,實現了Mynav導航組件只在佈局組件裏導入一次.比如如果我們後面還要加一個尾部固定組件的話,那我們只需要在佈局組件裏再增加一個尾部組件即可,這樣非常方便.
在這裏插入圖片描述

全局佈局組件

上面的Mylayout佈局組件在主頁,教師頁和學生頁等每個頁面都引入了一次,有沒有辦法全局一次引入呢?辦法如下:
在pages文件加下創建_app.js(只能叫這個名字),寫如下代碼(是固定寫法):

import React from 'react'
import App, { Container } from 'next/app'
import Mylayout from './../layouts/Mylayout'

// Layout就是要寫的佈局組件,其它是固定寫法
class Layout extends React.Component {
  render() {
    const { children } = this.props
    return <Mylayout>{children}</Mylayout>
  }
}

export default class MyApp extends App {
  render() {
    const { Component, pageProps } = this.props
    return (
      <Container>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </Container>
    )
  }
}

這樣其它頁面都不用引入Mylayout局部組件了,只寫自己的內容即可.

Link組件路由參數傳遞和獲取的方法

現在需求是老師渲染列表頁面點擊某位老師要進入詳情頁面並把id以參數方式傳遞過去.
在教師頁面修改如下:

// import Mynav from '../../../components/Mynav'
// import Mylayout from './../../../layouts/Mylayout'
import Link from 'next/link'
const teacherList=[
    {name:"teacher1",id:1},
    {name:"teacher2",id:2},
    {name:"teacher3",id:3},
]
const Teacher =()=>{
    return (
        <div>
            {/* <Mynav/> */}
            {/* <Mylayout>
                <p> teacher頁面</p>
            </Mylayout> */}
            <p> teacher頁面</p>
            <ul>
            {
               teacherList.map((item)=>{
                   return (
                       <li key={item.id}>
                           <Link href={`/next-route/teacher/detail?id=${item.id}`}>
                               <a>{item.name}</a>
                           </Link>
                           
                       </li>
                   )
               }) 
            }
            </ul>

           
        </div>
    )
}

export default Teacher;

準備一個老師的詳情頁面,引入withRouter高階組件,在withRouter()方法裏將組件傳遞過去,然後在props.router.query.id裏得到傳遞過來的參數(重要:withRouter可以獲取url裏的參數)

import  {withRouter} from 'next/router';

const Detail=withRouter((props)=>{
    console.log(props);
    
    return (
        <div>
            這是{props.router.query.id}老師詳情頁面
        </div>
    )
})
export default Detail;

如果不引入withRouter是得不到query這個屬性,所以在next.js中一定要引入withRouter這個方法.
withRouter這個高階組件會講當前的路由對象注入到組件中去,並將路由對象綁定到組件的props這個參數上.

使用淺層路由優化路徑顯示

上面教師詳情頁顯示路徑如下
在這裏插入圖片描述
但路徑不好看,我們怎麼實現teacher/3這樣簡潔呢?使用next裏的淺層路由即可

其實就是使用Link組件有一個as屬性,它可以給路徑起別名,在教師頁面操作如下:
在這裏插入圖片描述

解決淺層路由刷新頁面找不到頁面的問題

上面的教師詳情頁當刷新頁面時,會找不到頁面,因爲通過as屬性,給browser history來個路由掩飾,但是按刷新按鈕路由就找不到了,因爲服務器回去重新找/p/xxxx頁面,但是實際上此時並不存在xxxx頁面,這個問題實際要服務器端協助解決(實際就是後臺將我們別名的路由地址轉爲原來真實的路徑),方法如下:

  1. 安裝express npm install --save express
  2. 在根目錄下創建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('*', (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)
    })
  1. 修改package.json文件中scripts字段(作用是再跑服務器)
 "scripts": {
   "dev": "node server.js",
   "build": "next build",
   "start": "NODE_ENV=production node server.js"
 }
  1. 在server.js裏創建自定義路由
  server.get('/next-route/teacher/:id', (req, res) => {
      const actualPage = '/next-route/teacher/detail'
      const queryParams = { id: req.params.id }
      app.render(req, res, actualPage, queryParams)
    })

在這裏插入圖片描述

靜態文件(即不需要打包的文件)

比如我們用到的圖片,它是不需要打包的,做法是在根目錄裏新建一個static文件夾(只能叫這個名字),在要用的地方寫絕對路徑即可.
在這裏插入圖片描述

請求數據接口(isomorphic-unfetch工具請求數據,裏面實現了函數組件和類組件的寫法)

isomorphic-unfetch支持服務器端渲染.使用方法如下:
1.安裝isomorphic-unfetch

npm install --save isomorphic-unfetch

2.使用

引入

import fetch from 'isomorphic-unfetch'

記住:fetch方法調用前要使用組件.getInitiaProps賦值一個函數,在函數裏面調用fetch方法.
因爲使用異步靜態方法getInitialProps獲取數據,此靜態方法能夠獲取所有的數據,並將其解析成一個 JavaScript對象,然後將其作爲屬性附加到 props對象上
在這裏插入圖片描述
上面是函數組件,類組件的話寫法如下:
在這裏插入圖片描述

這樣數據就出來了.
在這裏插入圖片描述
注意:getInitialProps 不能 在子組件上使用,只能使用在pages文件夾的頁面中進行調用。
同時,getInitialProps接收一個上下文對作爲參數,這個對象包含以下屬性:

  • pathname: URL的 path部分
  • query: URL的 query string部分,並且其已經被解析成了一個對象
  • asPath: 在瀏覽器上展示的實際路徑(包括 query字符串)
  • req: HTTP request 對象 (只存在於服務器端)
  • res: HTTP response 對象 (只存在於服務器端)
  • jsonPageRes: 獲取的響應數據對象 Fetch Response (只存在於客戶端)
  • err: 渲染時發生錯誤拋出的錯誤對象

樣式寫法

next.js支持普通的react樣式外,還有自己的獨特樣式,寫法如下:

在這裏插入圖片描述
上面寫法有兩個屬性要注意
jsx:它僅限作用於當前組件,子組件不會生效;
global:它不但作用域當前組件,子組件也會生效.

豆瓣電影項目

創建電影主頁面pages/index.js:

在這裏插入圖片描述

創建公共導航組件components/Movieheader.js:

import Link from 'next/link';
const Movieheader =()=>(
   <div class="movie-header">
       <style jsx>
          {`
              .movie-header {
                position: fixed;
                top: 0;
                left: 0;
                right: 0;
              }
              ul {
                display: flex;
                justify-content: space-around;
                align-items: center;
                padding: 15px 0;
                background-color: #1e2736;
                margin: 0;
              }
              li {
                list-style: none;
                line-height: 30px;
                height: 30px;
              }
              li a {
                color: white;
              }
              li a:hover {
                color: red;
              }
          `}
       </style>
       
       <ul>
           <li>
               <Link href="/movie/type?type=in-theaters"><a>正在熱映</a></Link>
           </li>
           <li>
               <Link href="/movie/type?type=cooming_soon"><a>即將上映</a></Link>
           </li>
           <li>
               <Link href="/movie/type?type=top250"><a>top250</a></Link>
           </li>
       </ul>
       
   </div> 
)

export default Movieheader;

電影列表頁面pages/movie/type/index.js:

注意數據請求發送方式.

import fetch from 'isomorphic-unfetch';
import {withRouter} from 'next/router'
import Link from 'next/link'
const Movietype = withRouter(props => (
  <div className="movie-type">
    <h1>這是電影詳情頁</h1>
    <ul>
      {props.movieList.map(item => {
        return (
          <div key={item.id} className="movie-box">
            {/* 提示:地址裏的type值獲取方式可以使用withRouter高階組件獲取 */}
            <Link href={`/movie/detail?id=${item.id}&type=${props.router.query.type}`}>
              <div>
                  <img src={item.img} alt={item.title}></img>
                  <h4>{item.title}</h4>
                  <p>評分:{item.rating}</p>
              </div>
            </Link>
          </div>
        )
      })}
    </ul>
    <style jsx>
      {`
      .movie-type {
        display: flex;
        flex-direction: column;
        align-items: center;
      }
      .movie-box {
        display: flex;
        flex-direction: column;
        align-items: center;
        margin: 20px 0;
        padding: 10px 0;
        width: 140%;
        box-shadow: 0 0 10px #bbb;
        
      }
      .movie-box:hover {
        box-shadow: rgba(0,0,0,0.3) 0px 19px 60px;
      }
      `}
    </style>
  </div>
))

Movietype.getInitialProps = async function(context) {
  let res = await fetch(`http://localhost:3301/${context.query.type}`)
  // console.log(context.query.type);
  
  let data = await res.json()
  console.log(data)
  return {
    movieList: data
  }
}
export default Movietype


電影詳情頁面

import fetch from 'isomorphic-unfetch'
const Detail=(props)=>(
    <div className="detail">
        <div className="detail-box">
            <img src={props.detail.img} alt={props.detail.title}/>
            <h4>{props.detail.title}</h4>
            <p>電影類型:{props.detail.genres.join(',')}</p>
            <p>上映時間:{props.detail.details[0].year}</p>
            <p>劇情介紹:{props.detail.details[0].summary}</p>
        </div>
        <style jsx>{`
        .detail {
            width: 40%;
            margin: 0 auto;
            padding: 20px;
            box-sizing: border-box;
            box-shadow: 0 0 10px #bbb;
          }
          .detail-box {
            text-align: center;
          }
        `}</style>
    </div>
)

Detail.getInitialProps=async function(context) {
    let res= await fetch(`http://localhost:3301/${context.query.type}/${context.query.id}?_embed=details`)
    let data =await res.json();
    console.log(data);
    
return {
    detail:data
}
}
export default Detail;

最後,介紹一下SEO搜索引擎優化

其實很簡單,next.js自己封裝了一個head組件,需要seo的地方按需引入,在裏面寫各自的標籤即可.
在這裏插入圖片描述
完整效果:
在這裏插入圖片描述

到此,next.js就學到這裏了.最後附上全部項目代碼克隆鏈接:
[email protected]:huanggengzhong/SSR.git

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章