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;
複製代碼

這樣是否是簡潔不少了呢?未來對於愈來愈多的組件,採用這種方式也是徹底能夠的。

相關文章
相關標籤/搜索