React学习笔记04-React中的样式

young 862 2022-09-30

组件化下的CSS

现在整个前端都已经是组件化的了,而CSS的设计就不是为组件化而生的,所以目前在组件化的框架中,都需要一种合适的CSS解决方法

在组件化中选择合适的CSS解决方案应该满足以下条件:

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

React中的CSS

CSS一直是React的痛点,这这一点上,Vue做的要好于React

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

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

相比而言,React官方并没有给出在React中统一的样式风格,由此,从普通的css到css modules,再到css in js,有几十种不同的解决方案,上百个不同的库

内联样式

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

  • style接收一个采用小驼峰命名属性的JavaScript对象,而不是CSS字符串
  • 并且可以引用state中的状态来设置相关的样式

内联样式的有点:

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

内联样式的缺点

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

官方已让是希望内样式和普通CSS结合来编写

import React, { PureComponent } from 'react'

export default class App extends PureComponent {
  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>
    )
  }
}

普通的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.js中的modules:true

React的脚手架已经内置的了css modules的配置:.css/.less/.scss等样式文件都被修改成.module.css/.module.less/.module.scss等,之后就可以引用并且直接使用了

css modules确实解决了局部作用域的问题,也是很多人喜欢在React中使用的一种方法

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

  • 引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的
  • 所有className都必须使用{style.className}的形式来编写
  • 不方便动态来修改某些样式,依然需要使用内联样式的方式

CSS in JS

官方文档中也有提到过CSS in JS这种方案

CSS-in-JS是指一种模式,其中CSS由JavaScript生成而不是在外部文件中定义,此功能并不是React的已不是,而是由第三方库提供。React对样式如何定义没有明确态度。

在传统的前端开发中,我们通常会将HTML,CSS,JavaScript进行分离,但是在React的思想中,认为逻辑本身和UI是无法分离的,所以才有了JSX语法。样式也是属于UI的一部分,事实上CSS-in-JS的模式就是一种将样式也写入到JavaScript中的方式,并且可以方便的使用JavaScript的状态,所以React有人称之为All in JS

认识styled-components

CSS-in-JS通过JavaScript来为CSS赋予一些能力,包括类似于CSS预处理器一样的样式嵌套、函数定义、逻辑复用、动态修改状态等。

CSS预处理器也具备某些能力,但是获取动态状态依然是一个不好处理的点

所以目前可以说CSS-in-JS是React编写CSS最为手欢迎的一中解决方案

目前比较流行的CSS-in-JS的库

  • styled-components
  • emotion
  • glamorous

目前可以说styled-components依然是社区最流行的CSS-in-JS库

yarn add styled-components

ES6标签模板

ES6中增加了模板字符串的语法,模板字符串的的另一种用法是标签模板字符串(Tagged Template Literals)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <script>
    // 1.模板字符串的基本使用
    const name = "why";
    const age = 18;
    const message = `my name is ${name}`;

    // 2.标签模板字符串: 可以通过模板字符串的方式对一个函数进行调用
    function test(...args) {
      console.log(args);
    }

    // test("aaa", "ccc");

    // test`aaaa`;
    test`my name is ${name}, age is ${age}`;

    test`
      font-size: 15px;
      color: red;
    `
  </script>
</body>
</html>

正常情况下,我们同时通过 函数名()的方式来进行的调用的,其实函数还有另一种调用方式 方法名``

如果我们在调用的时候插入了其他的变量

  • 模板字符串被拆分了
  • 第一个元素是数组,是被模块字符串拆分的字符串组合
  • 后面的一个元素是一个个模块字符串传入的内容

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

styled的基本使用

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

  • 这个组件会被自动添加上一个不重复的class
  • style-components会给该class添加相关的样式

另外,它支持类似于CSS预处理器一样的样式嵌套

  • 支持直接子代选择器或者后代选择器,并且直接编写样式
  • 可以通过&顾浩获取当前元素
  • 直接伪类选择器、伪元素等

import styled from 'styled-components';

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

  .banner {
    background-color: blue;

    span {
      color: #fff;

      &.active {
        color: red;
      }

      &:hover {
        color: green;
      }

      &::after {
        content: "aaa"
      }
    }

    /* .active {
      color: #f00;
    } */
  }
`

export const TitleWrapper = styled.h2`
  text-decoration: underline;
  color: ${props => props.theme.themeColor};
  font-size: ${props => props.theme.fontSize};
`
import React, { PureComponent } from 'react';

import { 
  HomeWrapper,
  TitleWrapper
} from "./style";

export default class Home extends PureComponent {
  render() {
    return (
      <HomeWrapper>
        <TitleWrapper>我是home的标题</TitleWrapper>
        <div className="banner">
          <span>轮播图1</span>
          <span className="active">轮播图2</span>
          <span>轮播图3</span>
          <span>轮播图4</span>
        </div>
      </HomeWrapper>
    )
  }
}

props、attrs属性

props可以穿透

props可以传递给styled组件

  • 获取props需要通过${}传入一个插值函数,props会作为该函数的参数
  • 这种方式可以有效的解决动态样式的问题
  • 添加attrs属性
import React, { PureComponent } from 'react';
import styled from 'styled-components';

/**
 * 特点:
 *  1.props穿透
 *  2.attrs的使用
 *  3.传入state作为props属性
 */

const HYInput = styled.input.attrs({
  placeholder: "coderwhy",
  bColor: "red"
})`
  background-color: lightblue;
  border-color: ${props => props.bColor};
  color: ${props => props.color};
`

export default class Profile extends PureComponent {
  constructor(props) {
    super(props);

    this.state = {
      color: "purple"
    }
  }

  render() {
    return (
      <div>
        <input type="password"/>
        <HYInput type="password" color={this.state.color}/>
        <h2>我是Profile的标题</h2>
        <ul>
          <li>设置列表1</li>
          <li>设置列表2</li>
          <li>设置列表3</li>
        </ul>
      </div>
    )
  }
}

styled高级特性

支持样式继承

const Button = styled.button`
	padding: 8px 30px;
	border-radius: 5 px;
`

const WarnButton = styled(Button)`
	background-color: red;
	color: #fff;
`

设置主题

import {ThemeProvider} from 'styled-components';

<ThemeProvider theme={{color: "red", fontSize: "30px"}}>
  	<Home/>
  	<Profile/>
</ThemeProvider>

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

vue中添加class

vue中添加class可以传入一个对象

<div class="static"
     v-bind: class="{active: isActive, 'text-danger': hasError}">
  
</div>

也可以传入一个数组

<div v-bind:class="[activeClass, errorClass]">
  
</div>

也可以是对象和数组的混合

<div v-bind:class="[{active: isActive}, errorClass]">
  
</div>

React中添加class

JSX

<div>
	<h2 className={"title " + (isActive? "active":"")}>我是标题</h2>
  <h2 className={["title",(isActive? "active":"")].join()}>我是标题</h2>
</div>

也可以借助第三方库classnames

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'
import classNames from 'classnames';
<div>
	<h2 className={classNames('foo','bar')}>我是标题</h2>
</div>