广告
返回顶部
首页 > 资讯 > 前端开发 > html >基于React.js和Node.js怎么实现SSR
  • 398
分享到

基于React.js和Node.js怎么实现SSR

2024-04-02 19:04:59 398人浏览 泡泡鱼
摘要

这篇文章主要介绍基于React.js和node.js怎么实现SSR,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!基础概念SSR:即服务端渲染(Server Side Render)

这篇文章主要介绍基于React.jsnode.js怎么实现SSR,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

基础概念

SSR:即服务端渲染(Server Side Render) 传统的服务端渲染可以使用Java,PHP开发语言来实现,随着 node.js 和相关前端领域技术的不断进步,前端同学也可以基于此完成独立的服务端渲染。

过程:浏览器发送请求 -> 服务器运行 react代码生成页面 -> 服务器返回页面 -> 浏览器下载html文档 -> 页面准备就绪 即:当前页面的内容是服务器生成好给到浏览器的。

基于React.js和Node.js怎么实现SSR 

对应CSR:即客户端渲染(Client Side Render) 过程:浏览器发送请求 -> 服务器返回空白 HTML(HTML里包含一个root节点和js文件) -> 浏览器下载js文件 -> 浏览器运行react代码 -> 页面准备就绪 即:当前页面的内容是js渲染出来

基于React.js和Node.js怎么实现SSR

如何区分页面是否服务端渲染: 右键点击 -> 显示网页源代码,如果页面上的内容在HTML文档里,是服务端渲染,否则就是客户端渲染。

对比

  • CSR:首屏渲染时间长,react代码运行在浏览器,消耗的是浏览器的性能

  • SSR:首屏渲染时间短,react代码运行在服务器,消耗的是服务器的性能

为什么要用服务端渲染

首屏加载时间优化,由于SSR是直接返回生成好内容的HTML,而普通的CSR是先返回空白的HTML,再由浏览器动态加载javascript脚本并渲染好后页面才有内容;所以SSR首屏加载更快、减少白屏的时间、用户体验更好。

SEO (搜索引擎优化),搜索关键词的时候排名,对大多数搜索引擎,不识别JavaScript 内容,只识别 HTML 内容。 (注:原则上可以不用服务端渲染时最好不用,所以如果只有 SEO 要求,可以用预渲染等技术去替代)

构建一个服务端渲染的项目

(1) 使用 Node.js 作为服务端和客户端的中间层,承担 proxy代理,处理cookie等操作。

(2) hydrate 的使用:在有服务端渲染情况下,使用hydrate代替render,它的作用主要是将相关的事件注水进HTML页面中(即:让React组件的数据随着HTML文档一起传递给浏览器网页),这样可以保持服务端数据和浏览器端一致,避免闪屏,使第一次加载体验更高效流畅。

 ReactDom.hydrate(<App />, document.getElementById('root'));

(3) 服务端代码webpack编译:通常会建一个WEBpack.server.js文件,除了常规的参数配置外,还需要设置target参数为'node'。

const serverConfig = {
 target: 'node',
 entry: './src/server/index.js',
 output: {
 filename: 'bundle.js',
 path: path.resolve(__dirname, '../dist')
 },
 externals: [nodeExternals()],
 module: {
 rules: [{
  test: /\.js?$/,
  loader: 'babel-loader',
  exclude: [
  path.join(__dirname, './node_modules')
  ]
 }
 ...
 ]
 }
 (此处省略样式打包,代码压缩,运行坏境配置等等...)
 ...
};

(4) 使用react-dom/server下的 renderToString方法在服务器上把各种复杂的组件和代码转化成 HTML 字符串返回到浏览器,并在初始请求时发送标记以加快页面加载速度,并允许搜索引擎抓取页面以实现SEO目的。

const render = (store, routes, req, context) => {
 const content = renderToString((
 <Provider store={store}>
  <StaticRouter location={req.path} context={context}>
  <div>
   {renderRoutes(routes)}
  </div>
  </StaticRouter>
 </Provider>
 ));
 return `
 <html>
  <head>
  <title>ssr</title>
  </head>
  <body>
  <div id='root'>${content}</div>
  <script src='/index.js'></script>
  </body>
 </html>
 `;
}
app.get('*', function (req, res) {
 ...
 const html = render(store, routes, req, context);
 res.send(html);
});

与renderToString类似功能的还有: i. renderToStaticMarkup:区别在于renderToStaticMarkup 渲染出的是不带data-reactid的纯HTML,在JavaScript加载完成后因为不认识之前服务端渲染的内容导致重新渲染(可能页面会闪一下)。

ii. renderToNodeStream:将React元素渲染为其初始HTML,返回一个输出HTML字符串的可读流。

iii. renderToStaticNodeStream:与renderToNodeStream此类似,除了这不会创建React在内部使用的额外DOM属性,例如data-reactroot。

(5) 使用redux 承担数据准备,状态维护的职责,通常搭配react-redux, redux-thunk(中间件:发异步请求用到action)使用。(本猿目前使用比较多是就是Redux和Mobx,这里以Redux为例)。 A. 创建store(服务器每次请求都要创建一次,客户端只创建一次):

