为什么要用css modules?
- 代码只改动一处;
- 只应用在特定的组件,不影响别的地方;
默认情况下,所有类名和动画名都在本地范围内的css文件。可以通过webpack或者Browserify来改变类名和选择器名,以保证作用域。
这可以很好的解决css中的全局作用域问题。
// AccountInfo.less
.container {
padding: 20px 0 0 40px;
:global {
.ant-col {
font-weight: 700;
text-align: left;
&-5 > label {
margin-left: 7px;
}
}
.ant-form-item {
margin-bottom: 14px;
&-required::before,
&-required::after,
&-label > label::after {
display: none;
}
}
}
}
// 调用
import styles from './AccountInfo.less';
<div className={styles.container}></div>
编译后:
<div class="src-biz-AccountManage-AccountInfo-AccountInfo__container--1Fy_N"></div>
composes
关键字
// colors.css
.display {
color: red;
font-size: 30px;
line-height: 35px;
}
// element.css
.element {
composes: display from "./colors.css"
}
composes
关键字指明.normal
包含所有来自.common
的样式,类似于Sass里的@extends
。但是Sass通过重写CSS选择器来实现,而CSS模块通过选择哪个类输出到JavaScript进行了改变。
不再需要BEM
BEM 是 Block、Element、Modifier 的缩写,利用不同的区块,功能以及样式来给元素命名。这三个部分使用 与 连接(这里用两个而不是一个是为了留下用于块的命名)。命名约定如下:
.block {}
.block__element {}
.block--modifier {}
.block__element--modifier {}
BEM 的原则很简单:一个 Block 代表一个对象(一个人、一个登录表单、一个菜单);一个 Element 是一个块中作为特定功能的组件(一个帮助按钮、一个登录按钮、一个菜单项);一个 Modifier 是我们如何表示块或元素的不同变化(一个女人、一个带有隐藏标签的迷你登录框、 footer 中一个不同的菜单)。
BEM优点:
通过这种命名方式,HTML 层级结构一目了然,组件功能清晰明朗,而且不必使用过多的层级选择器,在一定程度上能够提高 CSS 的渲染速度。
- 代码结构更加清晰;
- 规范化
- 利于团队协作
BEM缺点:
- 命名过长问。
在 DOM 层级过深的情况下,会导致 CSS Class 冗长,难以阅读,所以使用 BEM 命名的层级一般不超过4层。
- 无法根治样式污染问题
在某些情况下,比如命名稍不注意导致重名,就可能会出现样式污染问题,使用第三方库的情况下也有可能会产生命名冲突,导致样式污染。
在构建CSS Modules时,有两个好处:
易解析,类似type.display这样的代码,对于开发者来说就像BEM-y的.font-size__serif--large。当BEM选择器变长时,可能更容易被理解。
本地作用域。我们在一个模块中运用.big中的font-size属性,也可以在另一个类中,去同时增加padding和font-size。因为本地作用域的不用,他们不会存在冲突,甚至可以在一个module中引入2个样式表。本地作用域的原因,构建工具会给class加上不同的前缀作区分。这可以很好的解决css样式中的特殊性问题。
styled-components
简易上手的几个Demo
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
/* Adapt the colors based on primary prop */
const Button = styled.button`
background: ${props => props.primary ? "palevioletred" : "white"};
color: ${props => props.primary ? "white" : "palevioletred"};
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
render(
<div>
<Button>Normal</Button>
<Button primary>Primary</Button>
</div>
)
/* Extending Styles */
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
/* A new component based on Button, but with some override styles */
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
vs 「css in js」的好处
相比于React的 css in js
, styled-components
有一个好处,就是对伪元素的定义。
const Thing = styled.button`
color: blue;
::before {
content: '🚀';
}
:hover {
color: red;
}
`
css in js
的写法,伪元素只能通过onMouseOver这样的js方法去控制,增加代码量。
Theming in styled-components
styled-components
提供Context这样的机制,有一套主题的上下文组件可供使用ThemeProvider
。
import {ThemeProvider} from 'styled-components';
const provider = (
<Provider store={store}>
<ThemeProvider theme={theme}>
<App />
</ThemeProvider>
</Provider>
);
ReactDOM.render(
provider,
document.getElementById('root')
);
styled-components
还提供React hook,可以结合useContext 来获取theme。
import { useContext } from 'react';
import { ThemeContext } from 'styled-components';
const MyComponent = () => {
const themeContext = useContext(ThemeContext);
console.log('Current theme: ', themeContext);
// ...
}
Issues with specificity
// MyComponent.js
const MyComponent = styled.div`background-color: green;`;
// my-component.css
.red-bg {
background-color: red;
}
// For some reason this component still has a green background,
// even though you're trying to override it with the "red-bg" class!
<MyComponent className="red-bg" />
上述例子中,styled-components
可以取得在全局class之上的优先权。因为styled-components
是在运行时加载到<head>标签的尾部。因此它的样式优先级要高于类选择器。
Tips: 如果想要强行覆盖组件内的样式,可以通过加大选择器的权重来达到覆盖样式的效果。
/* my-component.css */
.red-bg.red-bg {
background-color: red;
}
/* 或者 */
&& {
background-color: red;
}
Tagged Template Literals(标签模板字符串)
标签模板字符串,是ES6的新特性。他们让你可以自定义字符串差值规则,这也是我们可以使用styled-components
的原因。
// These are equivalent:
fn`some string here`
fn(['some string here'])
const aVar = 'good'
// These are equivalent:
fn`this is a ${aVar} day`
fn(['this is a ', ' day'], aVar)
本质上来说,调用函数 styled.button() 和使用 styled.button``几乎是一回事!但是当你传入参数时就会看到不同之处了。
我们先创建一个简单的函数用于探索:
const logArgs = (...args) => console.log(...args)
const favoriteFood = 'pizza'
logArgs(`I like ${favoriteFood}.`)
// -> I like pizza.
logArgs`I like ${favoriteFood}.`
// -> ["I like ", "."] "pizza"
可以看到,我们不再仅仅是得到了一个内容为 "I like pizza" 的字符串。
传入参数的第一位仍然是数组,不过现在有了 2 个元素:
- 位于插值左侧的 I like,作为数组第一个元素;
- 位于插值的右侧的 .,是数组第二个元素。
插值内容 favoriteFoor 成为了第二个传入参数。
如果我们插入不止一个变量,
const favoriteFood = 'pizza'
const favoriteDrink = 'obi'
logArgs`I like ${favoriteFood} and ${favoriteDrink}.`
// -> ["I like ", " and ", "."] "pizza" "obi"
Why is this useful?
对于 React 组件,你希望使用 props 值调整他们的样式。比如我们通过传入一个 primary 的 prop 值,让 <Button /> 组件变大一些,像这样:
<Button primary />
当你使用 styled-components 传入一个插值函数,我们其实就向组件传入了一个 props,使用它就可以进行组件样式调整。
const Button = styled.button`
font-size: ${props => props.primary ? '2em' : '1em'};
`;
现在如果 Button 是个基本按钮(primary),就有 2em 大小的字体,否则为 1em。
// font-size: 2em;
<Button primary />
// font-size: 1em;
<Button />
再看一下logArgs 函数。我们传入一个字符串。
logArgs(`Test ${() => console.log('test')}`)
// -> Test () => console.log('test')
logArgs`Test ${() => console.log('test')}`
// -> ["Test", ""] () => console.log('test')
我们在调用模版字符串时候,确实可以拿到函数了。为了测试,我们来创建一个新的函数来执行所有入参的函数:
const execFuncArgs = (...args) => args.forEach(arg => {
if (typeof arg === 'function') {
arg()
}
})
测试调用一波:
execFuncArgs('a', 'b')
// -> undefined
execFuncArgs(() => { console.log('this is a function') })
// -> "this is a function"
execFuncArgs('a', () => { console.log('another one') })
// -> "another one"
// 用模版字符串测试一波
execFuncArgs`Hi, ${() => { console.log('Executed!') }}`
// -> "Executed!"
execFuncArgs 的第二个参数其实就是一个函数,并且可以执行这个函数。
styled-components的底层就是这么实现的。在渲染时向插值函数中传入 props,我们就可以基于 props 来定制样式。
标签模板字符串使得 styled-components
API 得以实现,没有这个特性 styled-compnents
就不可能出现。