React服务端渲染探秘: 7.CSS的服务端渲染思路(context钩子变量)

1、客户端项目中引入CSS

仍是以Home组件为例css

//Home/style.css
body {
  background: gray;
}

复制代码

如今,在Home组件代码中引入:html

import styles from './style.css';
复制代码

要知道这样的引入CSS代码的方式在通常环境下是运行不起来的,须要在webpack中作相应的配置。 首先安装相应的插件。node

npm install style-loader css-loader --D
复制代码
//webpack.client.js
const path = require('path');
const merge = require('webpack-merge');
const config = require('./webpack.base');

const clientConfig = {
  mode: 'development',
  entry: './src/client/index.js',
  module: {
    rules: [{
      test: /\.css?$/,
      use: ['style-loader', {
        loader: 'css-loader',
        options: {
          modules: true
        }
      }]
    }]
  },
  output: {
    filename: 'index.js',
    path: path.resolve(__dirname, 'public')
  },
}

module.exports = merge(config, clientConfig);
复制代码
//webpack.base.js代码,回顾一下,配置了ES语法相关的内容
module.exports = {
  module: {
    rules: [{
      test: /\.js$/,
      loader: 'babel-loader',
      exclude: /node_modules/,
      options: {
        presets: ['@babel/preset-react',  ['@babel/preset-env', {
          targets: {
            browsers: ['last 2 versions']
          }
        }]]
      }
    }]
  }
}
复制代码

好,如今在客户端CSS已经产生了效果。 react

但是打开网页源代码:

咦?里面并无出现任何有关CSS样式的代码啊!那这是什么缘由呢?很简单,其实咱们的服务端的CSS加载尚未作。接下来咱们来完成CSS代码的服务端的处理。

2、服务端CSS的引入

首先,来安装一个webpack的插件,webpack

npm install -D isomorphic-style-loader
复制代码

而后再webpack.server.js中作好相应的css配置:web

//webpack.server.js
const path = require('path');
const nodeExternals = require('webpack-node-externals');
const merge = require('webpack-merge');
const config = require('./webpack.base');

const serverConfig = {
  target: 'node',
  mode: 'development',
  entry: './src/server/index.js',
  externals: [nodeExternals()],
  module: {
    rules: [{
      test: /\.css?$/,
      use: ['isomorphic-style-loader', {
        loader: 'css-loader',
        options: {
          modules: true
        }
      }]
    }]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'build')
  }
}

module.exports = merge(config, serverConfig);
复制代码

它作了些什么事情?npm

再看看这行代码:数组

import styles from './style.css';
复制代码

引入css文件时,这个isomorphic-style-loader帮咱们在styles中挂了三个函数。输出styles看看:bash

如今咱们的目标是拿到CSS代码,直接经过styles._getCss便可得到。

那咱们拿到CSS代码后放到哪里呢?其实react-router-dom中的StaticRouter中已经帮咱们准备了一个钩子变量context。以下babel

//context从外界传入
<StaticRouter location={req.path} context={context}>
    <div>
        {renderRoutes(routes)}
    </div>
</StaticRouter>
复制代码

这就意味着在路由配置对象routes中的组件都能在服务端渲染的过程当中拿到这个context,并且这个context对于组件来讲,就至关于组件中的props.staticContext。而且,这个props.staticContext只会在服务端渲染的过程当中存在,而客户端渲染的时候不会被定义。这就让咱们可以经过这个变量来区分两种渲染环境啦。

如今,咱们须要在服务端的render函数执行以前,初始化context变量的值:

let context = { css: [] }
复制代码

咱们只须要在组件的componentWillMount生命周期中编写相应的逻辑便可:

componentWillMount() {
  //判断是否为服务端渲染环境
  if (this.props.staticContext) {
    this.props.staticContext.css.push(styles._getCss())
  }
}
复制代码

服务端的renderToString执行完成后,context的CSS如今已是一个有内容的数组,让咱们来获取其中的CSS代码:

//拼接代码
const cssStr = context.css.length ? context.css.join('\n') : '';
复制代码

如今挂载到页面:

//放到返回的html字符串里的header里面
<style>${cssStr}</style>
复制代码

网页源代码中看到了CSS代码,效果也没有问题。CSS渲染完成!

3、利用高阶组件优化代码

也许你已经发现,对于每个含有样式的组件,都须要在componentWillMount生命周期中执行彻底相同的逻辑,对于这些逻辑咱们是否可以把它封装起来,不用反复出现呢?

实际上是能够实现的。利用高阶组件就能够完成:

//根目录下建立withStyle.js文件
import React, { Component } from 'react';
//函数返回组件
//须要传入的第一个参数是须要装饰的组件
//第二个参数是styles对象
export default (DecoratedComponent, styles) => {
  return class NewComponent extends Component {
    componentWillMount() {
      //判断是否为服务端渲染过程
      if (this.props.staticContext) {
        this.props.staticContext.css.push(styles._getCss())
      }
    }
    render() {
      return <DecoratedComponent {...this.props} />
    }
  }
}
复制代码

而后让这个导出的函数包裹咱们的Home组件。

import WithStyle from '../../WithStyle';
//......
const exportHome = connect(mapStateToProps, mapDispatchToProps)(WithStyle(Home, styles));
export default exportHome;
复制代码

这样是否是简洁不少了呢?未来对于愈来愈多的组件,采用这种方式也是彻底能够的。

相关文章
相关标签/搜索