前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >用React框架和Express模块进行服务器端渲染

用React框架和Express模块进行服务器端渲染

作者头像
疯狂的技术宅
发布2019-03-27 12:24:36
4.4K0
发布2019-03-27 12:24:36
举报
文章被收录于专栏:京程一灯

这周末我启动了一个编外项目,这个项目里要做的是服务器端的渲染。我在网上找的教程也好,建议也好都太深了,像Redux框架或React路由导航(React Router)这些特殊、时髦的东西根本不需要,我们可爱的React好像没什么单纯的教程。

程序的生成步骤我就当大家已经准备好了。没有的话,下面给你一个链接,这个网页上包含了一个webpack配置文件,有了以后可以直接运行 npm run build这个命令。

https://github.com/Roilan/react-server-boilerplate

一开始,我们先要建立文件夹结构。文件夹结构看起来会是这样的:

代码语言:javascript
复制
/
 /dist -- 放生成文件
  /assets -- 放从生成步骤中打包过来的素材文件
   index.css
   bundle.js
  server.js -- 这是打包后的服务器文件

 /src -- 放源文件
  /app -- 放React组件(Component)
   index.js -- React根组件(root component)
   browser.js -- React根组件,用来包裹在`react-dom/render`里
  index.js -- express服务器文件
  template.js -- 基本HTML模板文件

dist文件夹里的文件不用看,这些是从生成步骤中产生的。创立好这些文件后,只要安装以下模块:

代码语言:javascript
复制
npm install --save react react-dom express

我先创建React的根组件,还有浏览器如何渲染。在 app/index.js文件里,就写一个hello world组件。

代码语言:javascript
复制
// app/index.js

import React, { Component } from 'react';

export default class App extends Component {
  render() {
    return (
      <div>
        <h1>hello world</h1>
      </div>
    );
  }
}

将这个组件导入到 app/browser.js文件中,并把它渲染到DOM树里.

代码语言:javascript
复制
// app/browser.js
import React from 'react';
import { render } from 'react-dom';
import App from './index';

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

大家可能会想“为什么把这两个文件分开?写在一起不好吗?”一会儿我就会说到这点,肯定是有道理的,相信我。

我们现在来看 src/template.js模板文件,在里面创建一个初始的HTML页面,服务器会把这个页面传送下来。 template.js模板文件只有一个函数,返回值是一个HTML字符串,然后我们的组件就可以渲染到这里面去,和 app/browser.js做的事差不多,只不过是由服务器完成的。模板会像这个样子:

代码语言:javascript
复制
// src/template.js
export default ({ body, title }) => {
  return `
    <!DOCTYPE html>
    <html>
      <head>
        <title>${title}</title>
        <link rel="stylesheet" href="/assets/index.css" />
      </head>

      <body>
        <div id="root">${body}</div>
      </body>

      `<script src="/assets/bundle.js">`</script>
    </html>
  `;
};

接下来要做的就是把 body内容和 title内容传进来,插到这个HTML字符串里去。 body的内容就是之前的React组件, title的内容 就是当前所在页面的标题。大家还可以看到两个额外的素材文件 index.cssbundle.jsindex.css是编译过的CSS样式文件, bundle.js是客户端用的React打包文件,从服务器发送时会一起发过来。当服务器完成渲染时,客户端的React会接收这个打包文件。

src/server.js服务器文件,这里是最终奇迹发生的地方,它会把React组件发送到客户端去。先导入所有的库、组件和模板。

代码语言:javascript
复制
// src/server.js
import express from 'express';
import React from 'react';
import { renderToString } from 'react-dom/server';
import App from './app';
import template from './template';

我们看到里面有一些新的内容,从 react-dom/server模块中导入了 renderToString函数。客户端调用 ReactDOM.render函数时, renderToString函数会将React组件渲染到HTML中去并保留。我们不想造成不必要的客户端渲染,而丧失了服务器端渲染的益处,所以这一点很好。剩下要做的就是告诉express模块,客户访问初始路线时,要把我们的组件传送下来。

代码语言:javascript
复制
const server = express();

server.use('/assets', express.static('assets'));

server.get('/', (req, res) => {
  const appString = renderToString(<App />);

  res.send(template({
    body: appString,
    title: 'Hello World from the server'
  }));
});

