css一直是react的痛点,也是被很多开发者诟病的一个点。
在组件化中选择合适的CSS解决方案应该符合以下条件:
- 可以编写局部css:css具备自己的具备作用域,不会随意污染其他组件内的原生;
- 可以编写动态的css:可以获取当前组件的一些状态,根据状态的变化生成不同的css样式;
- 支持所有的css特性:伪类、动画、媒体查询等;
- 编写起来简洁方便、最好符合一贯的css风格特点;
- 等等…
在这一点上,Vue做的要远远好于React:
- Vue通过在.vue文件中编写
Vue在CSS上虽然不能称之为完美,但是已经足够简洁、自然、方便了,至少统一的样式风格不会出现多个开发人员、多个项目采用不一样的样式风格。
相比而言,React官方并没有给出在React中统一的样式风格。由此,从普通的css,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库。大家一直在寻找最好的或者说最适合自己的CSS方案,但是到目前为止也没有统一的方案。
在这篇文章中,我会介绍挑选四种解决方案来介绍:
- 方案一:内联样式的写法;
- 方案二:普通的css写法;
- 方案三:css modules;
- 方案四:css in js(styled-components)
方案一:内联样式的写法
内联样式是官方推荐的一种css样式的写法:
- style 接受一个采用小驼峰命名属性的 JavaScript 对象,,而不是 CSS 字符串;
- 并且可以引用state中的状态来设置相关的样式;
export default class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
titleColor: "red"
}
}
render() {
return (
<div>
<h2 style={{color: this.state.titleColor, fontSize: "20px"}}>我是App标题</h2>
<p style={{color: "green", textDecoration: "underline"}}>我是一段文字描述</p>
</div>
)
}
}
内联样式的优点:
- 内联样式, 样式之间不会有冲突
- 可以动态获取当前state中的状态
内联样式的缺点:
- 写法上都需要使用驼峰标识
- 某些样式没有提示
- 大量的样式, 代码混乱
- 某些样式无法编写(比如伪类/伪元素)
推荐使用内联样式加css modules的写法
方案二:普通的css写法
普通的css我们通常会编写到一个单独的文件。
App.js中编写React逻辑代码:
import React, { PureComponent } from 'react';
import Home from './Home';
import './App.css';
export default class App extends PureComponent {
render() {
return (
<div className="app">
<h2 className="title">我是App的标题</h2>
<p className="desc">我是App中的一段文字描述</p>
<Home/>
</div>
)
}
}
App.css中编写React样式代码:
.title {
color: red;
font-size: 20px;
}
.desc {
color: green;
text-decoration: underline;
}
但是普通的css都属于全局的css,样式之间会相互影响,不推荐。
方案三:css modules
css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。
使用了webpack的话,打开config目录下的webpack.config.js文件进行配置:
如果是taro小程序框架,需要进行如下配置:
weapp: {
module: {
postcss: {
// css modules 功能开关与相关配置
cssModules: {
enable: true, // 默认为 false,如需使用 css modules 功能,则设为 true
config: {
namingPattern: 'module', // 转换模式,取值为 global/module,下文详细说明
generateScopedName: '[name]__[local]___[hash:base64:5]'
}
}
}
}
}
}
设置了之后.css/.less/.scss文件都要修改成.module.css/.module.less/.module.scss 等
这种方式会使最终生成的class名称全局唯一:
css modules确实解决了局部作用域的问题,但是这种方案也有自己的缺陷:
- 引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的;
- 所有的className都必须使用{style.className} 的形式来编写;
- 不方便动态来修改某些样式,依然需要使用内联样式的方式;
另外,如果想要修改某个ui组件的全局样式,则需要通过:global()
来设置
在全局样式前面可以加上属于哪个类名之下,这样可以提高权重,避免覆盖组件类名的样式:
.map :global(.am-navbar){
margin-top: -45px;
}
方案四:css in js(styled-components)
实际上,官方文档也有提到过CSS in JS这种方案:
- “CSS-in-JS” 是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义;
- 注意此功能并不是 React 的一部分,而是由第三方库提供。 React对样式如何定义并没有明确态度;
目前比较流行的CSS-in-JS的库有哪些呢?
- styled-components
- emotion
- glamorous
目前可以说styled-components依然是社区最流行的CSS-in-JS库,所以我们以styled-components的讲解为主;
1.安装
yarn add styled-components
2.标签模版字符串
ES6中增加了模板字符串的语法,这个对于很多人来说都会使用。
但是模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals)。
来看一个普通的JavaScript的函数:
function foo(...args) {
console.log(args);
}
foo("Hello World");
正常情况下,我们都是通过 函数名() 方式来进行调用的,其实函数还有另外一种调用方式:
foo`Hello World`; // [["Hello World"]]
如果我们在调用的时候插入其他的变量,那么模版字符串就会被拆分,第一个元素是数组,是被模版字符串拆分的字符串组合;后面的元素是一个个模板字符串传入的内容。
foo`Hello ${name}`; // [["Hello ", ""], "kobe"];
在styled component中,就是通过这种方式来解析模块字符串,最终生成我们想要的样式的。
看一个例子:
import React, { useEffect, useRef } from 'react';
import styled from 'styled-components';
const Wrapper = styled.div`
.text {
color: #666;
border: 1px solid #ccc;
outline: none;
&.active {
color: rgb(47, 13, 238) ;
border-color: rgb(185, 20, 20);
}
}
`;
const Test1 = () => {
const refInput = useRef();
const refSubmit = useRef();
useEffect(() => {
const { current } = refInput;
const handleFocus = () => {
current.classList.add('active');
}
const handleBlur = () => {
current.classList.remove('active');
current.value !== ''
? refSubmit.current.removeAttribute('disabled')
: refSubmit.current.setAttribute('disabled', true);
}
current.addEventListener('focus', handleFocus);
current.addEventListener('blur', handleBlur);
return () => {
current.removeEventListener('focus', handleFocus);
current.removeEventListener('blur', handleBlur);
}
},[]);
return (
<Wrapper>
<p>
<input
className='text'
type="text"
ref={refInput}
defaultValue="Focus me"
/>
</p>
<p>
<input
ref={refSubmit}
type="submit"
value="Submit"
/>
</p>
</Wrapper>
);
}
export default Test1;
结果如图:
这就是一个简单的input框在focus时改变样式的例子。
3.用法
styled-components支持类似于css预处理器一样的样式嵌套:
- 支持直接子代选择器或后代选择器,并且直接编写样式;
- 可以通过&符号获取当前元素;
- 直接伪类选择器、伪元素等;
(1)props属性
const HomeWrapper = styled.div`
color: ${props => props.color};
}
//使用
<HomeWrapper color="blue">
</HomeWrapper>
使用props需要通过${}
传入一个插值函数,props会作为该函数的参数。这种方式可以有效的解决动态样式的问题
(2)attrs属性
标签属性如input的placeholder,a标签的href等,style-components提供了属性attrs
export const NavSearch = styled.input.attrs({
placeholder: '搜索',
type: 'text'
}) `
width: 160px;
height: 38px;
margin-top: 9px;
padding: 0 40px 0 20px;
box-sizing: border-box;
background-color: #eee;
outline: none;
border: none;
border-radius: 19px;
color: #666;
&::placeholder {
color: #999;
}
&.focused {
width: 240px;
}
`;
attrs里是一个对象,如果需要多个属性,以对象的形式添加即可。
styled-components中还有其他一些用法,如样式继承,主题定制等等,但是我觉得用方案三中的css modules更方便一点。
学习styled-components主要是为了解决两个问题:1.动态样式 2.动态添加动画
动态添加动画这一部分的实践请看下面的博文:
这里主要使用了style-components和react-transition-group两种方案。