React 服務(wù)端渲染

點(diǎn)關(guān)注不迷路,建議收藏慢慢讀……

在開始之前我們需要先來(lái)搞清楚一個(gè)問題:什么是服務(wù)端渲染 ?

在以往的概念里,渲染的工作更多的是放在客戶端進(jìn)行的,那么為什么現(xiàn)在我們要讓服務(wù)端來(lái)做這個(gè)工作?

服務(wù)端渲染和客戶端渲染有什么不同之處嗎?

其實(shí)服務(wù)端渲染的工具有很多,看著手冊(cè)很快就能上手,并沒有什么難度,關(guān)鍵在于,我們什么場(chǎng)景下需要使用服務(wù)端渲染,什么樣的渲染方案更適合我們的項(xiàng)目;知其然,知其所以然,我們需要先搞清楚服務(wù)端渲染的基本概念和原理,服務(wù)端渲染為什么會(huì)出現(xiàn),到底解決了我們的什么問題,掌握整體的渲染邏輯和思路,我們才能在學(xué)習(xí)工具使用時(shí),輕松自在,而即便以后工具有了變化和更新,我們也能得心應(yīng)手,不會(huì)再說 “學(xué)不動(dòng)” 了;

這個(gè)邏輯就是所謂的道、法、術(shù)、器的概念;不要僅僅停留在工具的使用和一些工具的奇技淫巧中,更多的要向法、道的層面成長(zhǎng);

什么是 SSR ?

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

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

image-20210126143051858.png

SPA 應(yīng)用的客戶端渲染方式,最大的問題有兩個(gè)方面:

1:白屏?xí)r間過長(zhǎng),用戶體驗(yàn)不好;

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

這個(gè)問題的原因在于,首次加載時(shí),需要先下載整個(gè) SPA 腳本程序,瀏覽器執(zhí)行代碼邏輯后,才能去獲取頁(yè)面真正要展示的數(shù)據(jù),而 SPA 腳本的下載需要較長(zhǎng)的等待和執(zhí)行時(shí)間,同時(shí),下載到瀏覽器的 SPA 腳本是沒有頁(yè)面數(shù)據(jù)的, 瀏覽器實(shí)際并沒有太多的渲染工作,因此用戶看到的是沒有任何內(nèi)容的頁(yè)面,不僅如此,因?yàn)轫?yè)面中沒有內(nèi)容,搜索引擎的爬蟲爬到的也是空白的內(nèi)容,也就不利于 SEO 關(guān)鍵字的獲??;

相較于傳統(tǒng)的站點(diǎn),瀏覽器獲取到的頁(yè)面都是經(jīng)過服務(wù)器處理的有內(nèi)容的靜態(tài)頁(yè)面,有過后端編程經(jīng)驗(yàn)的可能會(huì)比較熟悉一些,頁(yè)面結(jié)構(gòu)和內(nèi)容,都是通過服務(wù)器處理后,返回給客戶端;

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

image-20210204131715564.gif

兩相比較我們會(huì)發(fā)現(xiàn),傳統(tǒng)站點(diǎn)的頁(yè)面數(shù)據(jù)合成在后臺(tái)服務(wù)器,而 SPA 應(yīng)用的頁(yè)面數(shù)據(jù)合成在瀏覽器,但是無(wú)論那種,最終的渲染展示,還是交給瀏覽器完成的,所以,不要誤會(huì),我們這里所說的 服務(wù)端渲染 和 客戶端渲染,指的是頁(yè)面結(jié)構(gòu)和數(shù)據(jù)合成的工作,不是瀏覽器展示的工作;

那么能不能借助傳統(tǒng)網(wǎng)站的思路來(lái)解決 SPA 的問題又能夠保留SPA的優(yōu)勢(shì)呢?不管是白屏?xí)r間長(zhǎng)還是 SEO 不友好,實(shí)際都是首屏的頁(yè)面結(jié)構(gòu)先回到瀏覽器,然后再獲取數(shù)據(jù)后合成導(dǎo)致的問題,那么,首屏的頁(yè)面結(jié)構(gòu)和數(shù)據(jù),只要像傳統(tǒng)站點(diǎn)一樣,先在服務(wù)端合成后再返回,同時(shí)將 SPA 腳本的加載依然放到首屏中,此時(shí)返回的頁(yè)面就是結(jié)構(gòu)和數(shù)據(jù)都有的完整內(nèi)容了,這樣瀏覽器在展示首頁(yè)數(shù)據(jù)的同時(shí)也能加載 SPA 腳本,搜索引擎的爬蟲同樣也能獲取到對(duì)應(yīng)的數(shù)據(jù),解決 SEO 的問題;為了更好的理解這個(gè)邏輯,我畫了一個(gè)流程圖:

image-20210126211924169.png

沒錯(cuò),這就是我們所說的 服務(wù)端渲染的基本邏輯,服務(wù)端渲染也就是 SSR (Server Side Rendering) ;

白屏?xí)r間過長(zhǎng)的問題得以解決,因?yàn)槭状渭虞d時(shí),服務(wù)器會(huì)先將渲染好的靜態(tài)頁(yè)面返回,在靜態(tài)頁(yè)面中再次加載請(qǐng)求 SPA 腳本;

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

image-20210126143216537.png

明白了其中的原理,也就是到了道、法的境界,接下來(lái),讓我們下凡進(jìn)入術(shù)、器的應(yīng)用層面感受一下;

其中 Vue 框架和 React 框架都有對(duì)應(yīng)的比較成熟的 SSR 解決方案,React對(duì)應(yīng)的是 Next.js 框架,Vue 對(duì)應(yīng)的就是 Nuxt.js,當(dāng)然,如果你對(duì)這些都不感興趣,也可以自己實(shí)現(xiàn)一個(gè) SSR 的服務(wù)端應(yīng)用,我自己之前也寫過一個(gè),如果你感興趣,想看看我實(shí)現(xiàn)的代碼,可以留言給我,回頭做成教程發(fā)出來(lái);

image-20210126144831765.png

我們以 React 對(duì)應(yīng)的 Next.js 為例,來(lái)具體感受服務(wù)端渲染;

Next.js 框架

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

image-20210205144005140.png

基本應(yīng)用

安裝部署

項(xiàng)目安裝命令: npx create-next-appnpm init next-app my-next-project

運(yùn)行:npm run dev 命令,啟動(dòng)項(xiàng)目

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

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

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

  • dev - 運(yùn)行 next dev,以開發(fā)模式啟動(dòng) Next.js

  • build - 運(yùn)行 next build,以構(gòu)建用于生產(chǎn)環(huán)境的應(yīng)用程序

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

訪問 http://localhost:3000 即可查看我們的應(yīng)用程序了。

image-20210201154252505.png

頁(yè)面路由

在 Next.js 中,頁(yè)面是被放置在 pages 文件夾中的 Reac 組件,這是框架定義好的;

組件需要被默認(rèn)導(dǎo)出;組件文件中不需要引入 React;頁(yè)面地址與文件地址是對(duì)應(yīng)的關(guān)系;

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

在你的項(xiàng)目中的 pages 目錄創(chuàng)建一個(gè) 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

頁(yè)面跳轉(zhuǎn)

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

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

靜態(tài)資源

應(yīng)用程序根目錄中的 public 文件夾用于存放靜態(tài)資源; 通過以下形式進(jìn)行訪問: 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 ,它是一個(gè)CSS-in-JS庫(kù),允許在 React 組件中編寫 CSS,CSS 僅作用于當(dāng)前組件內(nèi)部;

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

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

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

通過使用CSS模塊功能,允許將組件的 CSS 樣式編寫在單獨(dú)的 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}> 這是一個(gè)p標(biāo)簽里面的內(nèi)容 </p>
 </div>
 }
}
export default ListPage</pre>
全局樣式文件

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

global.css 中的樣式,將會(huì)全局起作用

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

import '../styles/globals.css'

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

export default MyApp

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

服務(wù)端渲染方法

getServerSideProps() 這個(gè)方法是服務(wù)端渲染的方法,適合頁(yè)面數(shù)據(jù)實(shí)時(shí)變化的應(yīng)用;getServerSideProps() 方法接收一個(gè)參數(shù),是當(dāng)前的 HTTP 客戶端請(qǐng)求對(duì)象;

import React from 'react'

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

// Node 環(huán)境下執(zhí)行
// 文件讀寫,數(shù)據(jù)庫(kù)鏈接,網(wǎng)絡(luò)通信
// 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

項(xiàng)目構(gòu)建與運(yùn)行

項(xiàng)目構(gòu)建:npm run build

啟動(dòng)運(yùn)行項(xiàng)目: npm run start

靜態(tài)站點(diǎn)生成

next.js 不僅提供服務(wù)端渲染的方式,同時(shí)還提供了靜態(tài)站點(diǎn)生成的解決方案;

靜態(tài)站點(diǎn)生成也被稱為 SSG(Static Site Generators),就是將應(yīng)用中用到的所以頁(yè)面,全部生成靜態(tài)文件的方案;

next 官方建議在大多數(shù)情況下使用靜態(tài)站點(diǎn)生成,靜態(tài)站點(diǎn)生成方案,更適合 CDN、緩存、內(nèi)容數(shù)據(jù)無(wú)變化的頁(yè)面,比如:宣傳頁(yè)、博客文章、幫助文檔、新聞頁(yè)面、電商產(chǎn)品列表等眾多應(yīng)用場(chǎng)景;

Next.js 中的 getStaticProps 、 getStaticPaths 就是靜態(tài)站點(diǎn)生成;是在構(gòu)建時(shí)生成 HTML 的方法,以后的每個(gè)請(qǐng)求都共用構(gòu)建時(shí)生成好的 HTML;

Next.js 建議大多數(shù)頁(yè)面使用靜態(tài)生成,因?yàn)轫?yè)面都是事先生成好的,一次構(gòu)建,反復(fù)使用,訪問速度快。 服務(wù)器端渲染訪問速度不如靜態(tài)生成快,但是由于每次請(qǐng)求都會(huì)重新渲染,所以適用數(shù)據(jù)頻繁更新的頁(yè)面或頁(yè)面內(nèi)容隨請(qǐng)求變化而變化的頁(yè)面;

在 next.js 中,靜態(tài)生成分為 無(wú)數(shù)據(jù)和有數(shù)據(jù)兩種情況; 如果組件不需要在其他地方獲取數(shù)據(jù),默認(rèn)直接進(jìn)行靜態(tài)生成,如果組件需要在其他地方獲取數(shù)據(jù),在構(gòu)建時(shí) Next.js 會(huì)預(yù)先獲取組件需要的數(shù)據(jù),然后再對(duì)組件進(jìn)行靜態(tài)生成

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

image-20210202210535010.png

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

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

getStaticProps() 這個(gè)方法官方翻譯為 靜態(tài)生成。是把組件提前編譯成 html 文件,然后把整個(gè) html 文件響應(yīng)到客戶端,從而達(dá)到預(yù)渲染的目的。

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

對(duì)于這個(gè)方法的使用,先看 demo:

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

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

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

export default ListPage

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

getStaticPaths() 這個(gè)方法也是靜態(tài)生成。與 getStaticProps 共同使用,會(huì)根據(jù)不同的請(qǐng)求參數(shù)生成不同的靜態(tài)頁(yè)面,它的使用方式比較特殊,代碼文件要放在一個(gè)目錄中,同時(shí)代碼文件的文件名,要使用 可選項(xiàng) 文件名的形式,如\pages\props\[id].js 的形式,在項(xiàng)目構(gòu)建時(shí),next 會(huì)根據(jù)不同的 ID 值,生成不同的對(duì)應(yīng)的 靜態(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)頁(yè)面
export async function getStaticPaths() {
 return {
 // 匹配客戶端請(qǐng)求的 id 值 
 paths: [{ params: { id: "1" } }, { params: { id: "2" } }],
 fallback: false
 }
}

// 自動(dòng)調(diào)用當(dāng)前方法,將客戶端參數(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)服務(wù)器數(shù)據(jù),獲取
 for (let i = 0; i < data.length; i++) {
 // console.log(data[i].id)
 if (params.id == data[i].id) {
 // 返回對(duì)應(yīng)數(shù)據(jù)
 const backData = data[i];
 return {
 props: { backData }
 }
 }
 }
}

export default ListPage

最終構(gòu)建后,會(huì)生成不同的靜態(tài)頁(yè)面:

image-20210205151341092.png

靜態(tài)站點(diǎn)導(dǎo)出

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

執(zhí)行命令 npm run export,進(jìn)行構(gòu)建和導(dǎo)出操作,生成 out 文件夾,獲取靜態(tài)站點(diǎn)資源;

image-20210205151648214.png

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

當(dāng)然,你 React 有的,我 Vue 怎么可能沒有呢:Gridsome https://www.gridsome.cn/

斗爭(zhēng)吧,前端框架們……

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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