css modules & styled-component

为什么要用css modules?

  1. 代码只改动一处;
  2. 只应用在特定的组件,不影响别的地方;

默认情况下,所有类名和动画名都在本地范围内的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 的渲染速度。

  1. 代码结构更加清晰;
  2. 规范化
  3. 利于团队协作
BEM缺点:
  1. 命名过长问。

在 DOM 层级过深的情况下,会导致 CSS Class 冗长,难以阅读,所以使用 BEM 命名的层级一般不超过4层。

  1. 无法根治样式污染问题

在某些情况下,比如命名稍不注意导致重名,就可能会出现样式污染问题,使用第三方库的情况下也有可能会产生命名冲突,导致样式污染。

在构建CSS Modules时,有两个好处:

  1. 易解析,类似type.display这样的代码,对于开发者来说就像BEM-y的.font-size__serif--large。当BEM选择器变长时,可能更容易被理解。

  2. 本地作用域。我们在一个模块中运用.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 就不可能出现。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容