仍是以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代碼的服務端的處理。首先,來安裝一個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渲染完成!
也許你已經發現,對於每個含有樣式的組件,都須要在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;
複製代碼
這樣是否是簡潔不少了呢?未來對於愈來愈多的組件,採用這種方式也是徹底能夠的。