獲取數據,依然是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之處。前端
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>
)
}
}
複製代碼
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
React老生命週期內獲取數據git
以抓取用戶列表爲例,咱們能夠在組件裏的componentDidMount生命週期內獲取github
// /components/user/userList.js
...
componentDidMount() {
this.props.fetchUserList();
}
複製代碼
// /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
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項目也是在生命週期裏控制一些數據的獲取。可能更習慣吧,不過我認可第二種更牛逼一些,性能也可能更好吧~各取所需吧。
這個組件從我使用的角度來看,做用跟我前幾章有個地方的目的是同樣的,就是咱們在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裏的。
其實之前在寫服務端渲染項目的時候會遇到不少坑,最多見的就是好比我想引入一些外部組件,這些組件裏有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爲咱們提供了更多的方法,感興趣的能夠去官網看文檔,有四種異步引入的方法,其中還包含只在服務端引入~文檔地址
錯誤處理,目前不少優秀的腳手架都爲咱們提供了錯誤處理,好比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>
)
}
}
複製代碼
又一個高級功能,它支持咱們把各類路由導出成靜態頁面,不過你細想其實也沒啥大用,畢竟咱們項目都是有邏輯的,導出靜態頁面也不能操做,哈哈。不過既然是挺牛逼的一個功能,就拿來試試。
exportPathMap: async (defaultPathMap) => {
return {
'/home': { page: '/' },
'/userList': { page: '/user/userList' },
}
},
複製代碼
"scripts": {
...
// 新增導出命令
"export": "yarn build && next export"
},
複製代碼
第三步,運行yarn export命令
運行完命令以後,根目錄下會出現一個out文件夾,真的是很是神奇,裏面有頁面文件夾和必要的靜態資源。
這裏還有一個高級的Next.js項目推送到github page的功能,依賴的也是這個export,不過期間問題我就沒寫,你們感興趣的去看看官方demo,應該能夠解決的~
寫到這裏,Next.js踩坑入門系列就寫完了。很是感謝有不少小夥伴一直在看,還有一些可愛的小夥伴催更,水平有限,徹底是踩坑集錦,若是能幫助到你們真的很開心。謝謝你們的閱讀。接下來準備用Next.js搭一個網站。完成後可能會再寫一篇Next.js的建站文章,其餘的就不寫了,再寫就是其餘內容啦~
本章節代碼地址
項目代碼地址,喜歡的給個Star,謝謝米娜桑