- 目前整个前端已经是组件化的天下:
- 而CSS的设计就不是为组件化而生的,所以在目前组件化的框架中都在需要一种合适的CSS解决方案。
- 在组件化中选择合适的CSS解决方案应该符合以下条件:
- 可以编写局部css:css具备自己的局部作用域,不会随意污染其他组件内的元素;
- 可以编写动态的css:可以获取当前组件的一些状态,根据状态的变化生成不同的css样式;
- 支持所有的css特性:伪类、动画、媒体查询等;
- 编写起来简洁方便、最好符合一贯的css风格特点;
- 等等...
- 事实上,css一直是React的痛点,也是被很多开发者吐槽、诟病的一个点。
- 在这一点上,Vue做的要确实要好于React:
- Vue通过在.vue文件中编写
<style><style>
标签来编写自己的样式; - 通过是否添加 scoped 属性来决定编写的样式是全局有效还是局部有效;
- 通过 lang 属性来设置你喜欢的 less、sass等预处理器;
- 通过内联样式风格的方式来根据最新状态设置和改变css;
- 等等...
- Vue在CSS上虽然不能称之为完美,但是已经足够简洁、自然、方便了,至少统一的样式风格不会出现多个开发人员、多个项目
采用不一样的样式风格。 - 相比而言,React官方并没有给出在React中统一的样式风格:
- 由此,从普通的css,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库;
- 大家一致在寻找最好的或者说最适合自己的CSS方案,但是到目前为止也没有统一的方案;
内联样式
- 内联样式是官方推荐的一种css样式的写法:
- style 接受一个采用小驼峰(原生中使用的是连接符)命名属性的 JavaScript 对象
<h2 style={{fontSize: "50px", color: "red"}}>我是标题</h2>
,而不是 CSS 字符串; - 并且可以引用state中的状态来设置相关的样式;
- style 中属性值需要用双引号或单引号引起来, 不然会被认为是变量
constructor(props) {
super(props);
this.state = {
color: "purple"
}
}
render() {
const pStyle = {
color: this.state.color,
textDecoration: "underline"
}
return (
<div>
<h2 style={{fontSize: "50px", color: "red"}}>我是标题</h2>
<p style={pStyle}>我是一段文字描述</p>
</div>
)
}
- 内联样式的优点:
- 1.内联样式, 样式之间不会有冲突
- 2.可以动态获取当前state中的状态
- 内联样式的缺点:
- 1.写法上都需要使用驼峰标识
- 2.某些样式没有提示,比如在标签内写 style 属性时有提示,但是如果像上面
pStyle
拿到外面写就不会有提示了 - 3.大量的样式, 代码混乱
- 4.某些样式无法编写(比如伪类/伪元素)
- 所以官方依然是希望内联合适和普通的css来结合编写;
普通的 CSS
- 普通的css我们通常会编写到一个单独的文件,之后再进行引入。
- 这样的编写方式和普通的网页开发中编写方式是一致的:
- 如果我们按照普通的网页标准去编写,那么也不会有太大的问题;
- 但是组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响;
- 但是普通的css都属于全局的css,样式之间会相互影响;
- 这种编写方式最大的问题是样式之间会相互层叠掉;
比如我们在 home 组件中有个 css 文件,写了个title 选择器的样式
.title {
font-size: 30px;
color: red;
}
然后在 profile 组件中有个 css 文件,也写了个 title 选择器的样式
.title {
color: yellow;
}
由于 css 是全局的, 所以这两个 title 选择器就会发生层叠,也就是在使用到 title 选择器的地方要么都显示红色,要么都显示黄色, 不会是我们希望的 home 中显示红色,profile 中显示黄色
css modules
- css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。
- 但是,如果在其他项目中使用个,那么我们需要自己来进行配置,比如配置webpack.config.js中的modules: true等。
- React的脚手架已经内置了css modules的配置:
- .css/.less/.scss 等样式文件都修改成.module.css/.module.less/.module.scss 等;
- 之后就可以引用并且进行使用了;
比如我们定义了一个 style.module.css 文件,文件内容如下:
.title {
font-size: 30px;
color: red;
}
.banner {
color: orange;
}
在 index.js 文件中的使用如下:
import React, { PureComponent } from 'react';
import homeStyle from './style.module.css'; // 这里我们按包导入的方式导入,在使用的时候都是用homeStyle.xxx 的方式使用
export default class Home extends PureComponent {
render() {
return (
<div className="home">
<h2 className={homeStyle.title}>标题</h2>
<div className={homeStyle.banner}>
<span>轮播图</span>
</div>
</div>
)
}
}
- css modules确实解决了局部作用域的问题,也是很多人喜欢在React中使用的一种方案。
- 但是这种方案也有自己的缺陷:
- 引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的;
- 所有的className都必须使用{style.className} 的形式来编写;
- 不方便动态来修改某些样式,依然需要使用内联样式的方式;
- 如果你觉得上面的缺陷还算OK,那么你在开发中完全可以选择使用css modules来编写,并且也是在React中很受欢迎的一种方式。
CSS in JS
- CSS-in-JS是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义。注意此功能并不是 React 的一部分,而是由第三方库提供。 React 对样式如何定义并没有明确态度。
- 在传统的前端开发中,我们通常会将结构(HTML)、样式(CSS)、逻辑(JavaScript)进行分离。
- React的思想中认为逻辑本身和UI是无法分离的,所以才会有了JSX的语法。
- 样式也是属于UI的一部分;
- 事实上CSS-in-JS的模式就是一种将样式(CSS)也写入到JavaScript中的方式,并且可以方便的使用JavaScript的状态;
- 所以React又被人称之为 All in JS;
- 当然,这种开发的方式也受到了很多的批评:
- 有一篇叫做 Stop using CSS in JavaScript for web development 专门
写了 css-in-js 不好的地方
- 批评声音虽然有,但是在我们看来很多优秀的CSS-in-JS的库依然非常强大、方便:
- CSS-in-JS通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修
改状态等等; - 虽然CSS预处理器也具备某些能力,但是获取动态状态依然是一个不好处理的点;
- 所以,目前可以说CSS-in-JS是React编写CSS最为受欢迎的一种解决方案;
- 目前比较流行的 CSS-in-JS 的库有
styled-components
、emotion
, 前者是社区最流行的 CSS-in-JS库
styled-components
- 安装命令:
yarn add styled-components
- 要学习styled-components首先要了解 ES6 的标签模板字符串,详见JavaScript中的 ES6标签模板字符串;
- 在styled component中,就是通过这种方式来解析模块字符串,最终生成我们想要的样式的
- styled-components的本质是通过函数的调用,最终创建出一个组件:
- 这个组件会被自动添加上一个不重复的class;
import React, { Component } from 'react';
import styled from 'styled-components'
const DivCmp = styled.div`
color:red;
`
export default class App extends Component {
render() {
return (
<DivCmp>
<h2>用户昵称</h2>
</DivCmp>
)
}
}
如上图 styled.div`...` 会给我返回一个 div 类型的标签,styeld 后面跟什么就会返回什么类型,如果是 h1, 返回的就是h1,如果是 span,就返回 span, 如果没有这种类型会不显示,
- styled-components会给该class添加相关的样式;如上图生成一个给 div 添加 class="sc-hKMtZM gt0Tnf" 的样式,这个字符串是唯一的,不会与别的重复
- props 属性穿透, props 可以被传递给 styled 组件,获取 props 需要通过${}传入一个插值函数,props 会作为该函数的参数,这种方式可以有效解决动态样式的问题
import React, { Component } from 'react';
import styled from 'styled-components'
const DivCmp = styled.div`
color:red;
font-size: ${props=>props.fontSize};
text-decoration: ${props=>props.textDecoration};
`
export default class App extends Component {
render() {
return (
<DivCmp fontSize="55px" textDecoration="underline">
<span>用户昵称</span>
</DivCmp>
)
}
}
- attrs 函数, 通过styled-components创建出来的 styled 组件都会有一个attrs函数, 该函数接收一个对象作为参数, 并返回 styled 组件。
- 参数中我们既可以放置组件的属性,也能放置组件的样式
import React, { Component } from 'react';
import styled from 'styled-components'
const HYInput = styled.input.attrs({
placeholder: "请输入文字", // 这里和HYInput组件中设置placeholder属性是一样的,
bColor: "red" // 这里和在HYInput组件中设置一个 bColor 属性是一样的,可以通过 props 来取
})`
background-color: lightblue;
border-color: ${props => props.bColor}; // 取出 attrs 中的 bColor
color: ${props => props.color};// 取出通过属性传过来的 color
`
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
color: "purple"
}
}
render() {
return (
<div>
{/* 从 state 中取出 color 赋值给 color 属性, 这样就能动态改变样式了 */}
<HYInput type="password" color={this.state.color}/>
</div>
)
}
}
- 继承, styled组件支持继承, 当很多组件有同样的样式时,我们就可以将同样的样式抽取成一个基类,然后不一样的样式部分再实现子类,通过子类继承基类来达到样式的服用。下面是继承的例子子组件通过styled(父组件名)的方式来继承
import React, { PureComponent } from 'react';
import styled from 'styled-components';
const HYButton = styled.button`
padding: 10px 20px;
border-color: red;
color: red;
`
const HYPrimaryButton = styled(HYButton)`
color: #fff;
background-color: green;
`
export default class App extends PureComponent {
render() {
return (
<HYButton>我是普通的按钮</HYButton>
<HYPrimaryButton>我是主要的按钮</HYPrimaryButton>
)
}
}
- styled 提供了 ThemeProvider 来共享主题
import React, { PureComponent } from 'react';
import styled, { ThemeProvider } from 'styled-components';
const TitleWrapper = styled.h2`
text-decoration: underline;
color: ${props => props.theme.themeColor};
font-size: ${props => props.theme.fontSize};
`
class Home extends PureComponent {
render() {
return (
<TitleWrapper>我是home的标题</TitleWrapper>
)
}
}
export default class App extends PureComponent {
render() {
return (
<ThemeProvider theme={{themeColor: "red", fontSize: "25px"}}>
<Home />
</ThemeProvider>
)
}
}
React 中动态修改class
- React在JSX给了我们开发者足够多的灵活性,你可以像编写JavaScript代码一样,通过一些逻辑来决定是否添加某些class:
<div>
<h2 className={"title " + (isActive ? "active" : "")}> 我是标题</h2>
<h2 className={["title ", (isActive ? "active" : "")].join(" ")}> 我是标题</h2>
</div>
- 我们也可以借助第三方库: classnames, 这是一个用于动态添加 className 的库
classNames('foo','bar'); // 'foo bar'
classNames('foo',{bar: true}); // 'foo bar'
classNames({'foo-bar': true}); // 'foo bar'
classNames({'foo-bar': false}); // ''
classNames({foo: true},{bar: true}); // 'foo bar'
classNames({foo: true, bar: true}); // 'foo bar'
classNames('foo', {bar: true, duck: false}, 'baz', {quux: true}); // 'foo bar baz quux'
classNames(null,false,'bar',undefined, 0,1,{baz: null},''); // 'bar 1'