server.listen(8080);

大家可以看到三个主要部分,先设定好 assets路线要用 assets文件夹,这样,把里面的CSS文件和JS打包文件包括进来就很容易了。这里,大家会看到 renderToString函数如何实际运用,唯一传进去的参数就是React的根组件,这就是为什么我们之前要把这个组件分开写在两个文件里,我们只关心怎么把这个根组件渲染到服务器上的某个字符串里去。最后,把 body内容和 title内容传进模板文件里去,最终生成的字符串发到客户端去。


如果我们想从服务器发送一些属性到客户端怎么办?比如,要检测一下是不是移动设备,如果是,就渲染一个不同的视图。 让我们修改一下请求,加入一个 isMobile属性,更新一下根组件。

代码语言:javascript
复制
// src/server.js
server.get('/', (req, res) => {
  const isMobile = true; // 假设是移动设备
  const appString = renderToString(<App isMobile={isMobile} />);

  res.send(template({
    body: appString,
    title: 'Hello World from the server'
  }));
});
代码语言:javascript
复制
// app/index.js
export default class App extends Component {
  render() {
    const { isMobile } = this.props;

    return (
      <div>
        <h1>hello world {isMobile ? 'mobile' : 'desktop'}</h1>
      </div>
    );
  }
}

啊,不对!这是什么意思?

应该显示的是 hello world mobile,而现在这个结果不是我们想要的。要说的话,React是很智能的,它会保证客服两端的东西都能配对。这个错误信息很清楚,不是什么我们看不见的魔术,它问的是为什么有一个新的标记元素插进来。看到这个错误信息,我们明白了,客户端预计收到的标记元素和实际的不符。这个信息指出了一点,那就是要看看初始状态。

那到底发生了什么?当服务器上生成响应时,客户端不知道 isMobile这个属性应该是收到的一部分,也不知道要把这个属性的值设为真。我们需要给它一个初始状态,能让客户端先取得这个属性,然后客服两端就匹配了。

只要做一些小调整就可以了。一开始,先打开 server.js文件,给模板传入某个初始状态。

代码语言:javascript
复制
// src/server.js
server.get('/', (req, res) => {
  const isMobile = true;
  const initialState = { isMobile };
  const appString = renderToString(<App {...initialState} />);

  res.send(template({
    body: appString,
    title: 'Hello World from the server',
    initialState: JSON.stringify(initialState)
  }));
});

开始的部分,我们创建了一个初始状态( initialState)对象,将这个对象散布到根组件中去,再往下传到模板里去。在模板中,我们要把这个变化传到客户端去,看起来像这样:

代码语言:javascript
复制
// src/template.js
export default ({ body, title, initialState }) => {
  return '
    <!DOCTYPE html>
    <html>
      <head>
        `<script>window.__APP_INITIAL_STATE__ = ${initialState}</script>`
        <title>${title}</title>
        <link rel="stylesheet" href="/assets/index.css" />
      </head>

      <body>
        <div id="root">${body}</div>
      </body>

      `<script src="/assets/bundle.js">`</script>
    </html>
  ';
};

注意在窗口(window)中设置的这个初始状态对象。最后要改的是将这个初始状态对象散布到 browser.js文件里,加到根组件里去,使客服两端初始状态一致。

代码语言:javascript
复制
// app/browser.js
import React from 'react';
import { render } from 'react-dom';
import App from './index';

render(<App {...window.__APP_INITIAL_STATE__} />, document.getElementById('root'));

运行这个程序并记录下初始状态,我们会得到第一次想得到的结果。

成功!!!


往期精选文章

使用虚拟dom和JavaScript构建完全响应式的UI框架

扩展 Vue 组件

使用Three.js制作酷炫无比的无穷隧道特效

一个治愈JavaScript疲劳的学习计划

全栈工程师技能大全

WEB前端性能优化常见方法

一小时内搭建一个全栈Web应用框架

干货:CSS 专业技巧

四步实现React页面过渡动画效果

让你分分钟理解 JavaScript 闭包



小手一抖,资料全有。长按二维码关注京程一灯,阅读更多技术文章和业界动态。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-11-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 京程一灯 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档