首頁 web前端 js教程 React為服務端進行渲染改造

React為服務端進行渲染改造

Jun 15, 2018 pm 02:57 PM
react 服務端渲染

這次帶給大家React對服務端進行渲染改造,React對服務端進行渲染改造的注意事項有哪些,以下就是實戰案例,一起來看一下。

因為對網頁SEO的需要,要把之前的React專案改造為服務端渲染,經過一番調查和研究,查閱了大量網路資料。成功踩坑。

選型思路:實現服務端渲染,想用React最新的版本,並且不對現有的寫法做大的改動,如果一開始就打算服務端渲染,建議直接用NEXT框架來寫

專案位址:https://github.com/wlx200510/react_koa_ssr

鷹架選型:webpack3.11.0 react Router4 Redux koa2 React16 Node8.x

主要心得:對React的相關知識較為熟悉,成功拓展自己的技術領域,對服務端技術在實際專案上有所累積

#注意點:使用框架前一定確認目前webpack版本為3.x Node為8.x以上,讀者最好用React在3個月以上,並有實際React專案經驗

專案目錄介紹

├── assets
│ └── index.css //放置一些全局的资源文件 可以是js 图片等
├── config
│ ├── webpack.config.dev.js 开发环境webpack打包设置
│ └── webpack.config.prod.js 生产环境webpack打包设置
├── package.json
├── README.md
├── server server端渲染文件,如果对不是很了解,建议参考[koa教程](http://wlxadyl.cn/2018/02/11/koa-learn/)
│ ├── app.js
│ ├── clientRouter.js // 在此文件中包含了把服务端路由匹配到react路由的逻辑
│ ├── ignore.js
│ └── index.js
└── src
 ├── app 此文件夹下主要用于放置浏览器和服务端通用逻辑
 │ ├── configureStore.js //redux-thunk设置
 │ ├── createApp.js  //根据渲染环境不同来设置不同的router模式
 │ ├── index.js
 │ └── router
 │  ├── index.js
 │  └── routes.js  //路由配置文件! 重要
 ├── assets
 │ ├── css    放置一些公共的样式文件
 │ │ ├── _base.scss  //很多项目都会用到的初始化css
 │ │ ├── index.scss
 │ │ └── my.scss
 │ └── img
 ├── components    放置一些公共的组件
 │ ├── FloatDownloadBtn 公共组件样例写法
 │ │ ├── FloatDownloadBtn.js
 │ │ ├── FloatDownloadBtn.scss
 │ │ └── index.js
 │ ├── Loading.js
 │ └── Model.js   函数式组件的写法
 │
 ├── favicon.ico
 ├── index.ejs    //渲染的模板 如果项目需要,可以放一些公共文件进去
 ├── index.js    //包括热更新的逻辑
 ├── pages     页面组件文件夹
 │ ├── home
 │ │ ├── components  // 用于放置页面组件,主要逻辑
 │ │ │ └── homePage.js
 │ │ ├── containers  // 使用connect来封装出高阶组件 注入全局state数据
 │ │ │ └── homeContainer.js
 │ │ ├── index.js  // 页面路由配置文件 注意thunk属性
 │ │ └── reducer
 │ │  └── index.js // 页面的reducer 这里暴露出来给store统一处理 注意写法
 │ └── user
 │  ├── components
 │  │ └── userPage.js
 │  ├── containers
 │  │ └── userContainer.js
 │  └── index.js
 └── store
  ├── actions   // 各action存放地
  │ ├── home.js
  │ └── thunk.js
  ├── constants.js  // 各action名称汇集处 防止重名
  └── reducers
   └── index.js  // 引用各页面的所有reducer 在此处统一combine处理
登入後複製

專案的建置想法

  1. 本機開發使用webpack-dev-server,實作熱更新,基本流程跟之前react開發類似,仍是瀏覽器端渲染,因此在寫程式碼時要考慮到一套邏輯,兩種渲染環境的問題。

  2. 目前端頁面渲染完成後,其Router跳轉將不會對服務端進行請求,從而減輕服務端壓力,從而頁面的進入方式也是兩種,還要考慮兩種渲染環境下路由同構的問題。

  3. 生產環境要使用koa做後端伺服器,實現按需加載,在服務端獲取數據,並渲染整個HTML,利用React16最新的能力來合併整個狀態樹,實作服務端渲染。

本地開發介紹

查看本地開發主要涉及的文件是src目錄下的index.js文件,判斷當前的運行環境,只有在開發環境下才會使用module.hot的API,實現當reducer發生變化時的頁面渲染更新通知,注意其中的hydrate方法,這是v16版本的一個專門為服務端渲染新增的API方法,它在render方法的基礎上實現了對服務端渲染內容的最大可能重複使用,實現了靜態DOM到動態NODES的過程。實質是取代了v15版本下判斷checksum標記的過程,使得重用的過程更有效率優雅。

const renderApp=()=>{
 let application=createApp({store,history});
 hydrate(application,document.getElementById('root'));
}
window.main = () => {
 Loadable.preloadReady().then(() => {
 renderApp()
 });
};
if(process.env.NODE_ENV==='development'){
 if(module.hot){
 module.hot.accept('./store/reducers/index.js',()=>{
  let newReducer=require('./store/reducers/index.js');
  store.replaceReducer(newReducer)
 })
 module.hot.accept('./app/index.js',()=>{
  let {createApp}=require('./app/index.js');
  let newReducer=require('./store/reducers/index.js');
  store.replaceReducer(newReducer)
  let application=createApp({store,history});
  hydrate(application,document.getElementById('root'));
 })
 }
}
登入後複製

注意window.main這個函數的定義,結合index.ejs可以知道這個函數是所有腳本加載完成後才觸發,裡面用的是react-loadable的寫法,用於頁面的懶加載,關於頁面分別打包的寫法要結合路由設定來講解,這裡有個大致印象即可。要注意的是app這個檔案下暴露出的三個方法是在瀏覽器端和伺服器端通用的,接下來主要是說這部分的想法。

路由處理

接下來看以下src/app目錄下的文件,index.js暴露了三個方法,這裡面涉及的三個方法在服務端和瀏覽器端開發都會用到,這一部分主要講其下的router檔案裡面的程式碼想法和createApp.js檔案對路由的處理,這裡是實現兩端路由互相打通的關鍵點。

router資料夾下的routes.js是路由設定文件,將各個頁面下的路由配置都引進來,合成一個設定數組,可以透過這個配置來靈活控制頁面上下線。同目錄下的index.js是RouterV4的標準寫法,透過遍歷配置陣列的方式傳入路由配置,ConnectRouter是用來合併Router的元件,注意到history要作為參數傳入,需要在createApp.js檔案裡做單獨的處理。先大致看一下Route元件中的幾個配置項,值得注意的是其中的thunk屬性,這是實現後端獲取資料後渲染的關鍵一步,正是這個屬性實現了類似Next裡面的元件提前獲取資料的生命週期鉤子,其餘的屬性都可以在相關React-router文件中找到說明,這裡不在贅述。

import routesConfig from './routes';
const Routers=({history})=>(
 <ConnectedRouter history={history}>
 <p>
  {
  routesConfig.map(route=>(
   <Route key={route.path} exact={route.exact} path={route.path} component={route.component} thunk={route.thunk} />
  ))
  }
 </p>
 </ConnectedRouter>
)
export default Routers;
登入後複製

查看app目录下的createApp.js里面的代码可以发现,本框架是针对不同的工作环境做了不同的处理,只有在生产环境下才利用Loadable.Capture方法实现了懒加载,动态引入不同页面对应的打包之后的js文件。到这里还要看一下组件里面的路由配置文件的写法,以home页面下的index.js为例。注意/* webpackChunkName: 'Home' */这串字符,实质是指定了打包后此页面对应的js文件名,所以针对不同的页面,这个注释也需要修改,避免打包到一起。loading这个配置项只会在开发环境生效,当页面加载未完成前显示,这个实际项目开发如果不需要可以删除此组件。

import {homeThunk} from '../../store/actions/thunk';
const LoadableHome = Loadable({
 loader: () =>import(/* webpackChunkName: 'Home' */'./containers/homeContainer.js'),
 loading: Loading,
});
const HomeRouter = {
 path: '/',
 exact: true,
 component: LoadableHome,
 thunk: homeThunk // 服务端渲染会开启并执行这个action,用于获取页面渲染所需数据
}
export default HomeRouter
登入後複製

这里多说一句,有时我们要改造的项目的页面文件里有从window.location里面获取参数的代码,改造成服务端渲染时要全部去掉,或者是要在render之后的生命周期中使用。并且页面级别组件都已经注入了相关路由信息,可以通过this.props.location来获取URL里面的参数。本项目用的是BrowserRouter,如果用HashRouter则包含参数可能略有不同,根据实际情况取用。

根据React16的服务端渲染的API介绍:

  1. 浏览器端使用的注入ConnectedRouter中的history为:import createHistory from 'history/createBrowserHistory'

  2. 服务器端使用的history为import createHistory from 'history/createMemoryHistory'

服务端渲染

这里就不会涉及到koa2的一些基础知识,如果对koa2框架不熟悉可以参考我的另外一篇博文。这里是看server文件夹下都是服务端的代码。首先是简洁的app.js用于保证每次连接都返回的是一个新的服务器端实例,这对于单线程的js语言是很关键的思路。需要重点介绍的就是clientRouter.js这个文件,结合/src/app/configureStore.js这个文件共同理解服务端渲染的数据获取流程和React的渲染机制。

/*configureStore.js*/
import {createStore, applyMiddleware,compose} from "redux";
import thunkMiddleware from "redux-thunk";
import createHistory from 'history/createMemoryHistory';
import { routerReducer, routerMiddleware } from 'react-router-redux'
import rootReducer from '../store/reducers/index.js';
const routerReducers=routerMiddleware(createHistory());//路由
const composeEnhancers = process.env.NODE_ENV=='development'?window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ : compose;
const middleware=[thunkMiddleware,routerReducers]; //把路由注入到reducer,可以从reducer中直接获取路由信息
let configureStore=(initialState)=>createStore(rootReducer,initialState,composeEnhancers(applyMiddleware(...middleware)));
export default configureStore;
登入後複製

window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__这个变量是浏览器里面的Redux的开发者工具,开发React-redux应用时建议安装,否则会有报错提示。这里面大部分都是redux-thunk的示例代码,关于这部分如果看不懂建议看一下redux-thunk的官方文档,这里要注意的是configureStore这个方法要传入的initialState参数,这个渲染的具体思路是:在服务端判断路由的thunk方法,如果存在则需要执行这个获取数据逻辑,这是个阻塞过程,可以当作同步,获取后放到全局State中,在前端输出的HTML中注入window.__INITIAL_STATE__这个全局变量,当html载入完毕后,这个变量赋值已有数据的全局State作为initState提供给react应用,然后浏览器端的js加载完毕后会通过复用页面上已有的dom和初始的initState作为开始,合并到render后的生命周期中,从而在componentDidMount中已经可以从this.props中获取渲染所需数据。

但还要考虑到页面切换也有可能在前端执行跳转,此时作为React的应用不会触发对后端的请求,因此在componentDidMount这个生命周期里并没有获取数据,为了解决这个问题,我建议在这个生命周期中都调用props中传来的action触发函数,但在action内部进行一层逻辑判断,避免重复的请求,实际项目中请求数据往往会有个标识性ID,就可以将这个ID存入store中,然后就可以进行一次对比校验来提前返回,避免重复发送ajax请求,具体可看store/actions/home.js`中的逻辑处理。

import {ADD,GET_HOME_INFO} from '../constants'
export const add=(count)=>({type: ADD, count,})
export const getHomeInfo=(sendId=1)=>async(dispatch,getState)=>{
 let {name,age,id}=getState().HomeReducer.homeInfo;
 if (id === sendId) {
 return //是通过对请求id和已有数据的标识性id进行对比校验,避免重复获取数据。
 }
 console.log('footer'.includes('foo'))
 await new Promise(resolve=>{
 let homeInfo={name:'wd2010',age:'25',id:sendId}
 console.log('-----------请求getHomeInfo')
 setTimeout(()=>resolve(homeInfo),1000)
 }).then(homeInfo=>{
 dispatch({type:GET_HOME_INFO,data:{homeInfo}})
 })
}
登入後複製

注意这里的async/await写法,这里涉及到服务端koa2使用这个来做数据请求,因此需要统一返回async函数,这块不熟的同学建议看下ES7的知识,主要是async如何配合Promise实现异步流程改造,并且如果涉及koa2的服务端工作,对async函数用的更多,这也是本项目要求Node版本为8.x以上的原因,从8开始就可以直接用这两个关键字。

不过到具体项目中,往往会涉及到一些服务端参数的注入问题,但这块根据不同项目需求差异很大,并且不属于这个React服务端改造的一部分,没法统一分享,如果真是公司项目要用到对这块有需求咨询可以打赏后加我微信讨论。

以Home页面为例的渲染流程

为了方便大家理解,我以一个页面为例整理了一下数据流的整体过程,看一下思路:

  1. 服務端接收到請求,透過/home找到對應的路由設定

  2. #判斷路由存在thunk方法,此時執行store/actions/thunk. js裡面的暴露出的函數

  3. 非同步取得的資料會注入到全域state中,此時的dispatch分發其實不生效

  4. 要輸出的HTML程式碼中會將取得到資料後的全域state放到window.__INITIAL_STATE__這個全域變數中,作為initState

  5. window.__INITIAL_STATE__將在react生命週期起作用前合併入全域state,此時react發現dom已經生成,不會再觸發render,資料狀態得到同步

##服務端直出HTML

基本的流程已經介紹結束,至於一些Reducer的函數式寫法,還有actions的位置都是參考網上的一些分析來組織的,具體見仁見智,這個只要符合自己的理解,並且有助於團隊開發就好。如果您符合我在文章一開始設定的讀者背景,相信本文的敘述足夠您點亮自己的服務端渲染技術點啦。如果對React了解偏少也沒關係,可以參考這裡來補充一些React的基礎知識

相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章!

建議閱讀:

如何使用webpack vue環境區域網路

vue底層原則與元件通訊

###################################### ####

以上是React為服務端進行渲染改造的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

React前後端分離指南:如何實作前後端的解耦與獨立部署 React前後端分離指南:如何實作前後端的解耦與獨立部署 Sep 28, 2023 am 10:48 AM

React前後端分離指南:如何實現前後端的解耦和獨立部署,需要具體程式碼範例在當今的Web開發環境中,前後端分離已經成為一種趨勢。透過將前端和後端程式碼分開,可以讓開發工作更加靈活、高效,並且方便進行團隊協作。本文將介紹如何使用React實現前後端分離,從而實現解耦和獨立部署的目標。首先,我們要先理解什麼是前後端分離。傳統的Web開發模式中,前端和後端是耦合在

如何利用React和RabbitMQ建立可靠的訊息應用 如何利用React和RabbitMQ建立可靠的訊息應用 Sep 28, 2023 pm 08:24 PM

如何利用React和RabbitMQ建立可靠的訊息傳遞應用程式引言:現代化的應用程式需要支援可靠的訊息傳遞,以實現即時更新和資料同步等功能。 React是一種流行的JavaScript庫,用於建立使用者介面,而RabbitMQ是一種可靠的訊息傳遞中間件。本文將介紹如何結合React和RabbitMQ建立可靠的訊息傳遞應用,並提供具體的程式碼範例。 RabbitMQ概述:

React Router使用指南:如何實現前端路由控制 React Router使用指南:如何實現前端路由控制 Sep 29, 2023 pm 05:45 PM

ReactRouter使用指南:如何實現前端路由控制隨著單頁應用的流行,前端路由成為了一個不可忽視的重要部分。 ReactRouter作為React生態系統中最受歡迎的路由庫,提供了豐富的功能和易用的API,使得前端路由的實作變得非常簡單和靈活。本文將介紹ReactRouter的使用方法,並提供一些具體的程式碼範例。安裝ReactRouter首先,我們需要

PHP、Vue和React:如何選擇最適合的前端框架? PHP、Vue和React:如何選擇最適合的前端框架? Mar 15, 2024 pm 05:48 PM

PHP、Vue和React:如何選擇最適合的前端框架?隨著互聯網技術的不斷發展,前端框架在Web開發中起著至關重要的作用。 PHP、Vue和React作為三種代表性的前端框架,每一種都具有其獨特的特徵和優勢。在選擇使用哪種前端框架時,開發人員需要根據專案需求、團隊技能和個人偏好做出明智的決策。本文將透過比較PHP、Vue和React這三種前端框架的特徵和使

Java框架與前端React框架的整合 Java框架與前端React框架的整合 Jun 01, 2024 pm 03:16 PM

Java框架與React框架的整合:步驟:設定後端Java框架。建立專案結構。配置建置工具。建立React應用程式。編寫RESTAPI端點。配置通訊機制。實戰案例(SpringBoot+React):Java程式碼:定義RESTfulAPI控制器。 React程式碼:取得並顯示API回傳的資料。

如何利用React開發一個響應式的後台管理系統 如何利用React開發一個響應式的後台管理系統 Sep 28, 2023 pm 04:55 PM

如何利用React開發一個響應式的後台管理系統隨著互聯網的快速發展,越來越多的企業和組織需要一個高效、靈活、易於管理的後台管理系統來處理日常的操作事務。 React作為目前最受歡迎的JavaScript庫之一,提供了一種簡潔、高效和可維護的方式來建立使用者介面。本文將介紹如何利用React開發一個響應式的後台管理系統,並給出具體的程式碼範例。建立React專案首先

vue.js vs.反應:特定於項目的考慮因素 vue.js vs.反應:特定於項目的考慮因素 Apr 09, 2025 am 12:01 AM

Vue.js適合中小型項目和快速迭代,React適用於大型複雜應用。 1)Vue.js易於上手,適用於團隊經驗不足或項目規模較小的情況。 2)React的生態系統更豐富,適合有高性能需求和復雜功能需求的項目。

react有哪些閉包 react有哪些閉包 Oct 27, 2023 pm 03:11 PM

react有事件處理函數、useEffect和useCallback、高階元件等等閉包。詳細介紹:1、事件處理函數閉包:在React中,當我們在元件中定義事件處理函數時,函數會形成一個閉包,可以存取元件作用域內的狀態和屬性。這樣可以在事件處理函數中使用元件的狀態和屬性,實現互動邏輯;2、useEffect和useCallback中的閉包等等。

See all articles