Maison  >  Article  >  interface Web  >  Explication détaillée de la transformation du rendu côté serveur du projet React

Explication détaillée de la transformation du rendu côté serveur du projet React

小云云
小云云original
2018-03-20 09:10:382671parcourir

En raison du besoin de référencement des pages Web, j'ai souhaité transformer le précédent projet React en rendu côté serveur. Après quelques investigations et recherches, j'ai consulté de nombreuses informations sur Internet. J'ai marché avec succès sur le piège. Cet article vous présente principalement l'explication détaillée de la transformation du rendu côté serveur du projet React (koa2+webpack3.11. L'éditeur pense que c'est plutôt bon, je vais donc la partager avec vous maintenant et la donner comme référence). . Suivons l'éditeur pour y jeter un œil, j'espère que cela pourra aider tout le monde.

Idées de sélection : Pour implémenter le rendu côté serveur, vous souhaitez utiliser la dernière version de React et n'apporter pas de modifications majeures à la méthode d'écriture existante. Si vous envisagez de rendre sur le serveur. côté dès le début, il est recommandé d'utiliser directement le framework NEXT Venez écrire

Adresse du projet : https://github.com/wlx200510/react_koa_ssr

Sélection d'échafaudages : webpack3.11.0 + React Router4 + Redux + koa2 + React16 + Node8.x

Expérience principale : familiarisez-vous avec les connaissances liées à React, développez avec succès votre domaine technique et accumulez des tâches de serveur côté technologie dans les projets réels

Remarques : Avant d'utiliser le framework, assurez-vous de confirmer que la version actuelle du webpack est 3.x et que Node est 8.x ou supérieur. Il est préférable que les lecteurs aient utilisé React pour. plus de 3 mois et avoir une expérience réelle du projet React

Introduction à l'annuaire du projet


├── 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处理

Construire des idées du project

  1. Utilisez webpack-dev-server pour le développement local, pour réaliser une mise à jour à chaud, le processus de base est similaire au développement de réaction précédent, qui est toujours un rendu côté navigateur. Par conséquent, lors de l’écriture du code, un ensemble de logique et deux environnements de rendu doivent être pris en compte.

  2. Une fois le rendu de la page frontale terminé, son saut de routeur ne fera pas de requêtes au serveur, réduisant ainsi la pression sur le serveur. Il existe donc deux façons de le faire. entrez dans la page, qui doit également être considérée Le problème de l'isomorphisme de routage dans deux environnements de rendu.

  3. L'environnement de production doit utiliser koa comme serveur principal pour implémenter le chargement à la demande, obtenir des données côté serveur, restituer l'intégralité du code HTML et utiliser les dernières fonctionnalités. de React16 pour fusionner l'intégralité de l'arborescence d'état.

Introduction au développement local

Les principaux fichiers impliqués dans la vérification du développement local sont les fichiers index.js dans le répertoire src pour déterminer le environnement d'exploitation. L'API de module.hot ne sera utilisée que dans l'environnement de développement pour implémenter la notification de mise à jour du rendu de page lorsque le réducteur change. Faites attention à la méthode hydrate. Il s'agit d'une nouvelle méthode API spécialement ajoutée pour le rendu côté serveur dans l'environnement d'exploitation. Version v16. Basé sur la méthode de rendu, la réutilisation maximale possible du contenu de rendu côté serveur est obtenue et le processus du DOM statique aux NODES dynamiques est réalisé. L'essence est de remplacer le processus de jugement de la marque de contrôle sous la version v15, rendant le processus de réutilisation plus efficace et élégant.


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'));
 })
 }
}

Faites attention à la définition de la fonction window.main Combinée avec index.ejs, vous pouvez savoir que cette fonction est déclenchée une fois tous les scripts chargés, et React- y est utilisé. La méthode d'écriture de Loadable est utilisée pour le chargement paresseux des pages. La méthode d'écriture d'empaquetage des pages séparément doit être expliquée en conjonction avec les paramètres de routage. Il convient de noter que les trois méthodes exposées sous le fichier d'application sont communes côté navigateur et côté serveur. Ce qui suit concerne principalement cette partie de l'idée.

Traitement de l'itinéraire

Ensuite, regardez les fichiers suivants dans le répertoire src/app. index.js expose trois méthodes impliquées dans le service It. sera utilisé dans le développement final et côté navigateur. Cette partie parle principalement des idées de code dans le fichier du routeur et du traitement du routage par le fichier createApp.js. C'est le point clé pour parvenir à une communication mutuelle entre les routes. les deux extrémités.

Le routes.js sous le dossier du routeur est le fichier de configuration de routage. Il importe la configuration de routage sous chaque page et la synthétise dans un tableau de configuration. Vous pouvez utiliser cette configuration pour contrôler de manière flexible le système en ligne et. pages hors ligne. L'index.js dans le même répertoire est la manière standard d'écrire RouterV4. La configuration de routage est transmise en parcourant le tableau de configuration. ConnectRouter est un composant utilisé pour fusionner les routeurs. Notez que l'historique doit être transmis en tant que paramètre et doit être transmis. être dans le fichier createApp.js. Effectuez un traitement séparé. Jetons un bref coup d'œil à plusieurs éléments de configuration du composant Route. Ce qu'il convient de noter est l'attribut thunk. Il s'agit d'une étape clé dans la réalisation du rendu une fois que le backend a obtenu les données. C'est cet attribut qui permet aux composants similaires à Next d'obtenir des données. Les hooks de cycle de vie et autres attributs peuvent être trouvés dans la documentation React-router pertinente et ne seront pas décrits ici.


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;

En regardant le code dans createApp.js dans le répertoire de l'application, vous pouvez constater que ce framework est traité différemment pour différents environnements de travail. Il n'est utilisé que dans. l'environnement de production. Ensuite, nous utilisons la méthode Loadable.Capture pour implémenter le chargement paresseux et introduire dynamiquement les fichiers js packagés correspondant aux différentes pages. À ce stade, nous devons également examiner comment écrire le fichier de configuration de routage dans le composant, en prenant comme exemple index.js sous la page d'accueil. Notez que /* webpackChunkName: 'Home' */ Cette chaîne de caractères spécifie essentiellement le nom du fichier js correspondant à cette page après packaging, donc pour différentes pages, ce commentaire doit également être modifié pour éviter un packaging ensemble. L'élément de configuration de chargement ne prendra effet que dans l'environnement de développement et sera affiché avant le chargement de la page. Ce composant peut être supprimé s'il n'est pas nécessaire au développement réel du projet.


import {homeThunk} from &#39;../../store/actions/thunk&#39;;

const LoadableHome = Loadable({
 loader: () =>import(/* webpackChunkName: &#39;Home&#39; */&#39;./containers/homeContainer.js&#39;),
 loading: Loading,
});

const HomeRouter = {
 path: &#39;/&#39;,
 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 &#39;history/createMemoryHistory&#39;;
import { routerReducer, routerMiddleware } from &#39;react-router-redux&#39;
import rootReducer from &#39;../store/reducers/index.js&#39;;

const routerReducers=routerMiddleware(createHistory());//路由
const composeEnhancers = process.env.NODE_ENV==&#39;development&#39;?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 &#39;../constants&#39;
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(&#39;footer&#39;.includes(&#39;foo&#39;))
 await new Promise(resolve=>{
 let homeInfo={name:&#39;wd2010&#39;,age:&#39;25&#39;,id:sendId}
 console.log(&#39;-----------请求getHomeInfo&#39;)
 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

Le processus de base a été introduit. Quant à l'écriture fonctionnelle de certains Réducteurs et aux positions des actions, elles sont organisées en référence à certaines analyses sur Internet. Différentes personnes ont des avis différents. votre propre compréhension et est utile pour le développement de l’équipe. Très bien. Si vous rencontrez le contexte de lecteur que j'ai défini au début de l'article, je pense que la description de cet article est suffisante pour vous permettre d'éclairer votre propre technologie de rendu côté serveur. Peu importe si vous ne savez pas grand-chose sur React. Vous pouvez vous référer ici pour compléter quelques connaissances de base sur React.

Recommandations associées :

Explication détaillée des exemples de rendu côté serveur React

webpack+react+nodejs rendu côté serveur_html/css_WEB -ITnose

Fonction de rendu côté serveur Vue.js et ASP.NET Core

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn