動態

詳情 返回 返回

React服務端渲染之路07——添加CSS樣式 - 動態 詳情

所有源代碼、文檔和圖片都在 github 的倉庫裏,點擊進入倉庫

相關閲讀
  • React服務端渲染之路01——項目基礎架構搭建
  • React服務端渲染之路02——最簡單的服務端渲染
  • React服務端渲染之路03——路由
  • React服務端渲染之路04——redux-01
  • React服務端渲染之路05——redux-02
  • React服務端渲染之路06——優化
  • React服務端渲染之路07——添加CSS樣式
  • React服務端渲染之路08——404和重定向
  • React服務端渲染之路09——SEO優化

1. CSS 樣式的添加

  • 我們之前配置的 webpack,僅僅是配置了 js,對於 css 及 css 預處理器都沒有配置,所以我們需要配置一下 css,我們統一採用 sass 預處理器

1.1 webpack.client.js 的配置

  • 這裏就要問了,為什麼不把 css 配置到 webpack.base.js 裏呢,因為服務端不識別 css 代碼,所以我們不能簡單的把 css 配置信息寫在 webpack.base.js 裏
  • 下載依賴 npm i node-sass sass-loader -D
  • 修改 webpack.client.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader',
          {
            loader: 'css-loader',
            options: {
              importLoaders: 1,
              modules: true,
              localIdentName: '[name]_[local]_[hash:base64:5]'
            }
          }
        ]
      }
    ]
  }
}

1.2 webpack.server.js 的配置

  • 服務器端不能直接識別 css 資源,但是我們還是需要服務器端能夠識別 css 資源,所以我們使用一個庫,專門用來為服務端識別 css,這個庫是 isomorphic-style-loader
module.exports = {
  module: {
    rules: [
      {
        test: /\.css?$/,
        use: ['isomorphic-style-loader', {
          loader: "css-loader",
          options: {
            importLoaders: 1,
            modules: true,
            localIdentName: '[name]_[local]_[hash:base64:5]'
          }
        }]
      }
    ]
  }
}

1.3 組件使用 css 樣式

  • 組件使用 css 樣式的時候,可以像以前一樣,直接引入 css 文件,把樣式作用在對應的 DOM 標籤上
  • /src/containers/Home/index.css
/**
 * /src/containers/Home/index.css
 */
.wrapper {
  background: orange;
}

.title {
  color: red;
  font-size: 26px;
}
  • /src/containers/Home/index.js
// /src/containers/Home/index.js
import styles from './index.css';

class Home extends Component {

  render() {
    return (
      <div className={styles.wrapper}>
        <h2 className={styles.title}>HELLO, HOME PAGE</h2>
      </div>
    );
  }
}
  • 直接這樣使用,我們就可以在頁面上看到對應的 css 樣式
  • 但是這樣有兩個問題

    • 第一個問題是,瀏覽器必須要開啓 js,如果不開啓 js,那麼樣式是不生效的
    • 第二個問題是,當我們的頁面刷新頻率過快,並且不使用緩存,那麼頁面有非常明顯的抖動
  • 這兩個問題對用户來説,體驗非常不好,所以我們進一步改進

1.4 把樣式注入到服務端的 HTML 模板中

  • 實際上,上面我們用的方式是把 css 寫在了 js 裏邊,如果我們查看頁面的源代碼,我們只能在頁面上找到 DOM 元素的類名,但是我們找不到任何的 css 代碼,因為全部都在 /client.js 裏,所以我們要把 css 從 js 裏拿出來,寫在 HTML 頁面上
  • 當我們引入一個 css 文件的時候,引入的模塊就自帶一些屬性,這些屬性是 webpack 所提供的,我們可以看一下
import styles from './index.css';

console.log(styles);

{ wrapper: 'index_wrapper_2wP7c',
  title: 'index_title_39dQ8',
  _getContent: [Function],
  _getCss: [Function],
  _insertCss: [Function]
}

--------------------------------------------------

console.log(styles._getContent());

[
  [ './node_modules/_css-loader@2.1.1@css-loader/dist/cjs.js?!./src/containers/Home/index.css',
    '.index_wrapper_2wP7c {\r\n  background: orange;\r\n}\r\n\r\n.index_title_39dQ8 {\r\n  color: red;\
\n  font-size: 26px;\r\n}\r\n', '' ],
  toString: [Function: toString],
  i: [Function],
  locals: { wrapper: 'index_wrapper_2wP7c',
    title: 'index_title_39dQ8',
    _getContent: [Function],
    _getCss: [Function],
    _insertCss: [Function] } ]

--------------------------------------------------

console.log(styles._getCss());

.index_wrapper_2wP7c {
  background: orange;
}

