React学习笔记 --- React中样式的基本使用

1,909 阅读10分钟

一、 组件化css需要的特点

  1. 可以编写局部css:css具备自己的具备作用域,不会随意污染其他组件内的原生;
  2. 可以编写动态的css:可以获取当前组件的一些状态,根据状态的变化生成不同的css样式;
  3. 支持所有的css特性:伪类、动画、媒体查询等;
  4. 编写起来简洁方便、最好符合一贯的css风格特点;
  5. 等等...

二、 React中的CSS

事实上,css一直是React的痛点,也是被很多开发者吐槽、诟病的一个点

在这一点上,Vue做的要确实要好于React:

  1. Vue通过在.vue文件中编写 <style><style>标签来编写自己的样式
  2. 通过是否添加 scoped 属性来决定编写的样式是全局有效还是局部有效
  3. 通过 lang 属性来设置你喜欢的 lesssass等预处理器
  4. 通过内联样式风格的方式来根据最新状态设置和改变css;
  5. 等等...

Vue在CSS上虽然不能称之为完美,但是已经足够简洁、自然、方便了,至少统一的样式风格不会出现多个开发人员、多个项目 采用不一样的样式风格。

相比而言,React官方并没有给出在React中统一的样式风格:

  1. 由此,从普通的css,到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库;
  2. 大家一致在寻找最好的或者说最适合自己的CSS方案,但是到目前为止也没有统一的方案;

2.1 内联样式

内联样式是官方推荐的一种css样式的写法:

  1. style 接受一个采用小驼峰命名属性的 JavaScript 对象,,而不是 CSS 字符串;
  2. 并且可以引用state中的状态来设置相关的样式;
import React, { PureComponent } from 'react'

export default class App extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      pStyle: {
        color: 'purple',
        marginTop: '10px'
      }
    }
  }

  render() {
    return (
      <div>
        {/*
          这里可以传递一个对象来表示内联样式

          注意: 其key值只可以接受小驼峰
                其value值必须为字符串
        */}
        <h3 style={{ fontSize: '30px', color: 'red' }}  >
          这是h3标签
        </h3>

        {/* 
          使用内联样式的写法就可以去state中读取变量作为p标签的内联样式
        */}
        <p style={ this.state.pStyle }>
          这是一个p标签
        </p>
      </div>
    )
  }
}

0po0w6.png

内联样式的优点:

  1. 内联样式, 样式之间不会有冲突
  2. 可以动态获取当前state中的状态

内联样式的缺点:

  1. 写法上都需要使用驼峰标识
  2. 某些样式没有提示
  3. 大量的样式, 代码混乱
  4. 某些样式无法编写(比如伪类/伪元素)

所以官方依然是希望内联合适和普通的css来结合编写

2.2 原生css

0pbljJ.png

其中Home是组件,其下index.js是入口文件,style.js是样式文件

App.js

import React, { PureComponent, Fragment } from 'react'


// 在引入的时候,直接引入文件夹即可
// 因为webpack在进行打包的时候,会默认将文件夹下的index.js作为默认的入口文件
import Home from './Home'

export default class App extends PureComponent {
  render() {
    return (
      <Fragment>
        {/*
          和vue不同的是,react的组件是只要引入以后就可以直接使用
          不需要使用componets来进行声明
        */}
        <Home />
      </Fragment>
    )
  }
}

index.js

import React, { PureComponent, Fragment } from 'react'

// 样式文件需要先引入以后,才可以继续使用
import './style.css'

class Home extends PureComponent {
  render() {
   return (
    <Fragment>
      <h3 className="title">这是Home组件</h3>
      <article className="content">这是Home组件中的内容</article>
    </Fragment>
   )
  }
}

export default Home

style.css

.title {
  color: red
}

.content {
  color: skyblue;
}

