react中的css

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文件进行配置:
react中的css
如果是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名称全局唯一:
react中的css

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;

结果如图:
react中的css
这就是一个简单的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两种方案。

上一篇:Vue 和 React 深度对比


下一篇:React组件通信方式