React 服務端渲染

點關注不迷路雏蛮,建議收藏慢慢讀……

在開始之前我們需要先來搞清楚一個問題:什么是服務端渲染 痛黎?

在以往的概念里,渲染的工作更多的是放在客戶端進行的薛匪,那么為什么現(xiàn)在我們要讓服務端來做這個工作月匣?

服務端渲染和客戶端渲染有什么不同之處嗎钻洒?

其實服務端渲染的工具有很多,看著手冊很快就能上手锄开,并沒有什么難度航唆,關鍵在于,我們什么場景下需要使用服務端渲染院刁,什么樣的渲染方案更適合我們的項目糯钙;知其然,知其所以然退腥,我們需要先搞清楚服務端渲染的基本概念和原理任岸,服務端渲染為什么會出現(xiàn),到底解決了我們的什么問題狡刘,掌握整體的渲染邏輯和思路享潜,我們才能在學習工具使用時,輕松自在嗅蔬,而即便以后工具有了變化和更新剑按,我們也能得心應手,不會再說 “學不動” 了澜术;

這個邏輯就是所謂的道艺蝴、法、術鸟废、器的概念猜敢;不要僅僅停留在工具的使用和一些工具的奇技淫巧中,更多的要向法、道的層面成長缩擂;

什么是 SSR 鼠冕?

現(xiàn)代化的前端項目,大部分都是單頁應用程序胯盯,也就是我們說的 SPA 懈费,整個應用只有一個頁面,通過組件的方式博脑,展示不同的頁面內(nèi)容憎乙,所有的數(shù)據(jù)通過請求服務器獲取后,在進行客戶端的拼裝和展示趋厉;這就是目前前端框架的默認渲染邏輯,我們稱為:客戶端渲染方案( Client Side Render 簡稱: CSR )胶坠;

加載渲染過程如下: HTML/CSS 代碼 --> 加載 JavaScript 代碼 --> 執(zhí)行 JavaScript 代碼 --> 渲染頁面數(shù)據(jù)

image-20210126143051858.png

SPA 應用的客戶端渲染方式君账,最大的問題有兩個方面:

1:白屏時間過長,用戶體驗不好沈善;

2:HTML 中無內(nèi)容乡数,SEO 不友好;

這個問題的原因在于闻牡,首次加載時净赴,需要先下載整個 SPA 腳本程序,瀏覽器執(zhí)行代碼邏輯后罩润,才能去獲取頁面真正要展示的數(shù)據(jù)玖翅,而 SPA 腳本的下載需要較長的等待和執(zhí)行時間,同時割以,下載到瀏覽器的 SPA 腳本是沒有頁面數(shù)據(jù)的金度, 瀏覽器實際并沒有太多的渲染工作,因此用戶看到的是沒有任何內(nèi)容的頁面严沥,不僅如此猜极,因為頁面中沒有內(nèi)容,搜索引擎的爬蟲爬到的也是空白的內(nèi)容消玄,也就不利于 SEO 關鍵字的獲雀;

相較于傳統(tǒng)的站點翩瓜,瀏覽器獲取到的頁面都是經(jīng)過服務器處理的有內(nèi)容的靜態(tài)頁面受扳,有過后端編程經(jīng)驗的可能會比較熟悉一些,頁面結構和內(nèi)容兔跌,都是通過服務器處理后辞色,返回給客戶端;

全宇宙首發(fā)動圖,全流程展現(xiàn)

image-20210204131715564.gif

兩相比較我們會發(fā)現(xiàn)相满,傳統(tǒng)站點的頁面數(shù)據(jù)合成在后臺服務器层亿,而 SPA 應用的頁面數(shù)據(jù)合成在瀏覽器,但是無論那種立美,最終的渲染展示匿又,還是交給瀏覽器完成的,所以建蹄,不要誤會碌更,我們這里所說的 服務端渲染 和 客戶端渲染,指的是頁面結構和數(shù)據(jù)合成的工作洞慎,不是瀏覽器展示的工作痛单;

那么能不能借助傳統(tǒng)網(wǎng)站的思路來解決 SPA 的問題又能夠保留SPA的優(yōu)勢呢?不管是白屏時間長還是 SEO 不友好劲腿,實際都是首屏的頁面結構先回到瀏覽器旭绒,然后再獲取數(shù)據(jù)后合成導致的問題,那么焦人,首屏的頁面結構和數(shù)據(jù)挥吵,只要像傳統(tǒng)站點一樣,先在服務端合成后再返回花椭,同時將 SPA 腳本的加載依然放到首屏中忽匈,此時返回的頁面就是結構和數(shù)據(jù)都有的完整內(nèi)容了,這樣瀏覽器在展示首頁數(shù)據(jù)的同時也能加載 SPA 腳本矿辽,搜索引擎的爬蟲同樣也能獲取到對應的數(shù)據(jù)丹允,解決 SEO 的問題;為了更好的理解這個邏輯袋倔,我畫了一個流程圖:

image-20210126211924169.png

沒錯嫌松,這就是我們所說的 服務端渲染的基本邏輯,服務端渲染也就是 SSR (Server Side Rendering) 奕污;

白屏時間過長的問題得以解決萎羔,因為首次加載時,服務器會先將渲染好的靜態(tài)頁面返回碳默,在靜態(tài)頁面中再次加載請求 SPA 腳本贾陷;

基本原理:首頁內(nèi)容及數(shù)據(jù),在用戶請求之前生成為靜態(tài)頁面嘱根,同時加入 SPA 的腳本代碼引入髓废,在瀏覽器渲染完成靜態(tài)頁面后,請求 SPA 腳本應用该抒,之后的頁面交互依然是客戶端渲染慌洪;

image-20210126143216537.png

明白了其中的原理,也就是到了道、法的境界冈爹,接下來涌攻,讓我們下凡進入術、器的應用層面感受一下频伤;

其中 Vue 框架和 React 框架都有對應的比較成熟的 SSR 解決方案恳谎,React對應的是 Next.js 框架,Vue 對應的就是 Nuxt.js憋肖,當然因痛,如果你對這些都不感興趣,也可以自己實現(xiàn)一個 SSR 的服務端應用岸更,我自己之前也寫過一個鸵膏,如果你感興趣,想看看我實現(xiàn)的代碼怎炊,可以留言給我谭企,回頭做成教程發(fā)出來;

image-20210126144831765.png

我們以 React 對應的 Next.js 為例结胀,來具體感受服務端渲染赞咙;

Next.js 框架

中文官網(wǎng)首頁责循,已經(jīng)介紹的非常清楚了:https://www.nextjs.cn/

image-20210205144005140.png

基本應用

安裝部署

項目安裝命令: npx create-next-appnpm init next-app my-next-project

運行:npm run dev 命令糟港,啟動項目

同時,也可以查看 ./package.json 文件中的腳本配置

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start"
}

這些腳本涉及開發(fā)應用程序的不同階段:

  • dev - 運行 next dev院仿,以開發(fā)模式啟動 Next.js

  • build - 運行 next build秸抚,以構建用于生產(chǎn)環(huán)境的應用程序

  • start - 運行 next start,將啟動 Next.js 生產(chǎn)環(huán)境服務器

訪問 http://localhost:3000 即可查看我們的應用程序了歹垫。

image-20210201154252505.png

頁面路由

在 Next.js 中剥汤,頁面是被放置在 pages 文件夾中的 Reac 組件,這是框架定義好的排惨;

組件需要被默認導出吭敢;組件文件中不需要引入 React;頁面地址與文件地址是對應的關系暮芭;

頁面(page) 根據(jù)其文件名與路由關聯(lián)鹿驼。例如,pages/about.js 被映射到 /about辕宏。

在你的項目中的 pages 目錄創(chuàng)建一個 about.js 畜晰。

./pages/about.js 文件填充如下內(nèi)容:

// 類組件需要引入 react 繼承
import React from 'react'
class AboutPage extends React.Component {
  render(){
    return <h3>class Component -- About Page</h3>
  }
}
export default AboutPage
image-20210205144645393.png
// 函數(shù)組件不需要引入 React 
function AboutPage(){
  return <h3>Function Component -- About Page</h3>
}
export default AboutPage
image-20210205144814355.png

頁面跳轉

Link 組件默認使用 Javascript 進行頁面跳轉,即SPA形式的跳轉 如果瀏覽器中 Javascript 被禁用瑞筐,則使用鏈接跳轉 Link組件中不應添加除 href 屬性以外的屬性凄鼻,其余屬性添加到a標簽上 Link組件通過 預取(在生產(chǎn)中)功能自動優(yōu)化應用程序以獲得最佳性能

// 引入組件  
import Link from 'next/link'
// 函數(shù)組件不需要引入 React 
function AboutPage() {
 return (
 <div>
 {/* Link 中必須要寫 a 標簽,href 必須寫在 Link 中 */}
 <Link href="/list"><a>Go to list Page</a></Link>
 <h3>Function Component</h3>
 </div>
 )
}
export default AboutPage</pre>

靜態(tài)資源

應用程序根目錄中的 public 文件夾用于存放靜態(tài)資源; 通過以下形式進行訪問: public/img/1.png->/img/1.png public/css/style.css->/css/style.css

import React from 'react'
class ListPage extends React.Component {
 render(){
 return <div>
 <h3>ListPage</h3>
 {/* 引入圖片文件 */}
 <img src="/img/1.png" />
 </div>
 }
}
export default ListPage</pre>

CSS樣式

內(nèi)置 styled-jsx

https://github.com/vercel/styled-jsx#readme

在 Next.js中內(nèi)置了 styled-jsx 块蚌,它是一個CSS-in-JS庫闰非,允許在 React 組件中編寫 CSS,CSS 僅作用于當前組件內(nèi)部匈子;

import React from 'react'
class ListPage extends React.Component {
 render(){
 return <div>
 <h3 >ListPage</h3>
 <p className="pra"> 這是一個p標簽里面的內(nèi)容 </p>

 <style jsx>
 {`
 .pra{
 color: red;
 }
 `}
 </style>

 </div>
 }
}
export default ListPage</pre>
CSS模塊

通過使用CSS模塊功能河胎,允許將組件的 CSS 樣式編寫在單獨的 CSS 文件中 CSS 模塊約定樣式文件的名稱必須為: 組件文件名稱.module.css

創(chuàng)建 ./pages/list.module.css 文件并填寫如下內(nèi)容:

.prag{
 color:brown;
 font-size: 20px;
}
import React from 'react'
// 引入樣式文件
import ListStyle from './list.module.css'

class ListPage extends React.Component {
 render(){
 return <div>
 <h3 >ListPage</h3>
 {/* 使用樣式 */}
 <p className={ListStyle.prag}> 這是一個p標簽里面的內(nèi)容 </p>
 </div>
 }
}
export default ListPage</pre>
全局樣式文件

1:在 pages 文件夾中新建 _ app. js 文件(文件名固定) 2:在項目根目錄下創(chuàng)建 styles 文件夾,并在其中創(chuàng)建 global.css 3:在 _app.js 中通過 import 引入 global.css

global.css 中的樣式虎敦,將會全局起作用

/pages/_app.js 文件中的內(nèi)容如下:

import '../styles/globals.css'

// 固定代碼
function MyApp({ Component, pageProps }) {
 return <Component {...pageProps} />
}

export default MyApp

在新版框架中游岳,已經(jīng)幫我們做好相關內(nèi)容及文件和代碼

服務端渲染方法

getServerSideProps() 這個方法是服務端渲染的方法,適合頁面數(shù)據(jù)實時變化的應用其徙;getServerSideProps() 方法接收一個參數(shù)胚迫,是當前的 HTTP 客戶端請求對象;

import React from 'react'

// 類組件
class ListPage extends React.Component {
 render(){
 return <div>
 <h3 >ListPage</h3>
 <h4>服務器數(shù)據(jù):</h4>
 {/* 類組件的方式展示數(shù)據(jù)內(nèi)容 */}
 <p> {this.props.data[0].name} </p>
 </div>
 }
}

// // 函數(shù)組件
// function ListPage({data}){
//   return (
//     <div>
//       <h3 >ListPage</h3>
//       <h4>服務器數(shù)據(jù):</h4>
//       {/* 類組件的方式展示數(shù)據(jù)內(nèi)容 */}
//       <p> {data[1].name} </p>
//     </div>
//   )
// }

// Node 環(huán)境下執(zhí)行
// 文件讀寫唾那,數(shù)據(jù)庫鏈接访锻,網(wǎng)絡通信
// export async function getStaticProps(){
 export async function getServerSideProps(){
 const res = await fetch('http://localhost:80/');
 const backData = await res.json();
 const data = JSON.parse(backData);
 console.log(data);
 return {
 props:{data}
 }
 }

export default ListPage

項目構建與運行

項目構建:npm run build

啟動運行項目: npm run start

靜態(tài)站點生成

next.js 不僅提供服務端渲染的方式,同時還提供了靜態(tài)站點生成的解決方案闹获;

靜態(tài)站點生成也被稱為 SSG(Static Site Generators)期犬,就是將應用中用到的所以頁面,全部生成靜態(tài)文件的方案避诽;

next 官方建議在大多數(shù)情況下使用靜態(tài)站點生成龟虎,靜態(tài)站點生成方案,更適合 CDN沙庐、緩存鲤妥、內(nèi)容數(shù)據(jù)無變化的頁面,比如:宣傳頁拱雏、博客文章棉安、幫助文檔、新聞頁面铸抑、電商產(chǎn)品列表等眾多應用場景贡耽;

Next.js 中的 getStaticProps 、 getStaticPaths 就是靜態(tài)站點生成鹊汛;是在構建時生成 HTML 的方法蒲赂,以后的每個請求都共用構建時生成好的 HTML;

Next.js 建議大多數(shù)頁面使用靜態(tài)生成柒昏,因為頁面都是事先生成好的凳宙,一次構建,反復使用职祷,訪問速度快氏涩。 服務器端渲染訪問速度不如靜態(tài)生成快届囚,但是由于每次請求都會重新渲染,所以適用數(shù)據(jù)頻繁更新的頁面或頁面內(nèi)容隨請求變化而變化的頁面是尖;

在 next.js 中意系,靜態(tài)生成分為 無數(shù)據(jù)和有數(shù)據(jù)兩種情況; 如果組件不需要在其他地方獲取數(shù)據(jù)饺汹,默認直接進行靜態(tài)生成蛔添,如果組件需要在其他地方獲取數(shù)據(jù),在構建時 Next.js 會預先獲取組件需要的數(shù)據(jù)兜辞,然后再對組件進行靜態(tài)生成

我們來對比一下迎瞧,開發(fā)環(huán)境不會打包靜態(tài)文件,生產(chǎn)環(huán)境打包逸吵,默認生成靜態(tài)文件

image-20210202210535010.png

那么凶硅,有數(shù)據(jù)的情況應該怎么做呢?

有數(shù)據(jù)的靜態(tài)生成

getStaticProps() 這個方法官方翻譯為 靜態(tài)生成扫皱。是把組件提前編譯成 html 文件足绅,然后把整個 html 文件響應到客戶端,從而達到預渲染的目的韩脑。

getStaticProps() 方法是個異步方法氢妈,在 Node 環(huán)境下執(zhí)行(構建時執(zhí)行),因此可以進行文件讀寫段多,數(shù)據(jù)庫鏈接首量,網(wǎng)絡通信等一些列操作

對于這個方法的使用,先看 demo:

import React from 'react'
import Axios from "axios"

// 類組件
class ListPage extends React.Component {
 render(){
 return <div>
 <h3 >ListPage</h3>
 <h4>服務器數(shù)據(jù):</h4>
 {/* 類組件的方式展示數(shù)據(jù)內(nèi)容 */}
 <p> {this.props.data[0].name} </p>
 </div>
 }
}

// // 函數(shù)組件
// function ListPage({data}){
//   return (
//     <div>
//       <h3 >ListPage</h3>
//       <h4>服務器數(shù)據(jù):</h4>
//       {/* 類組件的方式展示數(shù)據(jù)內(nèi)容 */}
//       <p> {data[1].name} </p>
//     </div>
//   )
// }

// Node 環(huán)境下執(zhí)行
// 文件讀寫衩匣,數(shù)據(jù)庫鏈接蕾总,網(wǎng)絡通信
export async function getStaticProps(){
 const d3 = await Axios.get('http://localhost:80/');
 const data = JSON.parse(d3.data);
 console.log(data)
 // 返回的 Props 屬性的值會傳遞給組件
 return {
 props:{data}
 }
}

export default ListPage

getStaticProps 方法內(nèi)部必須返回一個對象粥航,這個對象中的 props 屬性講傳遞到組件中 琅捏。

getStaticPaths() 這個方法也是靜態(tài)生成。與 getStaticProps 共同使用递雀,會根據(jù)不同的請求參數(shù)生成不同的靜態(tài)頁面柄延,它的使用方式比較特殊,代碼文件要放在一個目錄中缀程,同時代碼文件的文件名搜吧,要使用 可選項 文件名的形式,如\pages\props\[id].js 的形式杨凑,在項目構建時滤奈,next 會根據(jù)不同的 ID 值,生成不同的對應的 靜態(tài)文件撩满,如下代碼

import React from 'react'
import Axios from "axios"

// 類組件
class ListPage extends React.Component {
 render() {
 return <div>
 <h3 >ListPage - Class</h3>
 <p>{this.props.backData.name}</p>
 </div>
 }
}

// 根據(jù)客戶端參數(shù)生成靜態(tài)頁面
export async function getStaticPaths() {
 return {
 // 匹配客戶端請求的 id 值 
 paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
 fallback: false
 }
}

// 自動調用當前方法蜒程,將客戶端參數(shù)傳入; { params } 接受到的客戶端參數(shù)
export async function getStaticProps({ params }) {
 const d3 = await Axios.get('http://localhost:80/');
 const data = JSON.parse(d3.data);
 console.log(params)
 // 循環(huán)服務器數(shù)據(jù)绅你,獲取
 for (let i = 0; i < data.length; i++) {
 // console.log(data[i].id)
 if (params.id == data[i].id) {
 // 返回對應數(shù)據(jù)
 const backData = data[i];
 return {
 props: { backData }
 }
 }
 }
}

export default ListPage

最終構建后,會生成不同的靜態(tài)頁面:

image-20210205151341092.png

靜態(tài)站點導出

"scripts": {
 "dev": "next dev",
 "build": "next build",
 "start": "next start",
 "export": "next build && next export"
 },

執(zhí)行命令 npm run export昭躺,進行構建和導出操作忌锯,生成 out 文件夾,獲取靜態(tài)站點資源领炫;

image-20210205151648214.png

除此之外偶垮,還有專門針對 React 的 SSG 靜態(tài)站點生成方案:Gatsby https://www.gatsbyjs.cn/ ,感興趣的可以自己去看看

當然帝洪,你 React 有的似舵,我 Vue 怎么可能沒有呢:Gridsome https://www.gridsome.cn/

斗爭吧,前端框架們……

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末葱峡,一起剝皮案震驚了整個濱河市啄枕,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌族沃,老刑警劉巖频祝,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異脆淹,居然都是意外死亡常空,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門盖溺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漓糙,“玉大人,你說我怎么就攤上這事烘嘱±デ荩” “怎么了?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵蝇庭,是天一觀的道長醉鳖。 經(jīng)常有香客問我,道長哮内,這世上最難降的妖魔是什么盗棵? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮北发,結果婚禮上纹因,老公的妹妹穿的比我還像新娘。我一直安慰自己琳拨,他們只是感情好瞭恰,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著狱庇,像睡著了一般惊畏。 火紅的嫁衣襯著肌膚如雪是牢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天陕截,我揣著相機與錄音驳棱,去河邊找鬼。 笑死农曲,一個胖子當著我的面吹牛社搅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播乳规,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼形葬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了暮的?” 一聲冷哼從身側響起笙以,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎冻辩,沒想到半個月后猖腕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡恨闪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年倘感,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片咙咽。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡老玛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出钧敞,到底是詐尸還是另有隱情蜡豹,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布溉苛,位于F島的核電站镜廉,受9級特大地震影響,放射性物質發(fā)生泄漏炊昆。R本人自食惡果不足惜桨吊,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一威根、第九天 我趴在偏房一處隱蔽的房頂上張望凤巨。 院中可真熱鬧,春花似錦洛搀、人聲如沸敢茁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽彰檬。三九已至伸刃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間逢倍,已是汗流浹背捧颅。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留较雕,地道東北人碉哑。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像亮蒋,于是被迫代替她去往敵國和親扣典。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容