Next.js 入門超詳解教程

Ozzie發表於2020-02-17

簡介

最近才學習了 React,現在要接觸服務端渲染,趁熱打鐵把 Next 學一下,關於 Next.js,可以移步到知乎討論:
知乎:關於 Next.js 的討論

初識Next.js

安裝

  1. Next.js 支援 Windows、Mac 和 Linux系統,均可安裝,但是前提是你已經安裝了 Node.js
  2. 建立示例專案的過程如下:
     mkdir hello-next
     cd hello-next
     npm init -y
     npm install --save react react-dom next
     mkdir pages

使用

  1. 開啟 hello-next/package.json,替換 scripts
     "scripts": {
         "dev": "next",
         "build": "next build",
         "start": "next start"
     }
  2. 啟動
     npm run dev
    在瀏覽器中開啟 http://localhost:3000,你會看到頁面顯示 404 | This page could not be found.
  3. 建立你的第一個頁面
    • 建立 pages/index.js,並輸入:
        const Index = () => (
          <div>
            <p>Hello Next.js</p>
          </div>
        );
        export default Index;
    • 再次輸入 npm run dev,就能看到效果了
    • 上述案例中,我們在 pages/index.js 模組中預設(default)匯出了一個簡單的 React 元件
  4. 試錯
    • 嘗試著錯一次:將 pages/index.js 改為:
        const Index = () => (
          <div>
            <p>Hello Next.js
          </div>
        );
        export default Index;
    • 重新啟動,瀏覽器顯示:
    • 一般情況下,Next.js 將跟蹤此類錯誤並在瀏覽器中顯示,這便於我們快速發現錯誤,而你修改程式碼並儲存後,頁面將立即出現對應結果,而不會重新載入整個頁面,這是通過 webpack 的 模組熱替換 實現的,Next 預設支援這個功能

頁面間導航

Introduction

  1. 我們的應用程式雖然很簡單,只有一個頁面,但是我們可以新增任意多個頁面,例如:
    • 建立 pages/about.js 來新建 “About” 頁面
        const About = () => (
          <div>
            <p>About Page</p>
          </div>
        );
        export default About;
    • 修改 pages/index.js
        const Index = () => (
          <div>
            <p>Hello Next.js</p>
            <a href="http://localhost:3000/about">This is a link to About-Page</a>
          </div>
        );
        export default Index;
    • 之後我們可以通過 http://localhost:3000/about 來訪問該頁面
    • 之後,我們需要連線兩個頁面,首先想到的是可以用一個 HTML 的 <a /> 標籤實現,但是結果就是:瀏覽器會向伺服器請求下一頁並重新整理當前頁面,也就是這樣做並不會執行客戶端導航
  2. 為了支援瀏覽器端導航,我們需要使用 Next.js 提供的 Link 元件,這個元件是通過 next/link 匯出的,接下來我們將使用它
  3. 我們需要準備一個簡單的 Next.js 應用課程,請在 hello-next 下輸入:
     git clone https://github.com/zeit/next-learn-demo.git
  4. 現在我們進入 hello-next/next-learn-demo/1-navigate-between-pages 啟動程式:
     cd next-learn-demo/1-navigate-between-pages
     npm install
     npm run dev

使用Link元件

注意,接下來的操作均在 hello-next/next-learn-demo/1-navigate-between-pages 下完成的

  1. pages/index.js 中新增:
     import Link from 'next/link';
     export default function Index() {
       return (
         <div>
           <p>Hello next.js</p>
           <Link href="/about">
             <a>About Page</a>
           </Link>
         </div>
       )
     }
    • 在這裡,我們將 next/link 匯入為 Link,並按照如下的方式使用:
        <Link href="/about">
          <a>About Page</a>
        </Link>
    • 訪問 3000 埠可檢視結果
  2. 這次點選連結同樣會導航到 “About” 頁面,這是客戶端導航,操作在瀏覽器中進行,而不向瀏覽器傳送請求,你可以通過開啟瀏覽器的 網路請求檢查器(network request inspector) 來驗證這一點
  3. 後退按鈕:
    當你點選連結,再點選後退時,依然會切換到歷史記錄的上一頁,也就是 next/link 為你完成了所有 location.history 的操作

新增連結道具

  1. 或許你需要在連線中新增屬性或道具,比如你需要向連結中新增 title 屬性,我們可以這樣新增它:
     <Link href="/about">
         <a title="About-Pages">About Page</a>
     </Link>
    檢視元素,可以看到結果如下:
  2. 切記不可新增到錯誤的地方去,若寫成如下:
     <Link href="/about" title="About-Pages">
         <a>About Page</a>
     </Link>
    則會在控制檯中報錯:
  3. 實際上,Link 元件上的標題道具無效,是因為 Link 只是一個包裝器元件,只接收 href 和一些類似的道具。如果需要向其新增道具,則需要將道具新增到其子項,這種情況下,Link 元件的子代是錨標記

使用共享元件

Introduction

  1. 我們可以通過匯出 React 元件並將該元件放在 pages 目錄中來建立頁面,每個頁面的 URL 都是基於檔名的,由於匯出的頁面是 JavaScript 模組,因此我們也可以將其他 JavaScript 元件匯入其中
  2. 我們將建立一個公共的 Header 元件並將其用於多個頁面,最後我們將研究實現 Layout 元件,並瞭解它如何幫我們定義多個頁面的外觀

執行

  1. 我們之前已經安裝過了 next-learn-demo,這裡直接使用:
    • 進入 hello-next/next-learn-demo/2-using-shared-components,之後我們的操作也會在此目錄下
  2. 執行:
     npm install
     npm run dev

建立標題元件

  1. 下面建立一個 Header 元件,建立 2-using-shared-components/components/Header.js
     import Link from 'next/link';
     const linkStyle = {
       marginRight: 15,
     };
     const Header = () => (
       <div>
         <Link href="/">
           <a style={marginRight}>Home</a>
         </Link>
         <Link href="/about">
           <a style={marginRight}>About</a>
         </Link>
       </div>
     );
     export default Header;
  2. 現在,匯入 Header 元件並在頁面中使用它:
    • index.js 修改為:
        import Header from '../components/Header';
        export default function Index() {
          return (
            <div>
              <Header />
              <p>Hello Next.js</p>
            </div>
          )
        }
    • about.js 修改為:
        import Header from '../components/Header';
        export default function Index() {
          return (
            <div>
              <Header />
              <p>This is the About Page</p>
            </div>
          )
        }
    • 啟動之後可以檢視結果
  3. 試錯:現在將 components 目錄改名為 comps,報錯如下:
    • 我們不需要將我們的元件放在一個特殊的目錄裡,也就是說,該元件目錄名稱可以取為任何,實際上,唯一特殊的目錄是 /pages/public,你甚至可以在 /pages 裡面建立元件.

佈局元件

本節依然是在 2-using-shared-components/ 下完成的

  1. 我們將建立 Layout 元件,以實現各頁面上的通用樣式,在 components/MyLayout.js 中輸入:
     import Header from './Header';
     const layoutStyle = {
       margin: 20,
       padding: 20,
       border: '1px solid #DDD'
     };
     const Layout = props => (
       <div style={layoutStyle}>
         <Header />
         {props.children}
       </div>
     );
     export default Layout;
  2. 完成操作後,我們可以在頁面中使用以下佈局:
    • pages/index.js 中輸入:
        import Layout from '../components/MyLayout';
        export default function Index() {
          return (
            <Layout>
              <p>Hello Next.js</p>
            </Layout>
          )
        }
    • pages/about.js 中輸入:
        import Layout from '../components/MyLayout';
        export default function About() {
          return (
            <Layout>
              <p>This is the about page</p>
            </Layout>
          )
        }
    • 啟動,檢視樣式
  3. 試錯:將 MyLayout.js 中的 {props.children} 刪除,再啟動,觀察結果:
    • 頁面上只保留了 Header 的內容,其他的均消失了

渲染子元件

  1. 前一個試錯中,我們刪除了 {props.children},則 Layout 無法呈現我們放入 Layout 元素內的內容,如下所示:
     export default function About() {
       return (
         <Layout>
           <p>This is the about page</p>
         </Layout>
       );
     }
    但這只是建立佈局元件的一種方法,以下是其他方法。
  2. 方法一:佈局為高階元件
     // components/MyLayout.js
     import Header from './Header';
     const layoutStyle = {
       margin: 20,
       padding: 20,
       border: '1px solid #DDD'
     };
     const withLayout = Page => {
       return () => (
         <div style={layoutStyle}>
           <Header />
           <Page />
         </div>
       );
     };
     export default withLayout;
     // pages/index.js
     import withLayout from '../components/MyLayout';
     const Page = () => <p>Hello Next.js</p>;
     export default withLayout(Page);
     // pages/about.js
     import withLayout from '../components/MyLayout';
     const Page = () => <p>This is the about page</p>;
     export default withLayout(Page);
  3. 方法二:頁面內容作為道具
     // components/MyLayout.js
     import Header from './Header';
     const layoutStyle = {
       margin: 20,
       padding: 20,
       border: '1px solid #DDD'
     };
     const Layout = props => (
       <div style={layoutStyle}>
         <Header />
         {props.content}
       </div>
     );
     export default Layout;
     // pages/index.js
     import Layout from '../components/MyLayout.js';
     const indexPageContent = <p>Hello Next.js</p>;
     export default function Index() {
       return <Layout content={indexPageContent} />;
     }
     // pages/about.js
     import Layout from '../components/MyLayout.js';
     const aboutPageContent = <p>This is the about page</p>;
     export default function About() {
       return <Layout content={aboutPageContent} />;
     }

建立動態頁面

Introduction

  1. 之前通過使用元件,我們建立了包含了多個頁面的小案例,之前為了建立一個頁面,我們必須新建一個檔案作為模組匯出,但是在一個真正的應用程式中,我們還需動態地建立頁面以顯示動態內容,接下來我們會使用 查詢字串 來實現這一點
  2. 我們將建立一個簡單的部落格應用,它在主頁上展示一個所有文章的列表,展示如下:
    • 主頁有文章列表
    • 點選某標題的連結,會出現對應的文章

安裝設定

  1. 我們仍然使用之前安裝過的 next-learn-demo,進入 next-learn-demo/3-create-dynamic-pages,接下來的一切也將在這個目錄下完成
  2. 執行
     npm install
     npm run dev

新增文章列表

  1. 首先,我們在文章主頁新增標題列表,如下:
     import Link from 'next/link';
     import Layout from '../components/MyLayout.js';
     const PostLink = props => (
       <li>
         <Link href={`/post?title=${props.title}`}>
           <a>{props.title}</a>
         </Link>
       </li>
     );
     export default function Blog() {
       return (
         <Layout>
           <h1>My Blog</h1>
           <ul>
             <PostLink title="Hello Next.js" />
             <PostLink title="Learn Next.js is awesome" />
             <PostLink title="Deploy apps with Zeit" />
           </ul>
         </Layout>
       )
     }

通過查詢字串傳遞資料

  1. 我們將通過查詢字串作為引數(也稱查詢引數)並傳遞資料,如:
     // pages/index.js
     const PostLink = props => (
       <li>
         <Link href={`/post?title=${props.title}`}>
           <a>{props.title}</a>
         </Link>
       </li>
     );
    • 此例中,查詢引數是 title,我們使用 PostLink 來執行的操作
    • 你也可以檢查 Link 元件的 href 屬性,以此類推,你可以使用查詢字串傳遞任何型別的資料

建立Post頁面

  1. 現在我們需要建立 post 頁面來顯示部落格文章,為此,我們需要從查詢字串中獲得標題,
  2. 建立 pages/post.js 檔案:
     import { useRouter } from 'next/router';
     import Layout from '../components/MyLayout';
     const Page = () => {
       const router = useRouter();
       return (
         <Layout>
           <h1>{router.query.title}</h1>
           <p>This is the blog post content.</p>
         </Layout>
       );
     };
     export default Page;
    • 啟動專案,並點選三個標題連結
  3. 上面的運作過程如下:
    • 首先從 next/router 匯入並使用 useRouter 函式,該函式返回 Next.js 的是 router 物件
    • 使用路由器(router)中的 query 物件,該物件儲存了所有查詢引數
    • 然後,使用 router.query.title 獲取標題
  4. useRouter 函式的介紹:
    • useRouter 允許你訪問頁面中的 router 物件,它是一個 React Hook,能與功能元件協同合工作
    • 之前的示例中,useRouter 函式被放到預新增的頁面元件中,而下面示例中,useRouter 函式在 Content 元件中,預新增的元件是 Page,但是功能不變
        import { useRouter } from 'next/router';
        import Layout from '../components/MyLayout';
        const Content = () => {
          const router = useRouter();
          return (
            <>
              <h1>{router.query.title}</h1>
              <p>This is the blog post content.</p>
            </>
          );
        };
        const Page = () => (
          <Layout>
            <Content />
          </Layout>
        );
        export default Page;

使用動態路由清理URL

Introduction

請確保你正在使用是 Next.js 9 或更高版本
接下來的操作都會在 next-learn-demo/4-clean-urls 中進行,請調至指定目錄

  1. 我們已經知道了如何使用查詢字串建立動態頁面,指向我們的某個部落格文章的連結如:
     http:// localhost:3000 / post?title = Hello%20Next.js
    而此連結要表達的卻是:
     http:// localhost:3000 / p / hello-nextjs

動態路由

  1. 啟動專案,在 4-clean-urls/ 下輸入:

     npm install
     npm run dev
  2. 我們將使用 Next.js 的 動態路由 功能,它允許你處理 /pages 動態路由

  3. 現在我們將建立新頁面,並命名為 pages/p/[id].js,這也是我們建立的第一個動態路由,步驟如下:

    • 首先,在 /pages 內新增資料夾 /p
    • 然後,你需要在 /p 資料夾中建立 [id].js,並在這些 js 檔案中新增如下內容:
        import { useRouter } from 'next/router';
        import Layout from '../../components/MyLayout';
        export default function Post() {
          const router = useRouter();
          return (
            <Layout>
              <h1>{router.query.id}</h1>
              <p>This is the blog post content.</p>
            </Layout>
          );
        };
  4. 前一頁是特殊的,它不會處理 /about 等靜態路由,而是會處理 p/ 之後的路由,例如,此頁面將處理 /p/hello-next.js,而 /p/post-1/another

  5. 頁面名稱中的帶有方括號([])使其成為動態路由,你不能使頁面名稱的一部分成為動態名稱,而只能使全名成為動態名稱,例如,支援 /pages/p/[id].js,但不支援 /pages/p/post-[id].js

  6. 建立動態路線時,我們在方括號([])之間新增了 id,這是頁面接受到查詢引數的名稱,因此對於 /p/hello-nextjs,該 query 物件將具有 { id: 'hello-nextjs' },我們可以使用 useRouter() 進行訪問

  7. 現在,我們新的動態路由新增多個連結,修改 pages/index.js

     // pages/index.js
     import Layout from '../components/MyLayout';
     import Link from 'next/link';
    
     const PostLink = props => (
       <li>
         <Link href="/p/[id]" as={`/p/${props.id}`}>
           <a>{props.id}</a>
         </Link>
       </li>
     )
     export default function Blog() {
       return (
         <Layout>
           <h1>My Blog</h1>
           <ul>
             <PostLink id="Hello-Next.js" />
             <PostLink id="Learn-Next.js" />
             <PostLink id="Deploy-Next.js" />
           </ul>
         </Layout>
       )
     }
    • 著重看看以下內容:
        const PostLink = props => (
          <li>
            <Link href="/p/[id]" as={`/p/${props.id}`}>
              <a>{props.id}</a>
            </Link>
          </li>
        )
      • <Link> 元素中,href 代表的是該頁面在 pages 資料夾中的路徑,而 as 代表的是該頁面在瀏覽器中的 URL 路徑
    • 現在,你可以重新啟動專案,注意觀察 URL 的變化!
  8. 動態路由可以很好地和瀏覽器歷史記錄配合使用,而我們要做的就是將 as 新增到連結元件中


為頁面獲取資料

Introduction

接下來的操作都會在 next-learn-demo/6-fetching-data 中進行,請調至指定目錄

  1. 現在我們已經能建立一個相對完整的 Next.js 應用,但還沒有解決的是:如何從遠端資料來源中獲取資料?,Next.js 提供了一個標準 API 來獲取頁面所需的資料,即 getInitialProps 非同步函式
  2. getInitialProps 只能新增到頁面匯出的預設元件中,在其他元件中是不會起作用的,它可以從遠端資料來源為指定頁面獲取資料,並將這些資料通過 props 傳遞到我們的頁面,它會同時在客戶端和伺服器上工作,因為它在兩個環境中都會被呼叫
  3. 我們將利用 getInitialProps 構建一個應用程式來顯示有關 Batman TV Shows 的資訊,利用的是公開的 TVmaze API
  4. 在即將演示的示例中,我們的主頁上有一個文章列表,現在我們來展示 Batman TV shows 的節目列表,我們將從遠端伺服器上獲取這些節目列表,而不是硬編碼
    • 在這個示例中,我們使用的是 TVMaze API 來獲取 TV shows 節目列表,這是一個搜尋電視節目的 API

安裝設定

  1. 進入 next-learn-demo/6-fetching-data,輸入:
     npm install
     npm run dev
  2. 在瀏覽器中開啟 http://localhost:3000/ 檢視專案

獲取 Batman Shows 的資料

  1. 首先,我們需要安裝 isomorphic-unfetch,這是我們用來獲取資料的工具庫,這是瀏覽器的 fetch API 的一個簡單實現,但在客戶端和伺服器環境中都可以使用
     npm install --save isomorphic-unfetch
  2. pages/index.js 替換為以下內容:
     // pages/index.js
     import Layout from '../components/MyLayout';
     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 href="/p/[id]" as={`/p/${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.map(entry => entry.show)
       };
     };
     export default Index;
  3. 我們著重分析下面這部分:
     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.map(entry => entry.show)
       };
     };
    • 這是一個靜態非同步函式,可以新增到程式的任何頁面中,使用此函式,我們就可以獲取資料並作為 props 傳遞給我們的頁面
    • 以下便是我們的抓取結果,資料被抓取後,將會作為 props 的 ‘show’ 屬性傳遞我們的頁面中
  4. 注意,我們之前有一行用於列印資訊的程式碼:
     console.log(`Show data fetched. Count: ${data.length}`);
    • 那麼到底是在伺服器端輸出呢,還是在瀏覽器端的控制檯輸出呢,現在重新整理一下瀏覽器,會發現之後服務端的控制檯顯示
    • 在這種情況下,訊息只會在服務端輸出,因為我們的頁面是在服務端繪製的,所以,我們在服務端已經有了資料,沒有必要在客戶端再次獲取這些資料

實現 Post 頁面

  1. 現在讓我們把 TV show 的詳細資訊新增到 post 中:將 pages/p/[id].js 替換為以下內容:
     // pages/p/[id].js
     import Layout from '../../components/MyLayout';
     import fetch from 'isomorphic-unfetch';
     const Post = props => (
       <Layout>
         <h1>{props.show.name}</h1>
         <p>{ props.show.summary.replace(/<[/]?[pb]>/g), '' }</p>
       </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;
    • 注意該頁的 getInitialProps
        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 };
        };
    • 該函式的第一個引數是 context 物件,此物件包含一個 query 物件,我們用 context.query 來獲取資訊,即 id 物件,並使其在 TVMaze API 中獲取電視節目資料
  2. 在這個 getInitialProps 函式中,我們新增了一個 console.log 來列印節目的標題,現在我們看看它將列印到哪裡
    • 開啟伺服器和客戶端的控制檯,然後啟動專案,訪問 3000 埠
    • 單擊第一個 Batman show 的標題
  3. 結果是:會在客戶端的控制檯輸出
    • 與之前不同的是,我們這次只能在客戶端看到訊息,這是因為我們通過客戶端導航到了 post 頁面
    • 當我們單擊連結時,由於該連結是被 Next.js 的 <Link> 元件包裝過的元件,所以頁面轉換將在瀏覽器中進行,而不會想伺服器發起請求
    • 但是,如果你直接訪問的是 post 頁面,而不是點選連結(例如,你直接訪問 http://localhost:3000/p/975 ),訊息會被列印在服務端,而不是客戶端

為元件新增樣式

Introduction

接下來的操作都會在 next-learn-demo/7-styling-components 中進行,請調至指定目錄

  1. 對於 React,我們可以使用許多不同的技術來設定樣式,這些技術可以分為兩大類:
    • 傳統的基於 CSS 檔案的樣式設計(包括 SASS、PostCSS 等)
    • CSS in JS
  2. 傳統的基於 CSS 檔案的樣式設計(尤其是 SSR)需要考慮一堆的實際問題,因此我們在為 Mext.js 設定樣式時避免使用這種方法,相反,我們會在 JS 中使用 CSS,你可以使用它來設定某單個元件的樣式,而不是匯入 CSS 檔案
  3. 為此,我們需要認識一個新的框架:styled-jsx,這是Next.js 預裝了一個 CSS in JS 框架,它允許你為元件編寫熟悉的 CSS 規則,這些 CSS 規則對元件以外的任何內容(當然也包括子元件)都沒有影響,也就是說,你的 CSS 規則是有作用域的

安裝設定

  1. 進入 next-learn-demo/7-styling-components
  2. 輸入
     npm install
     npm run dev
  3. 啟動專案,訪問應用

設定主頁的樣式

  1. 現在我們在主頁中新增一些樣式,進入 pages/index.js

     // pages/index.js
    
     import Layout from '../components/MyLayout';
     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 function Blog() {
       return (
         <Lauout>
           <h1>My Blog</h1>
           <ul>
             {
               getPosts().map(post => (
                 <li>
                   <Link href="/p/[id]" as={`/p/${post.id}`}>
                     <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: red;
             }
             a:hover {
               opacity: 0.6;
             }
           `}</style>
         </Lauout>
       )
     }
    • 那個 <style jsx> 元素,就是我們編寫 CSS 規則的地方
    • 現在可以啟動專案,檢視結果
  2. 上面的程式碼中,我們沒有直接在 <style> 標籤內編寫樣式程式碼,而是寫在模板字串({``})裡面的,Next.js 預設支援 babel 語法,而 styled-jsx 可以看做 babel 的一個外掛,它將解析所有 CSS 並將其應用於構建工程

CSS樣式和巢狀元件

  1. 現在我們將會對主頁做一些更改,我們將會像這樣隔離 Link 元件:

     import Layout from '../components/MyLayout';
     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 href="/p/[id]" as={`/p/${post.id}`}>
           <a>{post.title}</a>
         </Link>
       </li>
     );
    
     export default function Blog() {
       return (
         <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>
       );
     }
    • 執行後發現:h1 的樣式還在,但是連結已經失效了,如下:
  2. 上述結果顯示:CSS 樣式規則對子元件中的元素沒有影響,styled-jsx 這個特性可以幫助你管理大型的應用程式的樣式,在這種情況下,我們需要直接設定子元件的樣式,在我們的示例中,我們需要對 Link 元件執行以下操作:

     const PostLink = ({ post }) => (
       <li>
         <Link href="/p/[id]" as={`/p/${post.id}`}>
           <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>
     );

全域性樣式

  1. 有時我們確實需要更改子元件內部的樣式,尤其是在使用支援 React 的 markdown 時

    • 我們需要安裝 react-markdown
        npm install --save react-markdown
  2. 下面就是全域性樣式派上用場的地方,現在就來試試利用 styled-jsx 新增一些全域性樣式,開啟 pages/p/[id].js

     import { useRouter } from 'next/router';
     import Markdown from 'react-markdown';
     import Layout from '../../components/MyLayout';
    
     export default () => {
       const router = useRouter();
       return (
         <Layout>
           <h1>{router.query.id}</h1>
           <div className="markdown">
             <Markdown
               source={`
     This is our blog post.
     Yes. We can have a [link](/link).
     And we can have a title as well.
    
     ### This is a titlr.
    
     And here's the content.
               `}
             />
           </div>
           <style jsx global>{`
             .markdown {
               font-family: 'Arial';
             }
             .markdown a {
               text-decoration: none;
               color: red;
             }
             .markdown a:hover {
               opacity: 0.6;
             }
             .markdown h3 {
               margin: 0;
               padding: 0;
               text-transform: uppercase;
             }
           `}</style>
         </Layout>
       );
     };
    • 注意,markdown 語法部分的縮排不可修改!!!
    • 我們定義的 style 作用於整個 <div> 標籤部分,也就是作用於全域性,雖然這樣很方便,但是還是建議寫帶有作用域的樣式
    • 儘管如此,這依然是一個比普通樣式標籤更好的解決方案,使用 styled-jsx 時,所有必要的特定於瀏覽器廠商字首和 CSS 校驗都通過了一個 Babel 外掛完成了,這並不會導致額外的開銷

部署 Next.js 應用程式

Introduction

接下來的操作都會在 next-learn-demo/8-deploying 中進行,請調至指定目錄

  1. ZEIT Now 是將應用程式部署到生產環境的最簡單和可擴充套件的方法,但是,你可以部署 Next.js 應用程式,而且它相對來講比較簡單
  2. 現在我們將部署 Next.js 應用程式
  3. 進入 next-learn-demo/8-deploying,輸入命令:
     npm install
     npm run dev

部署到 ZEIT Now

  1. 我們來看一下 package.json 檔案的 script 配置段:
     "scripts": {
        "dev": "next",
        "build": "next build",
        "start": "next start"
      },
  2. 首先,我們需要為生產環境編譯我們的 Next.js 應用程式,它將生成一組優化的用於生產環境的程式碼:
     npm run build
  3. 然後,你需要啟動 Next.js 應用程式並監聽某個埠,此伺服器將執行伺服器端渲染並返回靜態頁面(使用上述命令編譯)
     npm run start

執行兩個例項

  1. 現在我們將為我們的應用程式啟動兩個例項,通常這樣是為了橫向擴充套件我們的應用程式
  2. 首先,將 package.json 中的 script 配置端替換為以下的內容:
     "scripts": {
       "dev": "next",
       "build": "next build",
       "start": "next start -p %PORT%"
     }
    • 我們更改了 start 指令碼,它解除安裝接受一個代表埠號的引數來啟動應用程式
    • 注意,%PORT% 是對於 Windows 來說的,Linux 對應的是 $PORT
  3. 現在構建我們的應用程式:
     npm run build
  4. 現在我們需要安裝一個新的包,即:cross-env,我們在全域性環境下安裝:
     npm install cross-env -g
  5. 同時開啟兩個終端,分別在終端中輸入:
     cross-env PORT=8000 npm start
     cross-env PORT=9000 npm start
    • 在 Linux 上,則直接開啟兩個命令列終端,並分別在每個命令列終端上執行:
        PORT=8000 npm start
        PORT=9000 npm start
  6. 現在,在瀏覽器分別檢視:http://localhost:8000http://localhost:9000
  7. 可以得到的結果是,你只需要對應用程式構建一次。然後就可以在任意多個埠上啟動它

結語

求進之路,持之謙卑,此教程是從官網理解翻譯來的,增刪了一些知識,我不在乎別人怎麼評價,我已經進步了…

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章