Next.js踩坑入門系列(七) —— 其餘相關知識

Next.js踩坑入門系列

獲取數據&&getInitialProps

獲取數據,依然是Next與普通的React SPA應用不一樣的地方,React應用基本都有本身的路由組件(固然大部分是react-router),咱們能夠經過路由組件爲咱們提供的方法,好比react-router的onEnter()方法或者universal-router的beforeEnter()方法。css

這裏給你們推薦一個區別於react-router的路由組件universal-routerhtml

而Next.js沒有路由組件,因此具體方式確定不一樣於路由組件的方式,具體不一樣就體如今Next.js爲咱們提供了一個區別於React的新生命週期——getIntialProps(),下面來講說這個API的牛X之處。前端

使用方法

  • 在React.Component使用
import React from 'react'

   export default class extends React.Component {
     static async getInitialProps({ req }) {
       const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
       return { userAgent }
     }
   
     render() {
       return (
         <div>
           Hello World {this.props.userAgent}
         </div>
       )
     }
   }

複製代碼
  • 在stateless組件內使用
const Page = ({ stars }) =>
     <div>
       Next stars: {stars}
     </div>
   
   Page.getInitialProps = async ({ req }) => {
     const res = await fetch('https://api.github.com/repos/zeit/next.js');
     const json = await res.json();
     return { stars: json.stargazers_count };
   }
   
   export default Page;

複製代碼

這個生命週期是脫離於React的正常生命週期的,不過咱們依然能夠在組件里正常使用react組件的各類生命週期函數。react

服務端可用

這真是getInitialProps這個生命週期的過人之處了,他能夠在服務端運行,這樣作有什麼好處呢?說實話,我真不太清楚,我只知道一點,下面會講,哈哈。若是有大牛知道的話,能夠在留言給我講講~話很少說,上圖:webpack

能夠看到,這個生命週期我觸發了action獲取數據,而這個action在控制檯被打印出來了,說明能夠運行在服務端~

減小抓取數據的次數

  • React老生命週期內獲取數據git

    以抓取用戶列表爲例,咱們能夠在組件裏的componentDidMount生命週期內獲取github

// /components/user/userList.js
 ...
 componentDidMount() {
    this.props.fetchUserList();
 }
複製代碼

從上圖咱們能夠看出來,每次進入用戶列表頁,都會從新抓取用戶數據。有人可能會說,這不廢話嗎,react不就這樣嗎,路由都切換了啊。沒錯,正常就是應該這樣,因此才說Next.js的這個新生命週期牛逼啊。

  • 使用getInitialProps生命週期
// /pages/user/userList.js
import UserList from '../../containers/user/UserList';
import { fetchUserListData } from '../../redux/actions/user';

UserList.getInitialProps = async (props) => {
  const { store, isServer } = props.ctx;
  if (store.getState().user.list.list.length === 0) {
    store.dispatch(fetchUserListData());
  }
  return { isServer };
};

export default UserList;


複製代碼

兄弟們,看看上圖,發現沒,進入系統後只會在第一次進入路由的時候獲取數據,以後再進入由於服務端緩存過數據,因此不須要從新獲取,減小了獲取次數~

具體緣由就是由於static getInitialProps()這個生命週期是能夠在服務端運行的,當頁面第一次加載時,服務器收到請求,getInitialProps()會執行,getInitialProps()返回的數據,會序列化後添加到 window.__NEXT_DATA__.props上,寫入HTML源碼裏,相似於。這樣服務端的getInitialProps()就實現了把數據傳送給了客戶端。當咱們經過Next.js的路由Link來進行頁面跳轉的時候,客戶端就會從window.__NEXT_DATA__裏獲取數據渲染頁面,就無需從新獲取數據,算是提高性能的話一種方式吧~以下圖所示:web

存在問題——踩坑

這裏其實還真遇到一個坑,可能有不少人遇到過了,也可能沒人遇到過。具體問題描述起來大概是這個樣子,咱們在getInitialProps裏面預獲取數據,以用戶列表爲例,在首次加載的時候都是沒有問題的包括各類客戶端跳轉。不過當咱們在用戶列表頁面進行刷新的時候,其實他就沒有再走getInitialProps這個生命週期了,所以頁面會沒有能夠渲染的數據,就會出現空頁面,由於他認爲這個應該從window.__Next_DATA__裏面獲取,而不是從新獲取數據~那麼爲何刷新頁面以後沒有走這個getIntialProps,講道理,我還真沒太弄清楚,不過確實刷新頁面next.js會給咱們在props裏返回一個isServer:true,可是控制檯並無獲取數據。具體問題見下面截圖:json

從截圖咱們能夠很清楚地看到,頁面數據經過redux-saga獲取,在pages的getIntialProps()裏面,代碼以下:

import { fetchUserListData } from '../../redux/actions/user';