.index_title_39dQ8 {
  color: red;
  font-size: 26px;
}
  • 我們依次在控制枱輸出 styles 的一些屬性,我們可以查看到, 我們定義的類名,已經被進行了轉換,而且我們定義的樣式,全部都在 styles._getCss() 裏
  • 所以,我們可以把類名賦值需要使用的 DOM 元素,css 樣式的內容,傳遞給服務端,讓服務端直接把樣式載入到 HTML 模板中
  • 但是該怎麼操作呢?前邊我們説到了 StaticRouter 靜態路由有一個 context 的屬性,這個屬性是用來前後端進行傳遞數據的,所以我們可以把數據通過 context 傳遞
  • 我們直接在 Home 組件裏輸出一下 this.props,我們會發現有一個非常有意思的現象,就是在瀏覽器的控制枱,輸出的 props.staticContext 的值是 undefined,但是在服務端的控制枱,輸出的是一個對象,裏邊的 csses 的屬性值是我們之前定義的 css 內容
  • 這是因為,staticContext 雖然能夠傳值,但是傳值僅僅存在與服務端和組件之間,並不在客户端和組件之間,我們我們在服務端就可以拿到 css 的樣式
  • 拿到 css 樣式後,直接把 css 內容作為字符串,添加到 HTML 模板的 style 標籤裏,就可以了
  • 注意: context.csses 必須為數組類型,把每一個組件的樣式作為一個元素 push 到數組中,這樣每一個組件的 css 樣式都可以生效,但是,如果我們直接把 css 的樣式賦值給 context.csses ,那麼樣式將會被覆蓋,這個覆蓋不是樣式的覆蓋,而是 js 值的覆蓋,最先渲染的組件的 css 的樣式被後來渲染的組件的 css 樣式所覆蓋,這樣是不正確的,所以一定要使用數組,而不是直接賦值
  • /src/containers/Home/index.js
componentWillMount() {
  let staticContext = this.props.staticContext;
  if (staticContext) {
    if (staticContext) {
      staticContext.csses.push(styles._getCss());
    }
  }
}
  • /src/server/render.js
export default (req, res) => {

  let context = {
    csses: []
  };

  Promise.all(promises).then(() => {
    let domContent = renderToString(
      <Provider store={store}>
        <StaticRouter context={context} location={req.path}>
          {
            renderRoutes(routes)
          }
        </StaticRouter>
      </Provider>
    );

    let cssStr = context.csses.length ? context.csses.join('\n') : '';

    let html = `
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
  <link href="https://cdn.bootcss.com/twitter-bootstrap/3.3.7/css/bootstrap.css" rel="stylesheet">
  <title>react-ssr</title>
  <style>${cssStr}</style>
</head>
<body>
<div id="root">${domContent}</div>
<script>
  window.context = {
    state: ${JSON.stringify(store.getState())}
  }
</script>
<script src="/client.js"></script>
</body>
</html>
`;

    res.send(html);
  });
};
  • 這樣,快速刷新瀏覽器,頁面也不會抖動,禁用掉 js ,頁面樣式依然存在
  • 我們可以查看一下頁面源代碼,我們可以發現,css 的源代碼,就在 style 標籤裏

2. 封裝樣式組件

  • 如果我們有多個頁面,每一個頁面都有自己的 css 樣式,那麼我們就要在每一個組件裏都要寫 componmentWillMount 鈎子函數,在這個函數裏把 css 樣式傳遞到 staticContext 裏,這樣明顯不是一個好的辦法,所以我們可以封裝一個高階組件
  • 我們封裝一個 WithStyle 的高階組價,把原組件和樣式作為參數傳遞給高階組件
import React, { Component } from 'react';

export default (DecoratedComponent, styles) => {
  return class NewComponent extends Component {
    componentWillMount() {
      if (this.props.staticContext) {
        this.props.staticContext.csses.push(styles._getCss());
      }
    }

    render() {
      return (<DecoratedComponent {...this.props} />);
    }

  };
};
  • 在 Home 組件裏使用這個高階組件
import WithStyle from '../../withStyle';

export default connect(mapStateToProps, mapDispatchToProps)(WithStyle(Home, styles));
  • 這樣,我們就可以把樣式相關的功能作為高階組件封裝起來,提高代碼的複用率

3. 優化組件

  • 我們在 Home 組件裏定義了一個靜態方法 loadData,這個方法是在 Home 組件下的,但是我們使用了 WithStyle 高階組件對 Home 組件進行了包裝,那麼我們在導出的組件,就不再是 Home 組件了,這樣會有一些潛在的問題,就是導出的組件沒有 loadData 方法,那麼我們在使用的時候就會報錯,所以我們可以做一些改進
  • 我們重新定義個 ExportHome 的變量,這個變量是各個高階組件包裝後的返回值,在 ExportHome 組件上定義 loadData 方法,這樣就可以保證導出的組件一定有 loadData 方法
  • 之所以我們之前使用 connect 包裝之後沒有報錯,是因為 connect 自動幫我們做了轉換,已經把 loadData 方法掛載到導出的對象上了,所以沒有報錯
const ExportHome = connect(mapStateToProps, mapDispatchToProps)(WithStyle(Home, styles));

ExportHome.loadData = store => store.dispatch(UserActions.getSchoolList());

export default ExportHome;
  • 所以,這是一個需要注意的點
相關閲讀
  • React服務端渲染之路01——項目基礎架構搭建
  • React服務端渲染之路02——最簡單的服務端渲染
  • React服務端渲染之路03——路由
  • React服務端渲染之路04——redux-01
  • React服務端渲染之路05——redux-02
  • React服務端渲染之路06——優化
  • React服務端渲染之路07——添加CSS樣式
  • React服務端渲染之路08——404和重定向
  • React服務端渲染之路09——SEO優化
user avatar grewer 頭像 yinzhixiaxue 頭像 zhipanyun 頭像 littlelyon 頭像 anchen_5c17815319fb5 頭像 u_17443142 頭像 hyfhao 頭像 congjunhua 頭像 DolphinScheduler 頭像 54r9rxzy 頭像 yanyue404 頭像 heath_learning 頭像
點贊 48 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.