该篇为翻译文章:原文为:CSS Modules and React
该系列文章目录:
第一部分: CSS Modules是什么东西,我们为什么需要它?-译文(原文)
第二部分: 如何使用CSS Modules-译文(原文)
第三部分: CSS Modules和React (当前)
这篇文章是CSS Modules系列文章的最后一篇,我将演示用React
来做一个静态网站。当然,我们需要借助于Webpack
工具。这个例子中,我们会做两个页面,一个首页 ,一个关于页。我们将通过写几个React
组件来看一下CSS Modules在React
中是如何应用的。
在上一篇文章当中,我们说了如何使用Webpack
对各文件之间的依赖进行管理,以及如何在CSS
和HTML
当中生成惟一的class
名。这篇文章用到的东西除比较依赖上一篇中讲到的这些东西之外,当然还要会用React
。
上次写的Demo当中,我们在文章最后遗留了一个问题,就是我们是使用JS来生成的HTML
,这样我们的项目就是非常的乱。如果使用React,那么这个问题就不再是问题。
为了方便大家学习和练习,大家可以在我的GitHub上把 css-modules-react 项目下载下来,这个项目就是上篇文章没写完的东西,下载下来并把里面的npm
包安装好后,就可以在此基础上往下进行学习。
Webpack静态页面生成器
在Webpack
当中如果想生成静态的HTML
,我们需要安装一个插件static-site-generator-webpack-plugin
:
npm i -D static-site-generator-webpack-plugin
现在,我们需要在webpack.config.js
当中来配制插件并添加路由。路由的分配应该是这样,/
是首页,/about
是关于页面。插件正是根据路由来判断该创建哪个页面。
var StaticSiteGeneratorPlugin = require('static-site-generator-webpack-plugin');
var locals = {
routes: [
'/',
]
};
我们需要在不受后台代码影响的情况下来生成静态HTML文件,就需要用好static-site-generator-webpack-plugin
插件。
如何使用呢?现在我们需要来修改一下webpack.config.js
文件当中的module.exports
对象:
module.exports = {
entry: {
'main': './src/',
},
output: {
path: 'build',
filename: 'bundle.js',
libraryTarget: 'umd' // 这个属性非常非常重要!!
},
...
}
这里将libraryTarget
属性设置成umd
非常重要(umd
是Universal Module Definition的缩写,也就是’通用模块定义’的意思)。我们还设置了path
为/build
,这样所有生成的东西都会在这个文件夹下面。
然后,我们在webpack.config.js
文件当中添加并使用static-site-generator-webpack-plugin
插件,使用时传入路由以便插件根据路由进行生成:
plugins: [
new ExtractTextPlugin('styles.css'),
new StaticSiteGeneratorPlugin('main', locals.routes),
]
修改完成后的webpack.config.js
文件应该是这样的:
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: __dirname + '/src',
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract('css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'),
include: __dirname + '/src'
}
],
},
plugins: [
new StaticSiteGeneratorPlugin('main', locals.routes),
new ExtractTextPlugin("styles.css"),
]
};
接下来我们在src/index.js
当中添加代码:
// Exported static site renderer:
module.exports = function render(locals, callback) {
callback(null, '<html>Hello!</html>');
};
现在,我们只是想在首页打出Hello!
。而最终的页面将会比较复杂。
在package.json
中,我们设置了命令来运行webpack
:
npm start
在npm start
后,我们打开build
目录,应该会看到一个index.html
文件,并且里面有我们上面写下的内容。这说明static-site-generator-webpack-plugin
插件起作用了。
随后,我们来修改webpack.config.js
当中的routes
选项来让‘关于页面’都起作用:
var locals = {
routes: [
'/',
'/about'
]
};
这时再次运行npm start
命令,就会生成build/about/index.html
文件。但是,这个文件当中的内容也是Hello!
,也就是跟/build/index.html
文件一样,因为我们在两个文件当中传入了一样的内容。为了解决这个问题,我们需要用到router
,但首先我们需要进行React
的一些配制。
但在配制之前,我们需要将我们的路由拆分到另外一个文件,这样我们的项目看起来更有条理。所以,我们在./data.js
当中添加:
module.exports = {
routes: [
'/',
'/about'
]
}
在webpack.config.js
中,我们把locals
变量删掉,然后将data
导入:
var data = require('./data.js');
最后,我们需要修改static-site-generator-webpack-plugin
插件的配制 :
plugins: [
new ExtractTextPlugin('styles.css'),
new StaticSiteGeneratorPlugin('main', data.routes, data),
]
安装React
运行以下命令来进行安装react
和react-dom
等:
npm i -D react react-dom babel-preset-react
然后我们需要修改.babelrc
文件:
{
"presets": ["es2016", "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>
)
}
}
如果你对React
使用的JSX
的语法不熟悉的话,上面的代码中body
内的文字是一段注释。另外,上面的<head></head>
元素并不是HTML的标准元素,它是一个React组件,我们通过给它添加title
属性来达到传送数据的目的。其实这个title
在这里并不叫属性,它在React
当中叫做props
。
下面我们来看一下Head
组件,它是src/components/Head.js
文件:
import React from 'react'
export default class Head extends React.Component {
render() {
return (
<head>
<title>{this.props.title}</title>
</head>
)
}
}
现在在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
中的内容导入,然后使用React DOM
将其渲染出来。现在运行npm start
,然后再查看build/index.html
文件会发现React
已经将我们所有的HTML
内容都解析出来了。
但是,上面的内容依然会同时生成到首页和关于页。接下来我们通过使用路由来解决这个问题。
配制Router-路由
我们需要将路由和内容进行同步,关于页需要的是关于的内容,首页也有自己的内容,如果有其它页面,它们都会有自己的内容。我们需要react-router
来完成这些。
首先,我们需要安装react-router
:
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
)
我们需要多个页面,首页和关于页。首先,我们来看关于页src/templates/About.js
:
import React from 'react'
import Head from '../components/Head'
export default class About extends React.Component {
render() {
return (
<div>
<h1>About page</h1>
<p>This is an about page</p>
</div>
)
}
}
然后是首页src/templates/Home.js
:
import React from 'react'
import Head from '../components/Head'
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>
src/templates/Main.js
文件中包含了所有的必要HTML标签,比如<head>
。Home.js
和About.js
现在是React组件,可以作为内容而替换到Main.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
不是特别熟悉,那么可以查看 Brad Westfall的Leveling Up With React: React Router 。
因为我们删掉了index.js
文件,取而代之的是router.js
文件,所以我们需要回去修改webpack.config.js
文件当中entry
的值:
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}
最后将会被我们之前写的其它模板里的内容所替代。现在,我们再次运行npm start
将会生成build/index.html
和build/about/index.html
,每个文件的内容都是它们应该有的。
将CSS Modules加入其中
我们将来写一个按钮模块来进行说明。为了这一系列文章的完整性,我将继续使用上篇文章当中讲到的Webpack
的CSS loader
来进行演示,我这样说的原因是因为有一个替代品出现了。
现在,我们需要这样的一个目录结构:
/components
/Button
Button.js
styles.css
现在,我们需要在一个模板当中应用Button模块,在这之前我们先来创建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}
会通过找到style.css
当中的.red
类,然后,Webpack
会根据它生成一堆很长的class名。
现在,我们在src/components/Button/styles.css
文件当中添加一些简单的样式:
.red {
font-size: 25px;
background-color: red;
color: white;
}
最后,我们在模板当中运行Button组件,比如在src/templates/Home.js
当中使用:
import React from 'react'
import Head from '../components/Head'
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>
)
}
}
再次运行npm start
,我们就会看到最终效果啦!:
你可以将我已经完成的 React and CSS Modules下载下来进行查看学习。如果你发现了什么错误,欢迎到GitHub提出来。
结语
上面我们只是以一个Button模块为例,实际上,我们可以添加许多其它的组件,比如这样:
/components
Head.js
/Button
Button.js
styles.css
/Input
Input.js
style.css
/Title
Title.js
style.css
因此,即使我们在标题组件和按钮组件当中都有.large
这个class,它们也不会有任何冲突。当然,我们仍然可以使用一些全局的样式,比如我们可以创建一个src/globals.css
,然后在各组件当中引入它就行了。
那么是不是在我们以后的项目当中都要使用CSS Modules呢?当然不是,因为不同的项目的复杂程度不一样,要选择最合适的前端解决方案。没有好不好,只有合不合适。