next.js的框架


2. Next.js服務器端渲染

學習目標

  • 了解Next.js的作用
  • 掌握Next.js中的路由
  • 掌握Next.js中布局組件的創建
  • 掌握Next.js中的靜態文件服務
  • 掌握Next.js中獲取頁面數據的方法
  • 掌握Next.js中組件樣式的書寫
  • 使用Next.js完成豆瓣電影案例
  • 能夠自定義頭部元素head

2.1 什么是Next.js?

Next.js官網

Next.js是一個基於React的一個服務端渲染簡約框架。它使用React語法,可以很好的實現代碼的模塊化,有利於代碼的開發和維護。

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

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

使用服務器端渲染好處:

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

2.2 Next.js初體驗

mkdir hello-next
cd hello-next
npm init -y
npm install --save react react-dom next
mkdir pages

配置package.json中的scripts屬性

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

此時npm run dev 會得到一個404頁面

創建一個pages/index.js

const Index = () => (
  <div>
    <p>Hello Next.js</p>
  </div>
)

export default Index

創建一個pages/next-route/teacher.js頁面

const Teacher = () => (
  <div>
    <p>教師頁面</p>
  </div>
)

export default Teacher

2.3 頁面導航

2.3.1 路由跳轉

  1. Link組件
  • Link組件內不能直接寫文字,必須使用標簽包裹,標簽可以是任何標簽,但是必須只能保證Link組件內只有一個子元素;
  • 給Link組件設置樣式不會生效,因為Link組件是一個HOC(高階組件),但是可以給它里面的子元素設置樣式;
import Link from 'next/link'
<Link href="/teachers">
  <a>教師頁面</a>
</Link>

組件<Link>可接收 URL 對象,而且它會自動格式化生成 URL 字符串。例如:

<Link href={{pathname: '/teachers', query: {id: 1}}}>
  <a>教師頁面</a>
</Link>
  1. 命名式路由
import Router from 'next/router'

export default () => (<div><span onClick={() => Router.push('/teacher')}>教師</span></div>)

URL對象語法:

Router.push({pathname: '/teacher', query: {id: 1}})

注意:如果沒有匹配到的話,默認會去找_error.js中定義的組件; 路由跳轉不會向服務器發送請求頁面的請求。

2.3.2 創建組件

  1. 普通組件

    組件的創建可以在任何的文件夾下面,但是不要放在pages下面,因為組件並不需要url

  2. 布局組件

    利用this.props.children

  3. 全局布局組件, 創建_app.js,模板入下:

    import React from 'react'
    import App, {Container} from 'next/app'
    
    class Layout extends React.Component {
      render () {
        const {children} = this.props
        return <div className='layout'>
          {children}
        </div>
      }
    }
    
    export default class MyApp extends App {
      render () {
        const {Component, pageProps} = this.props
        return <Container>
          <Layout>
            <Component {...pageProps} />
          </Layout>
        </Container>
      }
    }
    

2.3.3 query strings

  1. 創建一個帶query的鏈接
  2. 如果你想應用里每個組件都處理路由對象,你可以使用withRouter高階組件。從next/router中引入withRouter,注入路由對象到Next.js中的組件作為組件屬性,從而獲取query對象
  3. 組件使用props.router.query.xxx獲取query strings

2.3.4 Clean URLs with Route Masking

通過as屬性,給browser history來個路由掩飾,但是按刷新按鈕路由就找不到了,因為服務器回去重新找/p/xxxx頁面,但是實際上此時並不存在xxxx頁面

    // /post?title=xxxx 會變成 /p/xxxx
	<Link as={`/p/${props.id}`} href={`/post?title=${props.title}`}>
      <a>{props.title}</a>
    </Link>

2.3.5 服務器端支持Clean URLs

創建自定義服務

  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.get('/teacher/:id', (req, res) => {
    const actualPage = '/teacher/detail'
    const queryParams = { id: req.params.id } 
    app.render(req, res, actualPage, queryParams)
  })

2.4 靜態文件服務

項目的根目錄新建 static 文件夾,代碼通過 /static/ 開頭的路徑來引用此文件夾下的文件,例如:

export default () => <img src="/static/logo.png" />

2.5 獲取頁面數據

  1. 下載isomorphic-unfetchnpm install --save isomorphic-unfetch
  2. 引入 import fetch from 'isomorphic-unfetch';

使用異步靜態方法getInitialProps獲取數據,此靜態方法能夠獲取所有的數據,並將其解析成一個 JavaScript對象,然后將其作為屬性附加到 props對象上

當頁面初次加載時,getInitialProps只會在服務端執行一次。getInitialProps只有在路由切換的時候(如Link組件跳轉或命名式路由跳轉)時,客戶端的才會被執行。

注意:getInitialProps 不能 在子組件上使用,只能使用在pages頁面中。

// Index是一個組件
Index.getInitialProps = async function() {
  const res = await fetch('http://localhost:3301/in_theaters')
  const data = await res.json()
	
	// 這段數據會在服務器端打印,客戶端連請求都不會發
  console.log(data)

  return {
    // 組件中通過props.shows可以訪問到數據
    movieList: data
  }
}

如果你的組件是一個類組件,你需要這樣寫:

export default class extends React.Component {
  static async getInitialProps() {
    const res = await fetch('http://localhost:3301/in_theaters')
    const data = await res.json()
    console.log(data);
    return {movieList: data}
  }
  render() {
    return (
      <div>
        {this.props.movieList.map(item => (
          <p key={item.id}>{item.title}</p>
        ))}
      </div>
    )
  }
}

getInitialProps: 接收的上下文對象包含以下屬性:

  • pathnameURLpath部分

  • queryURLquery string部分,並且其已經被解析成了一個對象

  • asPath: 在瀏覽器上展示的實際路徑(包括 query字符串)

  • reqHTTP request 對象 (只存在於服務器端)

  • resHTTP response 對象 (只存在於服務器端)

  • jsonPageRes: 獲取的響應數據對象 Fetch Response (只存在於客戶端)

  • err: 渲染時發生錯誤拋出的錯誤對象

// 在另外一個組件中,可以使用context參數(即上下文對象)來獲取頁面中的query
Post.getInitialProps = async function (context) {
  const { id } = context.query
  const res = await fetch(`http://localhost:3301/in_theaters/${id}?_embed=details`)
  const data = await res.json()
  console.log(data)

  return {movieDetail: data}
}

2.6 組件樣式

  1. css樣式文件

  2. css in js

  3. styled-jsx

  • scoped

    如果添加了 jsx屬性,只作用於當前組件,不包括子組件

   <style jsx>{`
     h1, a {
       font-family: "Arial";
     }

     ul {
       padding: 0;
     }

     li {
       list-style: none;
       margin: 5px 0;
     }

     a {
       text-decoration: none;
       color: blue;
     }

     a:hover {
       opacity: 0.6;
     }
   `}</style>

  • global

    作用於當前組件,包括子組件

<style jsx global>{``}</style>

2.7 豆瓣電影案例

接口

獲取電影列表:http://localhost:3301/in_theaters (in_theaters可以替換為coming_soon及top250)

獲取電影詳情:http://localhost:3301/in_theaters/1?_embed=details

2.7.1 豆瓣電影首頁

MovieHeader組件樣式

    .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;
    }

2.7.2 豆瓣電影列表頁

.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: 40%;
  box-shadow: 0 0 10px #bbb;
  
}
.movie-box:hover {
  box-shadow: rgba(0,0,0,0.3) 0px 19px 60px;
}

2.7.3 豆瓣電影詳情頁

.detail {
  width: 40%;
  margin: 0 auto;
  padding: 20px;
  box-sizing: border-box;
  box-shadow: 0 0 10px #bbb;
}
.detail-box {
  text-align: center;
}

2.8 自定義頭部元素head

引入next/head

export default () => {
    <div>
    	<Head>
      		<meta name="keywords" content="" key="viewport" />
    	</Head>
    </div>
}

注意:在卸載組件時,<head>的內容將被清除。請確保每個頁面都在其<head>定義了所需要的內容,而不是假設其他頁面已經加過了


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM