Skip to content

使用react,react-router-dom,redux,redux-thunk,express搭建的一个react ssr开发框架

Notifications You must be signed in to change notification settings

lizuncong/react-ssr-200305

Repository files navigation

生产环境打包构建:

1.首先运行npm run build打包编译代码,

2.cd dist目录,然后运行node server.js 启动node服务,即可通过localhost:3000端口访问

开发环境启动服务:npm run start。开发环境启动有问题,放弃治疗了。可以先试用生产环境打包构建方式 运行项目

开发环境热更新有点问题还没解决。

待解决的问题: 1.使用css提取样式文件并以外链的形式引用,在浏览器network-preview预览,发现css没生效。实际

上页面是能够正常显示的(禁用浏览器的js后,也能正常显示)。但是引用的antd的样式,在network-preview

的时候是生效的

2020.03.16:如果以外链的形式引用本地css文件,如:

      <link 
          rel="stylesheet"
          href="/assets/static/css/SideBarLayout.31270e6e.chunk.css"
       />
       <link href="https://cdnjs.cloudflare.com/ajax/libs/antd/4.0.1/antd.min.css" rel="stylesheet"/>
 如果查看浏览器network->preview预览,会发现引入的antd.min.css样式会生效,而本地的SideBarLayout.31270e6e.chunk.css
 
 预览的时候不生效,页面显示正常。排查了一下发现是由于浏览器的network->preview实现方式的问题。href要以绝对路径的形式引入,比如
 
 这里可以配置output.publicPath: 'http://localhost:3000/'给打包的css添加前缀
 
 ```html
      <link 
          rel="stylesheet"
          href="http://localhost:3000/assets/static/css/SideBarLayout.31270e6e.chunk.css"
       />
 ```
  这样在浏览器network->preview预览的时候,样式就正常了

React服务端渲染的思路: -> 服务器端运行React代码渲染出HTML(这个HTML同时包含script标签加载客户端脚本,这点很重要, 不然浏览器接收的只是一个静态的HTML,页面上所有的元素都不可交互) 因此需要配置服务端webpack配置以打包服务器端代码,然后运行打包后的代码。 -> 发送HTML给浏览器 -> 浏览器接收到内容展示 -> 浏览器加载HTML里面script标签的js脚本 -> js中的React代码在浏览器端重新执行,浏览器不会重新生成HTML,而是试图往HTML标记中添加事件等操作 -> js中的React代码接管页面操作,比如元素点击,页面导航都交由浏览器端js脚本处理

据实践,在服务端渲染中,只有组件的componentWillMount生命周期会调用 React服务端渲染的几大难点: 1.同构。即如何让服务端返回的html标记可交互。 2.css的服务端渲染。即如何收集组件的css并插入到服务端生成的html当中。

React服务端渲染路由问题: 在服务端渲染与客户端渲染有许多不同,因为服务器端是无状态的。因此服务器端使用 无状态的替代,然后通过来自于服务端的请求url使得路由能够匹配上。 对于客户端React代码路由,此时需要使用而不能使用,因为 使用的是hash导航,页面路径/#/后面的URL不会发送到服务端,从而造成服务端路由匹配不上。

的context属性: context是个原生js对象。在渲染期间(during the render),组件能够往context对象添加属性以存储渲染 信息。

1.服务端渲染 VS 客户端渲染 的利弊 1.1页面由前端渲染,前后端分离,开发效率得到极大提高 1.2客户端渲染的流程:浏览器下载JS文件 -> 浏览器运行React代码 -> 页面准备就绪 1.3服务端渲染的流程:浏览器下载html文档 -> 页面显示 1.4因此客户端渲染虽然提高了开发效率,但是牺牲了首屏加载速度,而且SEO(搜索引擎优化)不友好。 SSR虽然开发效率低,但是首屏加载快,而且SEO友好。 1.5客户端渲染,React代码在浏览器上执行,消耗的是用户浏览器的性能。 1.6服务端渲染,React代码在服务器上执行,消耗的是服务器的性能。比如在服务端将虚拟dom转换成 字符串需要大量的计算。 1.7总结:如果不是对SEO比较执着,而且网站首屏时间在合理范围,还是不要使用SEO,毕竟太伤服务器。

2.ReactDOMServer(react-dom/server)可以把react组件渲染成静态标记。 ReactDOMServer提供了四个方法: 1.renderToString():既可以在服务端,也可以在客户端使用。 这个方法会生成静态标记,并添加额外的属性,如data-reactroot。如果做服务端渲染,需要和ReactDOM.hydrate()结合使用 ReactDOM.hydrate会尝试往已经生成好的html标记中添加事件监听器,而不会重新生成html标记 2.renderToStaticMarkup:既可以在服务端,也可以在客户端使用。 3.renderToNodeStream()及renderToStaticNodeStream()方法由于依赖于 stream这个包,因此只能在服务端使用,不能在浏览器端使用。

