仍是以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
首先,来安装一个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代码后放到哪里呢?其实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>
复制代码
也许你已经发现,对于每个含有样式的组件,都须要在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;
复制代码
这样是否是简洁不少了呢?未来对于愈来愈多的组件,采用这种方式也是彻底能够的。