CSS Modules入門Ⅲ:與React協同
原文鏈接:CSS Modules and React
作者:ROBIN RENDLE
在最後這篇有關CSS Modules的教程里,我們會介紹如何運用Webpack開發一個React的靜態站點。這個靜態站點包含兩個頁面模板:主頁和關於我們頁面,通過一些React組件來實現。
- 第一部分:什麼是CSS Modules?為什麼要使用它?
- 第二部分:如何上手CSS Modules
- 第三部分:在React中使用CSS Modules
在上一篇教程中,我們通過一個簡單的示例項目學習了如何在Webpack中導入文件依賴,並在構建時生成唯一的類名。本篇教程中的示例需要在上一篇的基礎上進行,並且需要你稍稍有一點React的基礎。
在之前的demo中,我們的代碼還完全沒有框架結構,只是簡單地用JS的模板字元輸出。接下來我們會舉一些更實際的例子,我們會試著寫一些組件,並學習更多有關Webpack的知識。
假如你實在是懶得動手寫上一篇教程的代碼的話,可以在webpack-css-modules-example下載上篇教程的示例代碼再繼續下面的教程:
Webpack靜態站點生成器
為了讓Webpack幫我們生成靜態站點文件,我們需要安裝如下插件:
npm i -D static-site-generator-webpack-plugin
接下來我們需要在webpack.config.js中配置插件以及靜態站點的路由。路由就是網址結尾的路徑,例如/一般是主頁,/about是關於的介紹頁面。通過配置路由可以指定最終要渲染出的靜態文件。
var StaticSiteGeneratorPlugin = require("static-site-generator-webpack-plugin");var locals = { routes: [ "/", ]};
通過生成靜態頁面,我們可以免去伺服器端代碼的麻煩。我們使用的是StaticSiteGeneratorPlugin插件,它的文檔里提到:
本插件會根據你配置的路由在相應的目錄下生成index.html文件,通過webpack來渲染。
這聽起來可能還是很抽象,我們還是來看具體的例子吧,先修改webpack.config.js文件,在其中的module.exports里添加如下內容:
module.exports = { entry: { "main": "./src/", }, output: { path: "build", filename: "bundle.js", libraryTarget: "umd" // this is super important }, ...}
其中添加的libraryTarget配置項是靜態站點生成器插件正常工作所必須的。生成的文件同樣會保存在/build路徑下。
同樣我們還需要在webpack的plugins配置里添加StaticSiteGeneratorPlugin,並傳入路由作為參數:
plugins: [ new ExtractTextPlugin("styles.css"), new StaticSiteGeneratorPlugin("main", locals.routes),]
現在我們完整的配置文件看起來是這個樣子:
var path = require("path");var ExtractTextPlugin = require("extract-text-webpack-plugin");var StaticSiteGeneratorPlugin = require("static-site-generator-webpack-plugin");var locals = { routes: [ "/", ]};module.exports = { entry: "./src", output: { path: "build", filename: "bundle.js", libraryTarget: "umd" // this is super important }, module: { loaders: [{ test: /.js/, loader: "babel", include: path.resolve(__dirname, "src"), }, { test: /.css/, loader: ExtractTextPlugin.extract("css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]"), include: path.resolve(__dirname, "src"), }], }, plugins: [ new ExtractTextPlugin("styles.css"), new StaticSiteGeneratorPlugin("main", locals.routes), ]};
接下來,再將我們的src/index.js文件修改為:
// Exported static site renderer:module.exports = function render(locals, callback) { callback(null, "<html>Hello!</html>");};
我們先試著在主頁輸出Hello!來測試一下插件能否正常工作。
直接在命令行運行:
webpack
如果一切正常的話,你就可以在build文件夾里看到一個index.html文件,內容就和我們剛才要輸出的一樣。測試一切正常後,我們就開始下一步吧,先在webpack.config.js里修改路由:
var locals = { routes: [ "/", "/about" ]};
再次運行 webpack 命令後,我們就又能生成一個新文件build/about/index.html,內容也和剛才的build/index.html一樣,因為我們還沒有為兩個頁面配置不同的內容。
我們先將路由配置存放在一個獨立的文件里,在項目根目錄創建./data.js文件:
module.exports = { routes: [ "/", "/about" ]}
之後將webpack.config.js里的locals變數替換:
var data = require("./data.js");
最後再給插件傳入新的參數:
plugins: [ new ExtractTextPlugin("styles.css"), new StaticSiteGeneratorPlugin("main", data.routes, data),]
安裝React
我們需要寫出很多個模塊,然後把它們打包到一起。這也正是React能幫我們做到的,首先通過命令行安裝相關依賴:
npm i -D react react-dom babel-preset-react
然後修改我們的.babelrc文件:
{ "presets": ["es2015", "react"]}
新建文件夾/src/templates,之後在其中創建Main.js文件。這個文件用來存放頁面模板的通用部分:
import React from "react"import Head from "./Head"export default class Main extends React.Component { render() { return ( <html> <Head title="React and CSS Modules" /> <body> {/* This is where our content for various pages will go */} </body> </html> ) }}
有兩點需要注意的:首先,你可能還不太熟悉JSX的語法,這裡需要解釋的是body中的{}里的內容是注釋,<Head />不是原生的HTML標籤,而是代表React中的一個組件,我們可以給組件傳入一個title參數。要注意這並不代表HTML標籤的某個屬性,而是React中的props值。
現在再來創建/src/components/Head.js文件:
import React from "react"export default class Head extends React.Component { render() { return ( <head> <title>{this.props.title}</title> </head> ) }}
雖然我們也可以直接把所有的代碼全部都寫在Main.js里,可把不同的部件分開是有好處的,組件化的代碼可以很輕鬆地被複用,比方我們可以把它和另一個Footer.js模塊隨意組合使用。
然後在src/index.js文件中加入React的代碼:
import React from "react"import ReactDOMServer from "react-dom/server"import Main from "./templates/Main.js"module.exports = function render(locals, callback) { var html = ReactDOMServer.renderToStaticMarkup(React.createElement(Main, locals)) callback(null, "<!DOCTYPE html>" + html)}
上面這段代碼的意思是把所有Main.js里的內容通過ReactDOM渲染出來並返回。這時我們再運行一次webpack命令就能夠看到渲染的結果,但此時about頁面和主頁還是相同的內容,下一步我們要為它們配置不同的內容:
配置路由
我們需要為不同的路由設置不同的內容,About頁面和Home頁面都需要顯示各自的內容。我們可以使用react-router來實現這一需求:
本教程里使用的是2.0版本的react-router,和之前的版本可能會有出入。
首先還是安裝,react-router是獨立於react的一個庫:
npm i -D react-router
然後在/src文件夾下創建一個名為routes.js的文件:
import React from "react"import {Route, Redirect} from "react-router"import Main from "./templates/Main.js"import Home from "./templates/Home.js"import About from "./templates/About.js"module.exports = ( // Router code will go here)
之後為我們的About頁面創建單獨的模板:
import React from "react"export default class About extends React.Component { render() { return ( <div> <h1>About page</h1> <p>This is an about page</p> </div> ) }}
再然後是Home頁面的模板:
import React from "react"export default class Home extends React.Component { render() { return ( <div> <h1>Home page</h1> <p>This is a home page</p> </div> ) }}
現在,我們可以在routes.js中的module.exports里寫上:
<Route component={Main}> <Route path="/" component={Home}/> <Route path="/about" component={About}/></Route>
Main.js里包含網頁的框架內容(例如<head>等)。Home.js和About.js里包含了可以放在<body>元素中的內容。
接下來我們需要創建src/router.js文件。這部分實現替代了之前src/index.js中代碼的功能,你現在可以刪掉它了,然後在router.js中寫入:
import React from "react"import ReactDOM from "react-dom"import ReactDOMServer from "react-dom/server"import {Router, RouterContext, match, createMemoryHistory} from "react-router"import Routes from "./routes"import Main from "./templates/Main"module.exports = function(locals, callback){ const history = createMemoryHistory(); const location = history.createLocation(locals.path); return match({ routes: Routes, location: location }, function(error, redirectLocation, renderProps) { var html = ReactDOMServer.renderToStaticMarkup( <RouterContext {...renderProps} /> ); return callback(null, html); })}
如果你覺得一時間無法消化上面這段代碼,可以學習一下react-router入門教程。
因為我們已經刪掉了index.js文件,所以也要對webpack.config.js配置文件做出修改:
module.exports = { entry: "./src/router", // other stuff...}
最後我們只需要再調整一下src/templates/Main.js的代碼:
export default class Main extends React.Component { render() { return ( <html> <Head title="React and CSS Modules" /> <body> {this.props.children} </body> </html> ) }}
{this.props.children}用來傳遞About和Home兩個模板中的內容。我們再運行一遍webpack命令之後就能看到效果啦。
引入CSS Modules
接下來我們會一起實現一個Button模塊。在這篇教程里我們還會沿用之前Webpack的CSS loader,不過你也可以使用react-css-modules.
我們的Button模塊目錄結構大概是下面這個樣子:
/components /Button Button.js styles.css
首先創建src/components/Button/Button.js:
import React from "react"import btn from "./styles.css"export default class CoolButton extends React.Component { render() { return ( <button className={btn.red}>{this.props.text}</button> ) }}
我們在上一篇教程中已經了解到,這裡的{btn.red}代表styles.css中的.red的樣式。Webpack會在構建時自動生成相應的類名。
我們先創建src/components/Button/styles.css並添加一些簡單的樣式:
.red { font-size: 25px; background-color: red; color: white;}
最後,我們就可以在別的頁面模板中調用Button組件,例如在src/templates/Home.js中:
import React from "react"import CoolButton from "../components/Button/Button"export default class Home extends React.Component { render() { return ( <div> <h1>Home page</h1> <p>This is a home page</p> <CoolButton text="A super cool button" /> </div> ) }}
在Head.js里加入生成css文件的鏈接:
import React from "react"export default class Head extends React.Component { render() { return ( <head> <title>{this.props.title}</title> <link rel="stylesheet" href="./styles.css"/> </head> ) }}
最後再運行一下webpack命令,就可以看到生成的結果啦!
到這一步差不多就結束啦,你可以在React and CSS Modules找到本教程的源碼。
目前項目的代碼還有很多可以改進的地方,比如加入Browsersync,這樣我們就不用一遍又一遍地手動生成。還可以加入Sass, PostCSS一類的CSS預處理器。如果你喜歡探索可以親自嘗試一下,為了保證教程不至於太過複雜,我們就見好就收啦。
總結
截至目前我們達成了什麼呢?模塊化看起來好像把代碼分得支離破碎。可我們可以按照這樣的方式添加許多組件:
/components Head.js /Button Button.js styles.css /Input Input.js style.css /Title Title.js style.css
在這種方式下,假如Head模塊里有一個.large的樣式類,它不會與Button模塊里的同名.large類相互衝突。與此同時,也不影響我們添加全局的CSS樣式文件。
通過創建這個小demo,我們見識了許多React帶來的功能特性。你完全可以在這個demo的基礎上逐步開發出一個完整的靜態站點。
整個項目的開發工作流還是相當簡潔的,當然是否採用CSS Modules, React以及Webpack的這套解決方案,還要根據你的項目規模和形式而定。
加入一個項目組有很多人都在貢獻CSS代碼的話,採用CSS Modules可以很好地防止代碼之間相互的干擾。但對於UI設計師來說,這可能就為他們接觸CSS代碼造成了更多的障礙,因為在這種情況下必須掌握JavaScript才行。同樣CSS Modules也需要很多的工具依賴才能實現。
CSS Modules只是提供了一個前端代碼組織,命名衝突的解決方案。並不代表唯一的真理,只是在解決某些具體問題時可以採用它。
歡迎在評論區交流討論。
號外!號外!
從零學習前端開發有自己的QQ群啦!
群號:591950591
點擊鏈接加入群【從零學習前端開發】:加群鏈接
驗證問題:想學啊你?回答:我教你啊
我會在群內不定期分享各類前端學習資源,也會偶爾布置實踐小作業供大家一同交流學習襖,以後還會陸續開展各類活動~
推薦閱讀:
※基於 JSX 的動態數據綁定
※React 從青銅到王者系列教程之倔強青銅篇
※如何評價React VR Project?
※為什麼 GUI 編程中,Web 平台的技術革新特別火爆,而 Android 和 iOS 沒什麼成果?
※知乎是否會停止使用React?