存在的问题

  1. 样式会被覆盖

    import React, { PureComponent, Fragment } from 'react'
    
    
    import Home from './Home'
    import Profile from './Profile'
    
    export default class App extends PureComponent {
      render() {
        return (
          <Fragment>
            <Home />
            <Profile />
          </Fragment>
        )
      }
    }
    

    此时 在Home组件的后边加上一个新的组件Profile,

    profile组件中的style.css的内容如下

    .title {
      color: pink;
    }
    
    .content {
      color: green;
    }
    

    此时,界面的效果如下

    0pLies.png

    可以看到profile中的样式会Home中的样式给覆盖

    • 尝试解决方案:

      #Profile .title {
        color: pink;
      }
      
      #Profile > .content {
        color: green;
      }
      

      给每一个样式前面加上唯一标识,或者使用直接子代选择器找到唯一的那个元素设置对应的样式

      但是 这样就会导致样式的书写变得极其复杂,

      而且这样编写很有可能会导致因为样式的权重问题导致样式的覆盖(例如 #Profile .title的权重比.Profile .title高),不利于维护

总结:

普通的css我们通常会编写到一个单独的文件,之后再进行引入

这样的编写方式和普通的网页开发中编写方式是一致的:

  • 如果我们按照普通的网页标准去编写,那么也不会有太大的问题;
  • 但是组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响;
  • 但是普通的css都属于全局的css,样式之间会相互影响;

2.3 css module

  • css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。

    • 但是,如果在其他项目中使用个,那么我们需要自己来进行配置,比如配置webpack.config.js中的modules: true等
  • React的脚手架已经内置了css modules的配置:

    • .css/.less/.scss 等样式文件都修改成 .module.css/.module.less/.module.scss 等;
    • 之后就可以引用并且进行使用了;
import React, { PureComponent, Fragment } from 'react'

// 使用这种方式的话,其是将样式文件进行引入,
// 使webpack在进行打包编译的时候,可以自动将这个文件进行编译
// 但是因为我们并没有定义变量去指向这个引入的模块,
// 所以我们是无法使用它的
// import './style.css'

// 将原本的css文件改名为module.css 书写方式依旧和之前的css的书写方式一致
import style from './style.module.css'

class Home extends PureComponent {
  render() {
   return (
    <Fragment>
      <h3 className={ style.title }>这是Home组件</h3>
      <article className={ style.content }>这是Home组件中的内容</article>
    </Fragment>
   )
  }
}

export default Home

0pON40.png

可以看到在编译后,其样式为style_样式名_唯一标识,这样所有的样式就是唯一的,不会重复的了

但是这种方案也有自己的缺陷:

  • 引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的;
  • 所有的className都必须使用{style.className} 的形式来编写;
  • 不方便动态来修改某些样式,依然需要使用内联样式的方式;
{/* 
  如果需要在css in module中使用state中的状态
  那么就必须将className和style进行组合使用
  这一点和vue是很像的
*/}
<h3 className={ style.title } style={{ color: this.state.color }}>这是Home组件</h3>

2.4 styled-components

CSS in JS

实际上,官方文档也有提到过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

优点

  1. CSS-in-JS通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修 改状态等等;
  2. 依然CSS预处理器也具备某些能力,但是获取动态状态依然是一个不好处理的点;
  3. 所以,目前可以说CSS-in-JS是React编写CSS最为受欢迎的一种解决方案;

常见的css in js的库

  • styled-components
  • emotion
  • glamorous

目前可以说styled-components依然是社区最流行的CSS-in-JS库,所以我们以styled-components进行练习

模板字符串

​ 模板字符串还有另外一种用法:标签模板字符串(Tagged Template Literals)

​ 正常情况下,我们都是通过 函数名() 方式来进行调用的,其实函数还有另外 一种调用方式:

function printStr(...args) {
    console.log(args)
}

printStr`Tagged Template Literals`

0iQknK.png

const name = 'Klaus'
const age = 23

function printStr(...args) {
    console.log(args)
}

printStr`My name is ${name}, age is ${age}`

0iQc4J.png

  • 模板字符串被拆分了;

  • 第一个元素是数组,是被模块字符串拆分的字符串组合;

  • 后面的元素是一个个模块字符串传入的内容

在styled component中,就是通过这种方式来解析模块字符串,最终生成 我们想要的样式的

styled-components的本质是通过函数的调 用,最终创建出一个组件:

  • 这个组件会被自动添加上一个不重复的 class;
  • styled-components会给该class添加相 关的样式
import React, { PureComponent } from 'react'

import styled from 'styled-components'

// 这里会生成一个div标签包裹的组件
// 在这个组件中会自动添加上唯一的标识类名
// 在styled组件中设置样式的时候,每一行的样式结尾的分号是不可以省略的
const HomeWrapper = styled.div`
  color: red;
  font-size: 20px;
`

class Home extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      color: 'skyblue'
    }
  }
  render() {
   return (
      <HomeWrapper>
      {/* 
        使用这个生成的组件进包裹后
        其下的子组件就可以继承并使用其对应的样式
      */}
       <h3>这是Home组件</h3>
       <article>这是Home组件中的内容</article>
      </HomeWrapper>
   )
  }
}

export default Home

0ilhGj.png

在styled-components中可以书写样式

注意:其本质是调用js函数,生成组件,所以如果引出到一个单独的文件中的话,其是js文件,不是css文件

const HomeWrapper = styled.div`
  color: red;
  font-size: 20px;

  h3 {
    color: skyblue;

    &:hover {
      color: green;
    }
  }

  article {
    &::after {
      content: 'after'
    }
  }
`

styled-components的几大特性

  1. props穿透
import React, { PureComponent, Fragment } from 'react'
import styled from 'styled-components'


const XFInput = styled.input`
  background-color: lightgray;
`

export default class Home extends PureComponent {
  render() {
    return (
      <Fragment>
        {/* 
          因为XFInput最后会被转换为带有样式的input标签
          所以这个type虽然设置在XFInput上,但是最后依旧会被添加在最后的input标签上
          这种就被叫做 props穿透
        */}
        <XFInput type="password" />
      </Fragment>
    )
  }
}
  1. attrs的基本使用
import React, { PureComponent, Fragment } from 'react'
import styled from 'styled-components'