3.ReactDOM.hydrate(以下内容翻译自react官网): 1.和ReactDOM.render()方法一样,都是客户端方法。 2.会尝试往ReactDOMServer.renderToString方法生成的html标记上添加事件监听器。 3.React期望服务端生成的html标记和客户端生成的html标记要相同。 4.总之ReactDOM.render方法一定要和ReactDOMServer.renderToString方法一起使用。

4.同构:服务器端使用renderToString方法生成的html标记不会生成事件监听这些属性,renderToString 生成的html标记是静态的,不可交互。如果我们需要html标记可交互,比如点击按钮能执行某些操作。 那么我们可以先让renderToString生成静态的html标记并返回给客户端,然后在客户端使用ReactDOM.hydrate 方法再加载同样的一个js脚本,ReactDOM.hydrate不会重新生成一个html标记,而是会尝试往已生成的html标记 中添加事件属性等。这个过程就是同构。即一套代码在服务端执行一次生成html文档,在客户端再执行一次以绑定 事件等属性

5.CSS服务端渲染: 使用isomorphic-style-loader取代style-loader,isomorphic-style-loader功能和style-loader相似。 但是isomorphic-style-loader针对关键路径CSS渲染进行了优化,并且在同构app中能很好的运行。 在服务端渲染中,使用isomorphic-style-loader,isomorphic-style-loader提供了两个辅助函数: _insertCss(给dom注入css)以及_getCss(返回一个css字符串)。 因此服务端CSS渲染的思路(或者说原理)是: 1.首先在组件的componentWillMount生命周期内:

```jsx
        import styles from './index.css'
        ...
        componentWillMount () {
          if(this.props.staticContext){
            this.props.staticContext.css.push(styles._getCss())
          }
        }
```
    通过staticContext获取到组件的样式字符串,staticContext由StaticRouter提供。StaticRouter的context
    属性会在组件渲染期间收集一些信息。
2.然后在服务端渲染的时候,将context收集到的css插入到html中:
      const context = {
        css: [],
      };
      const content = renderToString(
        <StaticRouter location={req.path} context={context}>
          { Routes }
        </StaticRouter>
      )
      const cssStr = context.css.join('\n')
      return `
        <html>
          <head>
             <title>ssr</title>
             <style>${cssStr}</style>
          </head>
          <body>
            <div id="root">${content}</div>
            <script src="/client.bundle.js"></script>
          </body>
        </html>`
3.所以问题来了,凡是需要服务端渲染的组件,内部都要实现componentWillMount生命周期以收集组件css。这样难免会
很麻烦
    componentWillMount () {
      if(this.props.staticContext){
        this.props.staticContext.css.push(styles._getCss())
      }
    }
因此为了解决上面这个问题,可以实现一个高阶函数withStyle.js:
    export default (DecoratedComponent, styles) => {
      return class NewComponent extends React.Component{
        componentWillMount () {
          if(this.props.staticContext){
            this.props.staticContext.css.push(styles._getCss())
          }
        }
    
        render() {
          return <DecoratedComponent {...this.props} />
        }
      }
    }
这个函数接受一个组件及该组件的css对象。
比如header.jsx组件如果需要做服务端渲染,那么可以用withStyle(Header, styles)包装一下。
4.这个估计就是isomorphic-style-loader提供的StyleContext以及withStyles的实现原理?

6.异步数据服务器渲染: 在客户端渲染的实现当中,一般在组件的生命周期函数内,如componentDidMount,获取异步数据并渲染。 但是在服务端渲染中,大部分组件生命周期函数都不会执行(试了以下只有componentWillMount会执行)。因此 需要在服务端渲染的过程中调用接口获取数据并填充store。可以借助于react-router-dom提供的路由的loadData 属性。具体可看react-router官网的server-rendering指南 关于服务端渲染数据获取的一个流程: 1.服务端会根据路由匹配对应的页面 2.如果匹配到的页面定义了loadData方法,那么就调用这些loadData方法 3.等所有的loaData方法执行完毕,并填充到store后,返回给浏览器。 4.此时浏览器需要想方法获取到服务端返回的store里面的数据,这就是数据的脱水和注水过程, 服务端返回给浏览器的html文档中,会额外插入一个脚本及全局变量 那么浏览器在初始化store的时候,可以从这个全局变量中拿到初始的数据并初始化浏览器端的store

关于开发环境的搭建的几点思路: 1.服务端和客户端各自编译并监听文件改动,如果只改变到服务端文件,则只重新编译服务端代码。 如果只改动客户端文件,则只重新编译客户端代码

About

使用react,react-router-dom,redux,redux-thunk,express搭建的一个react ssr开发框架

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published