UserList.getInitialProps = async (props) => {
  const { store, isServer } = props.ctx;
  if (store.getState().user.list.list.length === 0) {
    store.dispatch(fetchUserListData());
  }
  return { isServer };
};
複製代碼

上面fetchUserListData()就是抓取數據的action,返回值就會存入state,渲染數據列表。很明顯,在第一次加載的時候是抓取成功的。可是刷新頁面後,沒有dispatch這個action,也就是代表,刷新頁面沒有走這個getIntialProps這個生命週期!!!redux

上面纔是關鍵問題所在,不刷新頁面的狀況下是正常的,刷新頁面沒有走這個生命週期,而咱們不少數據都是須要預獲取的,因此說還挺坑的,事實上,不少人遇到這個問題,並且我在next官方給出的reudx-demo裏面也發現這個問題,也就是說他們官方的demo刷新也會出現這個問題。

解決辦法

既然是踩坑,固然有解決辦法啦~並且仍是兩種:

  • 第一種:在組件生命週期裏判斷isServer

    剛剛問題描述過了,也就是正常加載和經過路由跳轉頁面,數據會正常渲染且會從瀏覽器的window.__NEXT_DATA__獲取來減小沒必要要的網絡請求~,而在頁面進行刷新的時候不會從新請求數據而且window.__NEXT_DATA__裏也找不到咱們想要的數據。不過經過控制檯信息咱們能夠發現問題所在以及解決辦法。那就是,第一次啓動系統的時候返回的isServer是false,而瀏覽器刷新頁面的時候isServer返回的是true,咱們能夠在組件裏進行這個變量的判斷,若是是true,就從新進行一次數據抓取。

// /components/user/UserList.js
...
componentDidMount() {
  if(this.props.isServer) {
  // 須要從新抓取數據
    this.props.fetchUserListData();
  }
}
...
複製代碼

從上圖能夠看到,刷新頁面的時候,咱們會從新獲取數據渲染頁面,若是不刷新就不會從新獲取。仍是可行的這個方法~

  • 第二種:換一種方式預獲取數據

    另外一種方法就比較高級了,原理我依然不知道,可是就是好用,哈哈,這東西真是邪門,爲何這麼說呢,其實本質沒改變什麼,就是換了種寫法就能夠。具體就是,上面的寫法我在getInitalProps裏面寫了dispatch了一個獲取數據的action,從上一節或者代碼裏大家能夠看到,其實這個action就是fetch一個api獲取數據返回state。這就是redux一個獲取數據的基本過程,這種方法在刷新時行不通,而行得通的方法是:不經過dispatch action的方式獲取數據,而是直接在getIntialProps裏面經過fetch api的方式獲取數據,這樣每次刷新頁面也均可以獲取到數據了。。。就是這麼神奇,我也真不知道爲啥。

// /pages/user/userList
import fetch from 'isomorphic-unfetch';
import UserList from '../../containers/user/UserList';
import { fetchUserListDataSuccess } from '../../redux/actions/user';

UserList.getInitialProps = async (props) => {
  const { store, isServer } = props.ctx;
  let userData;
  if (store.getState().user.list.list.length === 0) {
    const res = await fetch('https://jsonplaceholder.typicode.com/users');
    userData = await res.json();
    store.dispatch(fetchUserListDataSuccess(userData));
  }
  return { isServer };
};

export default UserList;
複製代碼

就是很神奇有木有,說實話我是真不知道爲啥,有大牛的話真心給我講講萬分感謝了~ 不過這兩種寫法我仍是比較喜歡上面第一種的,由於以爲第一種在本身可控範圍內,由於之前寫react項目也是在生命週期裏控制一些數據的獲取。可能更習慣吧,不過我認可第二種更牛逼一些,性能也可能更好吧~各取所需吧。

Document

這個組件從我使用的角度來看,做用跟我前幾章有個地方的目的是同樣的,就是咱們在Next.js裏沒有相似create-react-app裏面的index.html。所以咱們沒有辦法定義最後渲染的html的結構,好比title,meta等標籤。我最開始是經過next/head的Head組件來實現的,可是head組件其實最後生成的就是html的head標籤。而Document組件是徹底幫助咱們構造html結構。

// 除去Layout的Head結構
 // pages文件夾新增_document.js文件

  // ./pages/_document.js
  import Document, { Head, Main, NextScript } from 'next/document';
  
  export default class MyDocument extends Document {
    static async getInitialProps(ctx) {
      const initialProps = await Document.getInitialProps(ctx);
      return { ...initialProps };
    }
  
    render() {
      return (
        <html>
          <Head>
            <meta name='viewport' content='width=device-width, initial-scale=1' />
            <meta charSet='utf-8' />
            <title>Next-Antd-Scafflod</title>
            <link rel='shortcut icon' href='/static/favicon.ico' type='image/ico'/>
            <link rel='stylesheet' href='/_next/static/style.css' />
          </Head>
          <body>
            <Main />
            <NextScript />
          </body>
        </html>
      );
    }
  }
複製代碼

_document.js是隻在Next.js的服務端來進行渲染的,客戶端只是拿到服務端渲染事後的html字符串渲染前端頁面,上面提到的window.__NEXT_DATA__就是存放在NextScript裏的。

Dynamic Import

其實之前在寫服務端渲染項目的時候會遇到不少坑,最多見的就是好比我想引入一些外部組件,這些組件裏有window,document等這種客戶端變量,而這些變量在服務端是不存在的,所以在服務端渲染的時候就會報錯,因此就很麻煩,須要webpack各類配置而後在異步引入。好比:富文本編輯器。而next直接爲咱們封裝了動態引入的import,不出意外用的應該就是webpack的import方法,管他呢,好用就行。下面就給你們簡單是演示一下其中一個功能,就是動態引入一個富文本編輯器,而後空白期loading另外一個組件~用法很是簡單,就是下面這樣:

import dynamic from 'next/dynamic';

const DynamicComponent = dynamic(import('braft-editor'), {
  loading: () => <p>正在加載組件...</p>
});

render() {
    return (
      <Fragment>
        <h1>用戶信息:{this.state.username}</h1>
        <div style={{ width: '50%', height: '400px', }}>
          <DynamicComponent />
        </div>
      </Fragment>
    );
  }
複製代碼

詳細的Next爲咱們提供了更多的方法,感興趣的能夠去官網看文檔,有四種異步引入的方法,其中還包含只在服務端引入~文檔地址

error handling

錯誤處理,目前不少優秀的腳手架都爲咱們提供了錯誤處理,好比404和500的時候的頁面渲染,Next.js一樣,內部自動爲咱們封裝了errorPage。也就是咱們其實什麼都不用幹,就能夠享受這個服務。好比我在系統裏隨便輸入一個網址,會出現下面的結果:

而後你還能夠本身定義你的errorPage頁面,方法很是的簡單,就是在pages文件夾下面新建一個_error.js的文件,裏面寫上你的errorPage代碼就能夠了,下面就簡單寫一個,其實就是從官網扒下來的~

// /pages/_error.js
import React from 'react'

export default class Error extends React.Component {
  static getInitialProps({ res, err }) {
    const statusCode = res ? res.statusCode : err ? err.statusCode : null;
    return { statusCode }
  }

  render() {
    return (
      <p>
        {this.props.statusCode
          ? `An error ${this.props.statusCode} occurred on server`
          : 'An error occurred on client'}
      </p>
    )
  }
}

複製代碼

ok,能夠看到,很明顯的生效了。雖然效果差很少,可是你若是按照本身的來寫,確定是沒問題的。哈哈~

Static HTML export

又一個高級功能,它支持咱們把各類路由導出成靜態頁面,不過你細想其實也沒啥大用,畢竟咱們項目都是有邏輯的,導出靜態頁面也不能操做,哈哈。不過既然是挺牛逼的一個功能,就拿來試試。

  • 第一步,在config文件夾裏配置一下頁面和路由
exportPathMap: async (defaultPathMap) => {
    return {
      '/home': { page: '/' },
      '/userList': { page: '/user/userList' },
    }
  },
複製代碼
  • 第二步,package.json添加export命令
"scripts": {
    ...
    // 新增導出命令
    "export": "yarn build && next export"
  },
複製代碼
  • 第三步,運行yarn export命令

    運行完命令以後,根目錄下會出現一個out文件夾,真的是很是神奇,裏面有頁面文件夾和必要的靜態資源。

而後咱們打開index.html訪問一下應該就是咱們的首頁了,首頁就是下面這個樣子。
emm...這個首頁有點奇怪,靜態資源和css都不太對勁兒,至於爲何我就不去追究了,確定有辦法的。不過我只是試試功能,時間有限準備休息了,哈哈。感興趣的你們本身研究研究。

這裏還有一個高級的Next.js項目推送到github page的功能,依賴的也是這個export,不過期間問題我就沒寫,你們感興趣的去看看官方demo,應該能夠解決的~

總結

寫到這裏,Next.js踩坑入門系列就寫完了。很是感謝有不少小夥伴一直在看,還有一些可愛的小夥伴催更,水平有限,徹底是踩坑集錦,若是能幫助到你們真的很開心。謝謝你們的閱讀。接下來準備用Next.js搭一個網站。完成後可能會再寫一篇Next.js的建站文章,其餘的就不寫了,再寫就是其餘內容啦~
本章節代碼地址

項目代碼地址,喜歡的給個Star,謝謝米娜桑

相關文章
相關標籤/搜索