/*
  attrs是一个函数,其参数是一个对象
  其返回值依旧是一个函数,所以可以继续使用标签模板字符串来进行调用
  也就是链式调用

  在写样式的时候, 其值可以传递一个箭头函数
  有一个参数props,就是attrs中的配置对象
*/
const XFInput = styled.input.attrs({
  color: 'lightgray'
})`
  background-color: ${ props => props.color };
`

export default class Home extends PureComponent {
  render() {
    return (
      <Fragment>
        <XFInput type="password" />
      </Fragment>
    )
  }
}
  1. 使用state作为props传递

    import React, { PureComponent, Fragment } from 'react'
    import styled from 'styled-components'
    
    
    /*
      这里的props的值来源有2个
        1. attrs函数中的配置对象
        2. 组件上传递过来的props
    
        tips: 如果组件上传递过来的props和attrs中的配置对象上的属性名重合
          attrs中的对应属性的属性值会将传入的props中的属性值给覆盖
    */
    const XFInput = styled.input.attrs({
      bgColor: 'lightgray'
    })`
      background-color: ${ props => props.bgColor };
      color: ${ props => props.color }
    `
    
    export default class Home extends PureComponent {
      constructor(props) {
        super(props)
    
        this.state = {
          color: 'red'
        }
      }
    
      render() {
        return (
          <Fragment>
            {/*
              将state中样式值传递给 scoped-compoents
              写在这里的样式属性一般都是动态的,会发生改变的 
              写在attrs中的属性一般是固定的,不会发生改变的
            */}
            <XFInput type="password" color={ this.state.color } />
          </Fragment>
        )
      }
    }
    

结合上述特性,就可以将样式动态的设置给react,并进行渲染

高阶使用

  1. 继承
import React, { PureComponent, Fragment } from 'react'
import styled from 'styled-components'

const WXFButton =  styled.button`
  padding: 8px 30px;
  border-radius: 5px;
`
/*
  在styled函数中传入一个参数,表示这个styled组件中的样式值
  继承自传入的哪一个styled组件中的相关样式

  tips: 继承之后就不需要在写button了
*/
const WXFPrimaryButton = styled(WXFButton)`
  color: #fff;
  background-color: skyblue;
`


export default class Home extends PureComponent {
  render() {
    return (
      <Fragment>
        <WXFPrimaryButton>
          primary-button
        </WXFPrimaryButton>
      </Fragment>
    )
  }
}
  1. theme的使用(设置需要全局使用或需要共享的全局样式属性值)

app.js

import React, { PureComponent } from 'react'
import { ThemeProvider } from 'styled-components'

import Home from './Home'
import Profile from './Profile'


export default class App extends PureComponent {
  render() {
    return (
      <ThemeProvider theme={{ color: 'skyblue', fontSize: '30px' }}>
        <Home />
        <Profile />
      </ThemeProvider>
    )
  }
}

Home.js

import React, { PureComponent, Fragment } from 'react'
import styled from 'styled-components'

const XFSpan = styled.span`
  color: ${ props => props.theme.color };
  font-size: ${ props => props.theme.fontSize };
`

export default class Home extends PureComponent {
  render() {
    return (
      <Fragment>
        <XFSpan>
          这是使用了theme的styled组件
        </XFSpan>
      </Fragment>
    )
  }
}

2.5 多类名共存

vue中添加class是一件非常简单的事情:

对象语法

0ixIO0.png

数组语法

0ixHTU.png

数组和对象共存

0ixLY4.png

React在JSX给了我们开发者足够多的灵活性,你可以像编写JavaScript代码一样,通过一些逻辑来决定是否添加某些class:

React原生的样式写法

<h1 className="foo bar active">这是H1标题</h1>
{/* foo后边必须要有一个空格 表示多个类之间的区分 */}

{/*
   后面的三目运算符必须加上()以表示他们是一个整体,
   如果没有加上,那么在编译的时候foo会被丢失
   最后编译出来的样式为'active' 是不包含'foo'的
*/}
<h1 className={'foo ' + (isActive ? 'active' : '')}>这是H1标题</h1>

{/*
  这里的逻辑运算符外也必须包上括号,
   如果没有包含的话,编译出来的样式依旧是没有foo的
*/}
<h1 className={'foo ' + (isActive && 'active')}>这是H1标题</h1>

<h1 className={ ['foo', (isActive ? 'active' : '')].join(' ') }>这是H1标题</h1>

使用第三方样式库(classnames)来帮助我们书写React样式

<h1 className={className('foo', 'bar', 'active')}>这是H1标题</h1>

<h1 className={className({'active': isActive }, 'foo')}>这是H1标题</h1>

{/*
   所有的值会被转换为false的值,都不会被渲染
   例如: null, undefined, 0
 */}
<h1 className={className({'active': isActive }, 'foo', null, undefined)}>这是H1标题</h1>

{/* 但是这里的10 会被转换为字符串10 作为一个有效的类名存在 */}
<h1 className={className({'active': isActive }, 'foo', 10)}>这是H1标题</h1>


<h1 className={className(['foo', 'active'])}>这是H1标题</h1>

<h1 className={className(['foo', {'active': isActive }])}>这是H1标题</h1>

上一篇高阶组件和组件补充