摘要:今天這篇文章顯然不是討論這兩個(gè)詞語的,我們要嘗試使用最新版,構(gòu)件一個(gè)簡單的服務(wù)端渲染應(yīng)用。這樣取代了完全由客戶端渲染前后端分離方式模式。在場景下,我們可以使用自身的完成服務(wù)端初次渲染。這也是它在推出短短時(shí)間以來,便迅速走紅的原因之一。
參加或留意了最近舉行的JSConf CN 2017的同學(xué),想必對(duì) Next.js 不再陌生, Next.js 的作者之一到場進(jìn)行了精彩的演講。其實(shí)在更早些時(shí)候,由 Facebook 舉辦的 React Conf 2017,他就到場并有近40分鐘的分享。但兩次分享帶來的 demo 都是 hacker news。我觀察 Next.js 時(shí)間較長,看著它從1.x 版本一直到了今天的 3.x,終于決定寫一篇入門級(jí)的新手指導(dǎo)文章。而這篇文章試圖通過一個(gè)全新的例子,來讓大家了解 Next.js 到底是如何與 React 配合,達(dá)到服務(wù)端渲染的。
“React universal” 是社區(qū)上形容基于 React 構(gòu)建 web 應(yīng)用,并采用“服務(wù)端渲染”方式的一個(gè)詞語。也許很多人對(duì) “isomorphic” 這個(gè)單詞更加熟悉,其實(shí)這兩個(gè)詞語想要表達(dá)的概念類似。今天這篇文章顯然不是討論這兩個(gè)詞語的,我們要嘗試使用最新版 Next.js,構(gòu)件一個(gè)簡單的服務(wù)端渲染 React 應(yīng)用。最終項(xiàng)目地址可以點(diǎn)擊這里查看。
為何要開發(fā) Universal 應(yīng)用?React app 實(shí)現(xiàn)了虛擬 DOM,來實(shí)現(xiàn)對(duì)真實(shí) DOM 的抽象。這樣的設(shè)計(jì)迅速引領(lǐng)了前端開發(fā)浪潮。但是 “Every great thing comes with a price”,虛擬 DOM 同樣帶來了一些弊端,比如在前后端分離的開發(fā)模式下,SEO就成了問題;同樣首屏加載時(shí)間變長,各種 loading 消磨人的耐心。就像下面截圖所展現(xiàn)的那樣:
使用 Next.js 實(shí)現(xiàn) UniversalUniversal 應(yīng)用架構(gòu)可以簡單粗暴先而片面的理解成應(yīng)用將在客戶端和服務(wù)端共同完成渲染。這樣取代了完全由客戶端渲染(前后端分離方式)模式。在 React 場景下,我們可以使用 React 自身的 renderToString 完成服務(wù)端初次渲染。但是如果我們每次手動(dòng)來完成這些過程,手動(dòng)實(shí)現(xiàn)服務(wù)端繁瑣配置,難免令人頭大心煩。
Next.js 的出現(xiàn),就是為你解決這種惱人的問題。我們先來認(rèn)識(shí)一下它的幾個(gè)原則和思想:
不需要除 Next 之外,多余的配置和安裝(比如 webpack,babel);
使用 Glamor 處理樣式;
自動(dòng)編譯和打包;
熱更新;
方便的靜態(tài)資源管理;
成熟靈活的路由配置,包括路由級(jí)別 prefetching;
Demo:英超聯(lián)賽積分榜其實(shí)關(guān)于更多的 Next.js 設(shè)計(jì)理念我不想再贅述了,讀者都可以在其官網(wǎng)找到豐富的內(nèi)容。下面,我將使用 Football Data API 來簡單開發(fā)一個(gè)基于 Next.js 的應(yīng)用,這個(gè)應(yīng)用將展現(xiàn)英超聯(lián)賽的實(shí)時(shí)積分榜。同時(shí)包含了簡單的路由開發(fā)和頁面跳轉(zhuǎn)。
小試牛刀相信所有的開發(fā)者都厭惡超長時(shí)間的安裝和各種依賴、插件配置。不要擔(dān)心,Next.js 作為一個(gè)獨(dú)立的 npm package 最大限度的替你完成了很多耗時(shí)且無趣的工作。我們首先需要進(jìn)行安裝:
# Start a new project npm init # Install Next.js npm install next --save
安裝結(jié)束后,我們就可以開啟腳本:
"scripts": { "start": "next" },
Next 安裝的同時(shí),也會(huì)安裝 React,所以無需自己費(fèi)心。接下來所需要做的很簡單,就是在根目錄下創(chuàng)建一個(gè) pages 文件夾,并在其下新建一個(gè) index.js 文件:
// ./pages/index.js // Import React import React from "react" // Export an anonymous arrow function // which returns the template export default () => (This is just so easy!
)
好了,現(xiàn)在就可以直接看到結(jié)果:
# Start your app npm start
驗(yàn)證一下它來自服務(wù)端渲染:
就是這么簡單,清新。如果我們自己手段實(shí)現(xiàn)這一切的話,除了 NodeJS 的種種繁瑣不說,webpack 配置,node_modules 依賴,babel插件等等就夠折騰半天的了。
添加 Page Head在 ./pages/index.js 文件內(nèi),我們可以添加頁面 head 標(biāo)簽、meta 信息、樣式資源等等:
// ./pages/index.js import React from "react" // Import the Head Component import Head from "next/head" export default () => ()League Table This is just so easy!
這個(gè) head 當(dāng)然不是指真實(shí)的 DOM,千萬別忘了 React 虛擬 DOM 的概念。其實(shí)這是 Next 提供的 Head 組件,不過最終一定還是被渲染成為真實(shí)的 head 標(biāo)簽。
發(fā)送 Ajax 請(qǐng)求Next 還提供了 getInitialProps 方法,這個(gè)方法支持異步選項(xiàng),并且是服務(wù)端/客戶端同構(gòu)的。我們可以使用 async/await 方式,處理異步請(qǐng)求。請(qǐng)看下面的示例:
import React from "react" import Head from "next/head" import axios from "axios"; export default class extends React.Component { // Async operation with getInitialProps static async getInitialProps () { // res is assigned the response once the axios // async get is completed const res = await axios.get("http://api.football-data.org/v1/competitions/426/leagueTable"); // Return properties return {data: res.data} } }
我們使用了 axios 類庫來發(fā)送 HTTP 請(qǐng)求。網(wǎng)絡(luò)請(qǐng)求是異步的,因此我們需要在未來某個(gè)合適的時(shí)候(請(qǐng)求結(jié)果返回時(shí))接收數(shù)據(jù)。這里使用先進(jìn)的 async/await,以同步的方式處理,從而避免了回調(diào)嵌套和 promises 鏈。
我們將異步獲得的數(shù)據(jù)返回,它將自動(dòng)掛載在 props 上(注意 getInitialProps 方法名,顧名思義),render 方法里便可以通過 this.props.data 獲取:
import React from "react" import Head from "next/head" import axios from "axios"; export default class extends React.Component { static async getInitialProps () { const res = await axios.get("http://api.football-data.org/v1/competitions/426/leagueTable"); return {data: res.data} } render () { return (......); } }Barclays Premier League
...... {this.props.data.standing.map((standing, i) => { const oddOrNot = i % 2 == 1 ? "pure-table-odd" : ""; return (); })} {standing.position} {standing.points} {standing.goals} {standing.wins} {standing.draws} {standing.losses}
這樣,再訪問我們的頁面,就有了:
路由和頁面跳轉(zhuǎn)也許你已經(jīng)有所感知:我們已經(jīng)有了最基本的一個(gè)路由。Next 不需要任何額外的路由配置信息,你只需要在 pages 文件夾下新建文件,每一個(gè)文件都將是一個(gè)獨(dú)立的頁面。
讓我們來新建一個(gè) team 頁面吧!新建 ./pages/details.js 文件:
// ./pages/details.js import React from "react" export default () => (Coming soon. . .!
)
我們使用 Next 已經(jīng)準(zhǔn)備好的組件 來進(jìn)行頁面跳轉(zhuǎn):
// ./pages/details.js import React from "react" // Import Link from next import Link from "next/link" export default () => ()Coming soon. . .!
Go Home
這個(gè)頁面不能總是 “Coming soon. . .!” 的信息,我們來進(jìn)行完善以展示更多內(nèi)容,通過頁面 URL 的 query id 變量,我們來請(qǐng)求并展現(xiàn)當(dāng)前相應(yīng)隊(duì)伍的信息:
import React from "react" import Head from "next/head" import Link from "next/link" import axios from "axios"; export default class extends React.Component { static async getInitialProps ({query}) { // Get id from query const id = query.id; if(!process.browser) { // Still on the server so make a request const res = await axios.get("http://api.football-data.org/v1/competitions/426/leagueTable") return { data: res.data, // Filter and return data based on query standing: res.data.standing.filter(s => s.position == id) } } else { // Not on the server just navigating so use // the cache const bplData = JSON.parse(sessionStorage.getItem("bpl")); // Filter and return data based on query return {standing: bplData.standing.filter(s => s.position == id)} } } componentDidMount () { // Cache data in localStorage if // not already cached if(!sessionStorage.getItem("bpl")) sessionStorage.setItem("bpl", JSON.stringify(this.props.data)) } // . . . render method truncated }
這個(gè)頁面根據(jù) query 變量,動(dòng)態(tài)展現(xiàn)出球隊(duì)信息。具體來看,getInitialProps 方法獲取 URL query id,根據(jù) id 篩選出(filter 方法)展示信息。有意思的是,因?yàn)橐恢鼻蜿?duì)的信息比較穩(wěn)定,所以在客戶端使用了 sessionStorage 進(jìn)行存儲(chǔ)。
完整的 render 方法:
// . . . truncated export default class extends React.Component { // . . . truncated render() { const detailStyle = { ul: { marginTop: "100px" } } return () } }League Table {this.props.standing[0].teamName}
Points: {this.props.standing[0].points}
Home
- Goals: {this.props.standing[0].goals}
- Wins: {this.props.standing[0].wins}
- Losses: {this.props.standing[0].losses}
- Draws: {this.props.standing[0].draws}
- Goals Against: {this.props.standing[0].goalsAgainst}
- Goal Difference: {this.props.standing[0].goalDifference}
- Played: {this.props.standing[0].playedGames}
注意下面截圖中,同一頁面不同 query 值,分別展示了冠軍?切爾西和曼聯(lián)的信息。
別忘了我們的主頁(排行榜頁面)index.js 中,也要使用相應(yīng)的 sessionStorage 邏輯。同時(shí),在 render 方法里加入一條鏈接到詳情頁的 :
錯(cuò)誤頁面More...
在 Next 中,我們同樣可以通過 error.js 文件定義錯(cuò)誤頁面。在 ./pages 下新建 error.js:
// ./pages/_error.js import React from "react" export default class Error extends React.Component { static getInitialProps ({ res, xhr }) { const statusCode = res ? res.statusCode : (xhr ? xhr.status : null) return { statusCode } } render () { return ({ this.props.statusCode ? `An error ${this.props.statusCode} occurred on server` : "An error occurred on client" }
) } }
當(dāng)傳統(tǒng)情況下頁面404時(shí),得到:
在我們?cè)O(shè)置 _ error.js 之后,便有:
總結(jié)這篇文章實(shí)現(xiàn)了一個(gè)簡易 demo,只是介紹了最基本的 Next.JS 搭建 React 同構(gòu)應(yīng)用的基本步驟。
想想你是否厭煩了 webpack 惱人的配置?是否對(duì)于 Babel 各種插件云里霧里?
使用 Next.js,簡單、清新而又設(shè)計(jì)良好。這也是它在推出短短時(shí)間以來,便迅速走紅的原因之一。
除此之外,Next 還有非常多的功能,非常多的先進(jìn)理念可以應(yīng)用。
比如 搭配 prefetch,預(yù)先請(qǐng)求資源;
再如動(dòng)態(tài)加載組件(Next.js 支持 TC39 dynamic import proposal),從而減少首次 bundle size;
雖然它替我們封裝好了 Webpack、Babel 等工具,但是我們又能 customizing,根據(jù)需要自定義。
最后,對(duì)于這些本文章沒有演示到的功能是否有些手癢?感興趣的讀者可以關(guān)注本文 demo 的Github項(xiàng)目地址,自己手動(dòng)嘗試起來吧~
本文意譯了Chris Nwamba的:React Universal with Next.js: Server-side React 一文,并對(duì)原文進(jìn)行了升級(jí),兼容了最新的 Next 設(shè)計(jì)。
我的其他關(guān)于 React 文章:
做出Uber移動(dòng)網(wǎng)頁版還不夠 極致性能打造才見真章
解析Twitter前端架構(gòu) 學(xué)習(xí)復(fù)雜場景數(shù)據(jù)設(shè)計(jì)
React Conf 2017 干貨總結(jié)1: React + ES next = ?
React+Redux打造“NEWS EARLY”單頁應(yīng)用 一個(gè)項(xiàng)目理解最前沿技術(shù)棧真諦
一個(gè)react+redux工程實(shí)例
Happy Coding!
PS:
作者Github倉庫 和 知乎問答鏈接
歡迎各種形式交流。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/51023.html
摘要:今天這篇文章顯然不是討論這兩個(gè)詞語的,我們要嘗試使用最新版,構(gòu)件一個(gè)簡單的服務(wù)端渲染應(yīng)用。這樣取代了完全由客戶端渲染前后端分離方式模式。在場景下,我們可以使用自身的完成服務(wù)端初次渲染。這也是它在推出短短時(shí)間以來,便迅速走紅的原因之一。 參加或留意了最近舉行的JSConf CN 2017的同學(xué),想必對(duì) Next.js 不再陌生, Next.js 的作者之一到場進(jìn)行了精彩的演講。其實(shí)在更早...
摘要:更詳細(xì)的內(nèi)容下一章開篇深入聊聊前后分離講述關(guān)于我目前在寫從零構(gòu)建前后分離項(xiàng)目系列,修正和補(bǔ)充以此為準(zhǔn)不斷更新的項(xiàng)目實(shí)踐地址彩蛋提前預(yù)覽下一章傳送門 開篇 : 縱觀WEB歷史演變 在校學(xué)習(xí)和幾年工作工作中不知不覺經(jīng)歷了一半的 WEB 歷史演變、對(duì)近幾年的發(fā)展比較了解,結(jié)合經(jīng)驗(yàn)聊聊 WEB 發(fā)展歷史。 演變不易,但也是必然,因?yàn)闉槿耸冀K要進(jìn)步。 WEB 的發(fā)展史 一、開山鼻祖 - 石器時(shí)代...
摘要:更詳細(xì)的內(nèi)容下一章開篇深入聊聊前后分離講述關(guān)于我目前在寫從零構(gòu)建前后分離項(xiàng)目系列,修正和補(bǔ)充以此為準(zhǔn)不斷更新的項(xiàng)目實(shí)踐地址彩蛋提前預(yù)覽下一章傳送門 開篇 : 縱觀WEB歷史演變 在校學(xué)習(xí)和幾年工作工作中不知不覺經(jīng)歷了一半的 WEB 歷史演變、對(duì)近幾年的發(fā)展比較了解,結(jié)合經(jīng)驗(yàn)聊聊 WEB 發(fā)展歷史。 演變不易,但也是必然,因?yàn)闉槿耸冀K要進(jìn)步。 WEB 的發(fā)展史 一、開山鼻祖 - 石器時(shí)代...
摘要:前端日?qǐng)?bào)精選開發(fā)常見問題集錦前端碼農(nóng)的自我修養(yǎng)虛擬內(nèi)部是如何工作的譯知乎專欄并不慢,只是你使用姿勢(shì)不對(duì)一份優(yōu)化指南掘金老司機(jī)帶你秒懂內(nèi)存管理第一部中文免費(fèi)公開課前端面試的大關(guān)鍵點(diǎn),你到了嗎知乎專欄高效開發(fā)與設(shè)計(jì)姐的圖片二三 2017-07-19 前端日?qǐng)?bào) 精選 VueJS 開發(fā)常見問題集錦 - 前端碼農(nóng)的自我修養(yǎng) - SegmentFault虛擬 DOM 內(nèi)部是如何工作的?[譯]Hig...
摘要:寫在最前原文首發(fā)于作者的知乎專欄中間件思想遇見的靈感附,感興趣的同學(xué)可以知乎關(guān)注,進(jìn)行交流。其中,最重要的一個(gè)便是對(duì)多線程的支持。在中提出了工作線程的概念,并且規(guī)范出的三大主要特征能夠長時(shí)間運(yùn)行響應(yīng)理想的啟動(dòng)性能以及理想的內(nèi)存消耗。 寫在最前 原文首發(fā)于作者的知乎專欄:React Redux 中間件思想遇見 Web Worker 的靈感(附demo),感興趣的同學(xué)可以知乎關(guān)注,進(jìn)行交流...
閱讀 5156·2023-04-25 19:30
閱讀 2180·2023-04-25 15:09
閱讀 2631·2021-11-16 11:45
閱讀 2189·2021-11-15 18:07
閱讀 1470·2021-11-11 17:22
閱讀 2128·2021-11-04 16:06
閱讀 3586·2021-10-20 13:47
閱讀 3048·2021-09-22 16:03