Next.js之基礎概念(二)

本篇教程基於上一篇的基礎,主要講解服務端渲染,樣式以及部署相關的一些知識,若你沒有看過上一篇的內容,或者你看過又忘了,建議從新去看一遍。javascript

順便說一句,Next.js 3.0的版本在前幾天已經正式對外發布,本篇教程仍然基於2.x的版本,若你使用3.0的版本,代碼上可能有不一致的地方,須要你留意一下。css

爲了快速開始,我們直接使用官方的例子進行講解,先把代碼拷貝下來:java

git clone https://github.com/arunoda/learnnextjs-demo.git
cd learnnextjs-demo
git checkout clean-urls-ssr

項目拷貝下來後咱們進入目錄,安裝一下依賴並啓動:node

cd learnnextjs-demo
npm install
npm run dev

打開頁面,便可看到以下效果:react

圖片描述

服務端渲染

官方的例子是根據TVMaze提供的api來展現電視節目,所以咱們須要安裝isomorphic-unfetch用於獲取遠程數據:webpack

npm install  isomorphic-unfetch -S

而後將如下代碼替換到pages/index.js中:git

import Layout from '../components/MyLayout.js'
import Link from 'next/link'
import fetch from 'isomorphic-unfetch'

const Index = (props) => (
  <Layout>
    <h1>Batman TV Shows</h1>
    <ul>
      {props.shows.map(({show}) => (
        <li key={show.id}>
          <Link as={`/p/${show.id}`} href={`/post?id=${show.id}`}>
            <a>{show.name}</a>
          </Link>
        </li>
      ))}
    </ul>
  </Layout>
)

Index.getInitialProps = async function() {
  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
  }
}

export default Index

上面的代碼中,寫在Index.getInitialProps中的內容,既能夠跑在server端,也能夠跑在瀏覽器端,當咱們刷新頁面時,能夠看到server段的控制檯輸出了:github

Show data fetched. Count: 10

但在瀏覽器控制檯中卻沒有看到輸出,那何時這段代碼會跑在瀏覽器端呢?那就是當你經過客戶端路由進來的時候,這段代碼纔會執行,咱們來更改一下代碼,web

server.js中,找到包含/p/:id的地方,並替換以下:npm

server.get('/p/:id', (req, res) => {
    const actualPage = '/post'
    const queryParams = { id: req.params.id }
    app.render(req, res, actualPage, queryParams)
})

同時,更改pages/post.js:

import Layout from '../components/MyLayout.js'
import fetch from 'isomorphic-unfetch'

const Post =  (props) => (
    <Layout>
       <h1>{props.show.name}</h1>
       <p>{props.show.summary.replace(/<[/]?p>/g, '')}</p>
       <img src={props.show.image.medium}/>
    </Layout>
)

Post.getInitialProps = async function (context) {
  const { id } = context.query
  const res = await fetch(`https://api.tvmaze.com/shows/${id}`)
  const show = await res.json()

  console.log(`Fetched show: ${show.name}`)

  return { show }
}

export default Post

這時候當咱們是從首頁的連接點進去post頁面時,會由post頁面從瀏覽器端發出請求獲取數據,要是在這個頁面直接刷新頁面,則會由服務端獲取數據,這就是Next.js實現的ssr,是否是感受很簡單。

另外,若是有些代碼只但願在服務端執行,而不但願瀏覽器端執行,在能夠根據context裏面有沒有包含req這個字段來判斷,代碼以下:

Post.getInitialProps = async function (context) {
     if(context.req) {
         // 只會在服務端執行
     }
   
     return { show }
   }

上面的代碼實際上是有問題的,想一想看,我但願在服務端執行的代碼,天然不但願webpack把它打包到客戶端,不然會增大打包後的腳本,用戶體驗也很差,解決方案能夠參考這裏,這裏就不展開了。

樣式

在react中寫樣式,通常能夠歸爲2類,一類是基於css文件的傳統方式(包括sass,postcss等),另外一類則是css in js

基於傳統的方式寫css,在Next.js中會有些問題,特別是ssr的時候,所以,官網給出的解決方案是使用css in js,在Next.js中,已經預裝了一個css in js的框架叫styled-jsx

咱們回到咱們的代碼中,更改pages/index.js,代碼以下:

import Layout from '../components/MyLayout.js'
import Link from 'next/link'

function getPosts () {
  return [
    { id: 'hello-nextjs', title: 'Hello Next.js'},
    { id: 'learn-nextjs', title: 'Learn Next.js is awesome'},
    { id: 'deploy-nextjs', title: 'Deploy apps with ZEIT'},
  ]
}

export default () => (
  <Layout>
    <h1>My Blog</h1>
    <ul>
      {getPosts().map((post) => (
        <li key={post.id}>
          <Link as={`/p/${post.id}`} href={`/post?title=${post.title}`}>
            <a>{post.title}</a>
          </Link>
        </li>
      ))}
    </ul>
    <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>
  </Layout>
)

在標籤<style jsx>中,咱們寫咱們的css,css必須包含在 {``}中,不然會報錯。

因爲css in js有做用域的隔離,也就是說css只會應用於當前組件,不會應用於其子組件。所以,如下代碼,樣式只會做用於h1和ul標籤,不會做用於li標籤:

import Layout from '../components/MyLayout.js'
import Link from 'next/link'

function getPosts() {
  return [
    { id: 'hello-nextjs', title: 'Hello Next.js' },
    { id: 'learn-nextjs', title: 'Learn Next.js is awesome' },
    { id: 'deploy-nextjs', title: 'Deploy apps with ZEIT' },
  ]
}

const PostLink = ({ post }) => (
  <li>
    <Link as={`/p/${post.id}`} href={`/post?title=${post.title}`}>
      <a>{post.title}</a>
    </Link>
  </li>
)

export default () => (
  <Layout>
    <h1>My Blog</h1>
    <ul>
      {getPosts().map((post) => (
        <PostLink key={post.id} post={post} />
      ))}
    </ul>
    <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>
  </Layout>
)

因此,你須要把樣式寫在子組件中:

const PostLink = ({ post }) => (
  <li>
    <Link as={`/p/${post.id}`} href={`/post?title=${post.title}`}>
      <a>{post.title}</a>
    </Link>
    <style jsx>{`
      li {
        list-style: none;
        margin: 5px 0;
      }

      a {
        text-decoration: none;
        color: blue;
        font-family: "Arial";
      }

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

或者,使用全局選擇器(global selectors),只需在style標籤中加一個global關鍵字,以下:

<style jsx global>{`
      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>

部署Next.js應用

部署Next.js也是一件很是簡單的事情,咱們更改一下咱們的package.json文件,在scripts字段中添加build和start:

"scripts": {
    "dev": "node server.js",
    "build": "next build",
    "start": "next start"
}

而後執行:

npm run build
npm run start

npm run build命令會打包適用於生產環境的代碼,npm run start則會啓動咱們的應用,默認端口爲3000。

若想啓動兩個應用實例,只須要自定義端口便可,代碼以下:

"scripts": {
  "start": "next start -p $PORT"
}
npm run build

PORT=8000 npm start
PORT=9000 npm start

如上,會在8000及9000端口各自啓動一個實例。

至此,Next.js的基礎概念已經介紹完了,更高級的用法,能夠參考官方的例子:Next.js

相關文章
相關標籤/搜索