const reducer = combineReducers({
 home: homeReducer,
 page1: page1Reducer,
 page2: page2Reducer
});

export const getStore = (req) => {
 return createStore(reducer, applyMiddleware(thunk.withExtraArgument(serverAxiOS(req))));
}

export const getClientStore = () => {
 return createStore(reducer, window.STATE_FROM_SERVER, applyMiddleware(thunk.withExtraArgument(clientAxios)));
}

B. action: 负责把数据从应用传到store,是store数据的唯一来源

export const getData = () => {
 return (dispatch, getState, axiosInstance) => {
 return axiosInstance.get('interfaceUrl/xxx')
  .then((res) => {
  dispatch({
   type: 'HOME_LIST',
   list: res.list
  })
  });
 }
}

C. reducer:接收旧的state和action,返回新的state,响应actions并发送到store。

export default (state = { list: [] }, action) => {
 switch(action.type) {
 case 'HOME_LIST':
  return {
  ...state,
  list: action.list
  }
 default:
  return state;
 }
}
export default (state = { list: [] }, action) => {
 switch(action.type) {
 case 'HOME_LIST':
  return {
  ...state,
  list: action.list
  }
 default:
  return state;
 }
}

D. 使用react-redux的connect,Provider把组件和store连接起来

Provider 将之前创建的store作为prop传给Provider

const content = renderToString((
 <Provider store={store}>
 <StaticRouter location={req.path} context={context}>
  <div>
  {renderRoutes(routes)}
  </div>
 </StaticRouter>
 </Provider>
));

connect([mapStateToProps],[mapDispatchToProps],[mergeProps], [options])接收四个参数 常用的是前两个属性 mapStateToProps函数允许我们将store中的数据作为props绑定到组件上mapDispatchToProps将action作为props绑定到组件上

 connect(mapStateToProps(),mapDispatchToProps())(MyComponent)

(6) 使用react-router承担路由职责 服务端路由不同于客户端,它是无状态的。React 提供了一个无状态的组件StaticRouter,向StaticRouter传递当前URL,调用ReactDOMServer.renderToString() 就能匹配到路由视图。

服务端

import { StaticRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config'
import routes from './router.js'

<StaticRouter location={req.path} context={{context}}>
{renderRoutes(routes)}
</StaticRouter>

浏览器端

import { BrowserRouter } from 'react-router-dom';
import { renderRoutes } from 'react-router-config'
import routes from './router.js'

<BrowserRouter>
 {renderRoutes(routes)}
</BrowserRouter>

当浏览器的地址栏发生变化的时候,前端会去匹配路由视图,同时由于req.path发生变化,服务端匹配到路由视图,这样保持了前后端路由视图的一致,在页面刷新时,仍然可以正常显示当前视图。如果只有浏览器端路由,而且是采用BrowserRouter,当页面地址发生变化后去刷新页面时,由于没有对应的HTML,会导致页面找不到,但是加了服务端路由后,刷新发生时服务端会返回一个完整的html给客户端,页面仍然正常显示。 推荐使用 react-router-config插件,然后如上代码在StaticRouter和BrowserRouter标签的子元素里加renderRoutes(routes):建一个router.js文件

const routes = [{ component: Root,
 routes: [
 { path: '/',
  exact: true,
  component: Home,
  loadData: Home.loadData
 },
 { path: '/child/:id',
  component: Child,
  loadData: Child.loadData
  routes: [
  path: '/child/:id/grand-child',
  component: GrandChild,
  loadData: GrandChild.loadData
  ]
 }
 ]
}];

在浏览器端请求一个地址的时候,server.js 里在实际渲染前可以通过matchRouters 这种方式确定要渲染的内容,调用loaderData函数进行action派发,返回promise->promiseAll->renderToString,最终生成HTML文档返回。

import { matchRoutes } from 'react-router-config'
 const loadBranchData = (location) => {
 const branch = matchRoutes(routes, location.pathname)

 const promises = branch.map(({ route, match }) => {
  return route.loadData
  ? route.loadData(match)
  : Promise.resolve(null)
 })

 return Promise.all(promises)
}

(7) 写组件注意代码同构(即:一套React代码在服务端执行一次,在客户端再执行一次) 由于服务器端绑定事件是无效的,所以服务器返回的只有页面样式(&注水的数据),同时返回JavaScript文件,在浏览器上下载并执行JavaScript时才能把事件绑上,而我们希望这个过程只需编写一次代码,这个时候就会用到同构,服务端渲染出样式,在客户端执行时绑上事件。

优点: 共用前端代码,节省开发时间 弊端: 由于服务器端和浏览器环境差异,会带来一些问题,如document等对象找不到,DOM计算报错,前端渲染和服务端渲染内容不一致等;前端可以做非常复杂的请求合并和延迟处理,但为了同构,所有这些请求都在预先拿到结果才会渲染。

以上是“基于React.js和Node.js怎么实现SSR”这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注编程网html频道!

--结束END--

本文标题: 基于React.js和Node.js怎么实现SSR

本文链接: https://www.lsjlt.com/news/76239.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 基于React.js和Node.js怎么实现SSR
    这篇文章主要介绍基于React.js和Node.js怎么实现SSR,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!基础概念SSR:即服务端渲染(Server Side Render) ...
    99+
    2022-10-19
  • 基于Node.js怎么实现WebSocket通信
    这篇“基于Node.js怎么实现WebSocket通信”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“基于Node.js怎么实...
    99+
    2023-06-17
  • ReentrantLock是怎么基于AQS实现的
    这篇文章主要介绍了ReentrantLock是怎么基于AQS实现的的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇ReentrantLock是怎么基于AQS实现的文章都会有所收获...
    99+
    2022-10-19
  • 怎么实现php基于cookie登录
    小编给大家分享一下怎么实现php基于cookie登录,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!php制作记住密码自动登录的解决思路,其实也就是对session...
    99+
    2023-06-14
  • 基于dubbo分组group怎么实现
    本文小编为大家详细介绍“基于dubbo分组group怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“基于dubbo分组group怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。服务分组当一个接口有多...
    99+
    2023-07-05
  • 基于JS怎么实现Flappy Bird游戏
    本文小编为大家详细介绍“基于JS怎么实现Flappy Bird游戏”,内容详细,步骤清晰,细节处理妥当,希望这篇“基于JS怎么实现Flappy Bird游戏”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习...
    99+
    2023-06-30
  • 基于Java怎么实现扫码登录
    这篇文章主要介绍了基于Java怎么实现扫码登录的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇基于Java怎么实现扫码登录文章都会有所收获,下面我们一起来看看吧。原理解析1. 身份认证机制在介绍扫码登录的原理之前...
    99+
    2023-06-30
  • java基于quasar怎么实现协程池
    这篇文章主要讲解了“java基于quasar怎么实现协程池”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java基于quasar怎么实现协程池”吧!业务场景:golang与swoole都拥抱...
    99+
    2023-07-02
  • 基于Zookeeper怎么实现分布式锁
    这篇文章主要介绍“基于Zookeeper怎么实现分布式锁”,在日常操作中,相信很多人在基于Zookeeper怎么实现分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”基于Zookeeper怎么实现分布式锁...
    99+
    2023-06-22
  • Android基于Sqlite怎么实现注册和登录功能
    本篇内容主要讲解“Android基于Sqlite怎么实现注册和登录功能”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android基于Sqlite怎么实现注册和登录功能”吧!实现逻辑项目的图片结...
    99+
    2023-06-30
  • 基于vue3和element-plus的暗黑模式怎么实现
    本篇内容介绍了“基于vue3和element-plus的暗黑模式怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、基本使用因为是通过...
    99+
    2023-07-04
  • Android基于Fresco怎么实现圆角和圆形图片
    这篇文章主要介绍“Android基于Fresco怎么实现圆角和圆形图片”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android基于Fresco怎么实现圆角和圆形图片”文章能帮助大家解决问题。Fr...
    99+
    2023-06-29
  • 基于opencv和pillow怎么实现人脸识别系统
    这篇文章主要讲解了“基于opencv和pillow怎么实现人脸识别系统”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“基于opencv和pillow怎么实现人脸识别系统”吧!本文不涉及分类器、...
    99+
    2023-06-21
  • 基于Flask和PaddleHub怎么实现人脸检测功能
    这篇文章主要介绍“基于Flask和PaddleHub怎么实现人脸检测功能”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“基于Flask和PaddleHub怎么实现人脸检测功能”文章能帮助大家解决问题。...
    99+
    2023-07-05
  • 基于vw的响应式排版和布局怎么实现
    这篇“基于vw的响应式排版和布局怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“基于...
    99+
    2022-10-19
  • Python基于socket怎么实现TCP客户端和服务端
    这篇文章主要讲解了“Python基于socket怎么实现TCP客户端和服务端”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python基于socket怎么实现TCP客户端和服务端”吧!一、基...
    99+
    2023-06-30
  • 基于OpenCV和Gradio怎么实现简单的人脸识别
    今天小编给大家分享一下基于OpenCV和Gradio怎么实现简单的人脸识别的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。环境...
    99+
    2023-07-05
  • Java基于JNDI怎么实现读写分离
    这篇文章主要介绍“Java基于JNDI怎么实现读写分离”,在日常操作中,相信很多人在Java基于JNDI怎么实现读写分离问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java基于JNDI怎么实现读写分离”的疑...
    99+
    2023-06-22
  • MySQL基于GTID主从搭建怎么实现
    本篇内容介绍了“MySQL基于GTID主从搭建怎么实现”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成! ...
    99+
    2022-10-19
  • 基于Protostuff怎么实现Netty编解码器
    本篇内容主要讲解“基于Protostuff怎么实现Netty编解码器”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“基于Protostuff怎么实现Netty编解...
    99+
    2022-10-19
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作