Next.js踩坑入门系列(七) —— 其他相关知识
Next.js踩坑入門系列
- (一) Hello Next.js
- (二) 添加Antd && CSS
- (三) 目錄重構&&再談路由
- (四) Next.js中期填坑
- (五) 引入狀態管理Redux
- (六) 再次重構目錄
- (七) 其他相關知識
獲取數據&&getInitialProps
獲取數據,依然是Next與普通的React SPA應用不同的地方,React應用基本都有自己的路由組件(當然大部分是react-router),我們可以通過路由組件為我們提供的方法,比如react-router的onEnter()方法或者universal-router的beforeEnter()方法。
這里給大家推薦一個區別于react-router的路由組件universal-router
而Next.js沒有路由組件,所以具體方式肯定不同于路由組件的方式,具體不同就體現在Next.js為我們提供了一個區別于React的新生命周期——getIntialProps(),下面來說說這個API的牛X之處。
使用方法
- 在React.Component使用
- 在stateless組件內使用
這個生命周期是脫離于React的正常生命周期的,不過我們依然可以在組件里正常使用react組件的各種生命周期函數。
服務端可用
這真是getInitialProps這個生命周期的過人之處了,他可以在服務端運行,這樣做有什么好處呢?說實話,我真不太清楚,我只知道一點,下面會講,哈哈。如果有大牛知道的話,可以在留言給我講講~話不多說,上圖:
可以看到,這個生命周期我觸發了action獲取數據,而這個action在控制臺被打印出來了,說明可以運行在服務端~減少抓取數據的次數
-
React老生命周期內獲取數據
以抓取用戶列表為例,我們可以在組件里的componentDidMount生命周期內獲取
- 使用getInitialProps生命周期
具體原因就是因為static getInitialProps()這個生命周期是可以在服務端運行的,當頁面第一次加載時,服務器收到請求,getInitialProps()會執行,getInitialProps()返回的數據,會序列化后添加到 window.__NEXT_DATA__.props上,寫入HTML源碼里,類似于。這樣服務端的getInitialProps()就實現了把數據傳送給了客戶端。當我們通過Next.js的路由Link來進行頁面跳轉的時候,客戶端就會從window.__NEXT_DATA__里獲取數據渲染頁面,就無需重新獲取數據,算是提升性能的話一種方式吧~如下圖所示:
存在問題——踩坑
這里其實還真遇到一個坑,可能有很多人遇到過了,也可能沒人遇到過。具體問題描述起來大概是這個樣子,我們在getInitialProps里面預獲取數據,以用戶列表為例,在首次加載的時候都是沒有問題的包括各種客戶端跳轉。不過當我們在用戶列表頁面進行刷新的時候,其實他就沒有再走getInitialProps這個生命周期了,因此頁面會沒有可以渲染的數據,就會出現空頁面,因為他認為這個應該從window.__Next_DATA__里面獲取,而不是重新獲取數據~那么為什么刷新頁面之后沒有走這個getIntialProps,講道理,我還真沒太弄清楚,不過確實刷新頁面next.js會給我們在props里返回一個isServer:true,但是控制臺并沒有獲取數據。具體問題見下面截圖:
從截圖我們可以很清楚地看到,頁面數據通過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這個生命周期!!!
上面才是關鍵問題所在,不刷新頁面的情況下是正常的,刷新頁面沒有走這個生命周期,而我們很多數據都是需要預獲取的,所以說還挺坑的,事實上,很多人遇到這個問題,而且我在next官方給出的reudx-demo里面也發現這個問題,也就是說他們官方的demo刷新也會出現這個問題。
解決辦法
既然是踩坑,當然有解決辦法啦~而且還是兩種:
-
第一種:在組件生命周期里判斷isServer
剛剛問題描述過了,也就是正常加載和通過路由跳轉頁面,數據會正常渲染且會從瀏覽器的window.__NEXT_DATA__獲取來減少不必要的網絡請求~,而在頁面進行刷新的時候不會重新請求數據并且window.__NEXT_DATA__里也找不到我們想要的數據。不過通過控制臺信息我們可以發現問題所在以及解決辦法。那就是,第一次啟動系統的時候返回的isServer是false,而瀏覽器刷新頁面的時候isServer返回的是true,我們可以在組件里進行這個變量的判斷,如果是true,就重新進行一次數據抓取。
-
第二種:換一種方式預獲取數據
另一種方法就比較高級了,原理我依然不知道,但是就是好用,哈哈,這東西真是邪門,為什么這么說呢,其實本質沒改變什么,就是換了種寫法就可以。具體就是,上面的寫法我在getInitalProps里面寫了dispatch了一個獲取數據的action,從上一節或者代碼里你們可以看到,其實這個action就是fetch一個api獲取數據返回state。這就是redux一個獲取數據的基本過程,這種方法在刷新時行不通,而行得通的方法是:不通過dispatch action的方式獲取數據,而是直接在getIntialProps里面通過fetch api的方式獲取數據,這樣每次刷新頁面也都可以獲取到數據了。。。就是這么神奇,我也真不知道為啥。
就是很神奇有木有,說實話我是真不知道為啥,有大牛的話真心給我講講萬分感謝了~ 不過這兩種寫法我還是比較喜歡上面第一種的,因為覺得第一種在自己可控范圍內,因為以前寫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.jsimport 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文件夾里配置一下頁面和路由
- 第二步,package.json添加export命令
-
第三步,運行yarn export命令
運行完命令之后,根目錄下會出現一個out文件夾,真的是非常神奇,里面有頁面文件夾和必要的靜態資源。
這里還有一個高級的Next.js項目推送到github page的功能,依賴的也是這個export,不過時間問題我就沒寫,大家感興趣的去看看官方demo,應該可以解決的~
總結
寫到這里,Next.js踩坑入門系列就寫完了。非常感謝有很多小伙伴一直在看,還有一些可愛的小伙伴催更,水平有限,完全是踩坑集錦,如果能幫助到大家真的很開心。謝謝大家的閱讀。接下來準備用Next.js搭一個網站。完成后可能會再寫一篇Next.js的建站文章,其他的就不寫了,再寫就是其他內容啦~
本章節代碼地址
項目代碼地址,喜歡的給個Star,謝謝米娜桑
總結
以上是生活随笔為你收集整理的Next.js踩坑入门系列(七) —— 其他相关知识的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Git的学习与使用(五)——Git 创建
- 下一篇: fastjson与net.sf.json