React中的样式操作

457 阅读12分钟

样式设置

React 可以使用内联样式也可以外部引入css文件,样式设置有class和style两种方式

class类名设置

  1. 在标签中不能写class,必须写className
  2. 样式的代码写在css文件中
  3. className必须接受一个字符串
/* App组件的样式 */
.title-fa {
  color: gold;
}
// App.js
import './App.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "父组件的数据"
  }
  change = () => {
    this.setState({
      msg: "父组件数据被修改"
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className='title-fa'>父组件</h1>
        <p>{this.state.msg}</p>
        <button onClick={this.change}>changefromfather</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

image.png

style内联样式

  1. 不能像原生style属性中一样写成字符串,必须写成对象的形式
  2. 对于font-size这种以-连接的css属性,要用小驼峰方式命名
// App.js
import './App.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "父组件的数据"
  }
  change = () => {
    this.setState({
      msg: "父组件数据被修改"
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className='title-fa'>父组件</h1>
        {/* 设置内联样式,style只接受一个对象,{{}}双括号意义:外面的单括号表示括号中使用 JavaScript 表达式以及变量,里面或括号表示这是一个对象 */}
        <p style={{color:"blue",fontSize:"18px"}}>{this.state.msg}</p>
        <button onClick={this.change}>changefromfather</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

image.png

react中使用css module

在Vue中可以使用css作用域加上scoped属性来实现组件之间的样式不会互相干扰,实现样式独立。

react中的css没有域的概念,css是全局的,任何一个组件的样式规则,都对整个页面有效。比如当有两个css文件,它们的中的一些样式名是一样的,那么就会被覆盖。简单的解决办法就是将样式的命名变得复杂且不重复,但这样样式多了也很难避免重复,且命名也不会太好看。所以react中需要使用CSS Modules

CSS Modules 的做法是通过配置将.css文件进行编译,编译后在每个用到css的组件中的css类名都是独一无二的,从而实现CSS的局部作用域。

全局样式

  1. 全局样式css文件命名规则: xxx.css
  2. 全局样式引入方式:import 'xxx.css'
  3. 全局样式用法:<div className='css文件中命名的类名'>
// App.js
import './App.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'

class App extends PureComponent {
  state = {
    msg: "父组件的数据"
  }
  change = () => {
    this.setState({
      msg: "父组件数据被修改"
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className='title-fa son'>父组件</h1>
        {/* 设置内联样式,style只接受一个对象,{{}}双括号意义:外面的单括号表示括号中使用 JavaScript 表达式以及变量,里面或括号表示这是一个对象 */}
        <p style={{color:"blue",fontSize:"18px"}}>{this.state.msg}</p>
        <button onClick={this.change}>changefromfather</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;
/* App组件的样式 */
.title-fa {
  color: gold;
}
// son.js
import React from "react";
import "./son.css"
class Son extends React.Component {
    state = {
        sonMsg: "Hello from Son"
    }
    // 方法:向父组件传递数据
    sendMessageToParent = () => {
        this.props.onSendMessage(this.state.sonMsg); // 调用父组件传递的方法
    };
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        return (
            <div>
                <h1>子组件</h1>
                <p>{this.props.msg}</p>
                <button onClick={this.sendMessageToParent}>Send Message to App</button>
            </div>
        )
    }
}
export default Son;
/* Son组件的样式 */
.son {
    background-color: black;
}

image.png

App组件只引入了App.css,但实际上使用son.css中的类也生效了,所以任何一个组件的样式规则,都对整个页面有效,则css其实是全局的,并没有只对某一个组件单独生效。

局部样式(css modules)

  1. 局部样式css文件命名规则: xxx.module.css,其中module.css是固定写法
  2. 局部样式引入方式:import xxx from 'xxx.module.css'
  3. 局部样式用法:<div className={xxx.在xxx.module.css文件中命名的类名}>,当类名包含连字符,就需要使用方括号来获取,在 JavaScript 对象中连字符号是非法标识符,所以需要使用方括号语法来访问。<div className={xxx["xx-yy"]}>

实际上利用了css的模块化,CSS Module 允许将 CSS 文件视为 JavaScript 模块的一部分。这样就可以像导入 JavaScript 模块一样导入 CSS 文件,并使用导入的CSS文件的类名。

当引入一份以.module.css为css文件后缀时,最后引入的文件内容会输出为一个对象。

// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
console.log(appStyle)
class App extends PureComponent {
  state = {
    msg: "父组件的数据"
  }
  change = () => {
    this.setState({
      msg: "父组件数据被修改"
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        {/* 设置内联样式,style只接受一个对象,{{}}双括号意义:外面的单括号表示括号中使用 JavaScript 表达式以及变量,里面或括号表示这是一个对象 */}
        <p style={{ color: "blue", fontSize: "18px" }}>{this.state.msg}</p>
        <button onClick={this.change}>changefromfather</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;
/* App组件的样式,App.module.css */
.title-fa {
  color: gold;
}

.fa-bgc {
  background-color: purple;
}

image.png

image.png

css modules会默认给类名加上一个唯一标识符(即哈希字符串),从而实现类名不重复。如上图对象中的属性名是原本在.module.css文件中定义的类名,属性值是编译后经过模块化打包的新的类名,它加上了哈希字符串。最后页面上所使用的样式的类名也是经过模块化打包所输出 的新的类名

css modules语法

:global(.类名):表示声明一个全局规则。凡是这样声明的类名,都不会被编译成哈希字符串

/* App组件的样式,App.module.css */
.title-fa {
  color: gold;
}

.fa-bgc {
  background-color: purple;
}
/* 全局样式 */
:global(.all) {
  font-size: 20px;
  font-weight: 600;
  color: purple;
}
// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
console.log(appStyle)
class App extends PureComponent {
  state = {
    msg: "父组件的数据"
  }
  change = () => {
    this.setState({
      msg: "父组件数据被修改"
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        {/* 使用全局样式 */}
        <p className="all">{this.state.msg}</p>
        <button onClick={this.change}>changefromfather</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

// son.js
import React from "react";
// import sonStyle from "./son.module.css"
class Son extends React.Component {
    state = {
        sonMsg: "Hello from Son"
    }
    // 方法:向父组件传递数据
    sendMessageToParent = () => {
        this.props.onSendMessage(this.state.sonMsg); // 调用父组件传递的方法
    };
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        return (
            <div>
                {/* 使用全局样式 */}
                <h1 className="all">子组件</h1>
                <p>{this.props.msg}</p>
                <button onClick={this.sendMessageToParent}>Send Message to App</button>
            </div>
        )
    }
}
export default Son;

image.png

:local(.类名):表示显式的局部作用域,实际上是在告诉CSS Modules这个类名应该是局部的。如果省略 :local,类名默认也是局部的。

/* App组件的样式,App.module.css */
:local(.title-fa) {
  color: gold;
}

:local(.fa-bgc) {
  background-color: purple;
}
/* 全局样式 */
:global(.all) {
  font-size: 20px;
  font-weight: 600;
  color: purple;
}

以上代码等同于

/* App组件的样式,App.module.css */
.title-fa {
  color: gold;
}

.fa-bgc {
  background-color: purple;
}
/* 全局样式 */
:global(.all) {
  font-size: 20px;
  font-weight: 600;
  color: purple;
}

composes关键字:用于组合多个类名,表示一个选择器可以继承另一个选择器的规则。

/* App组件的样式,App.module.css */
/* 基本的按钮样式 */
.button {
  padding: 10px 20px;
  font-size: 16px;
  border: 1px solid #ccc;
  border-radius: 4px;
  background-color: #fff;
  cursor: pointer;
}

/* 特定类型的按钮样式 */
.primary-button {
  composes: button; /* 继承 .button 的样式 */
  background-color: blue;
  color: white;
}
// App.js
import Son from './son';
import { PureComponent } from 'react'
console.log(appStyle)
class App extends PureComponent {
  state = {
    msg: "父组件的数据"
  }
  change = () => {
    this.setState({
      msg: "父组件数据被修改"
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        <button className={appStyle.button} onClick={this.change}>changefromfather</button>
        <br />
        <button className={appStyle["primary-button"]}>普通按钮</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

image.png

在CSS Modules中,甚至可以从不同的CSS模块文件中组合类名,以实现样式的复用。这通常通过 @value 指令来实现,该指令允许从其他CSS模块文件中导入类名,然后在当前文件中使用这些类名进行组合。

/* 基本的按钮样式,Button.module.css */
.button {
    padding: 10px 20px;
    font-size: 16px;
    border: 1px solid #ccc;
    border-radius: 4px;
    background-color: #fff;
    cursor: pointer;
  }
/* Theme.module.css */
/* 主题样式 */
.primary {
    background-color: blue;
    color: white;
}

Son.module.css中组合上面两个不同文件的类名

/* Son组件的样式,Son.module.css */
.son {
    background-color: black;
}
/* 在Son.module.css中组合类名 */
@value button from './Button.module.css';
@value primary from './Theme.module.css';

/* 组合按钮样式和主题样式 */
.complex-button {
  composes: button primary;
  font-weight: bold;
}

使用组合的类名

// son.js
import React from "react";
import sonStyle from "./son.module.css"
class Son extends React.Component {
    state = {
        sonMsg: "Hello from Son"
    }
    // 方法:向父组件传递数据
    sendMessageToParent = () => {
        this.props.onSendMessage(this.state.sonMsg); // 调用父组件传递的方法
    };
    render() {
        // 不写constructor构造函数,采用ES7新写法props会直接绑定在组件实例上
        return (
            <div>
                {/* 使用全局样式 */}
                <h1 className="all">子组件</h1>
                <p>{this.props.msg}</p>
                {/* 使用组合后的类名 */}
                <button className={sonStyle["complex-button"]} onClick={this.sendMessageToParent}>Send Message to App</button>
            </div>
        )
    }
}
export default Son;

image.png

按钮就应用了 button 和 primary 类名的样式,以及 complex-button 类名的额外样式。

react动态添加样式

对于react中动态绑定样式实质上是通过表达式的结果进行字符串的拼接,它不像vue可以传递字符串、对象以及数组。

动态绑定className

对象语法

传给 className 一个对象,可动态地切换 class。但是使用类似vue、小程序等对象语法是不支持的。

错误写法:

render(){
  return <div className={ 'box-color':this.state.isError }>hello world</div>
}

下面这种对象变量写法是不支持的,也不会报错,控制台className显示为[object object],无效。

const classObj = {
  'box-show': this.state.isShow,
  'box-color': this.state.isError
}

render(){
  return <div className={ classObj }>hello world</div>
}
使用逻辑运算符
/* App组件的样式,App.module.css */
.title-fa {
  color: gold;
}

.title-red {
  color: red;
}
// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
class App extends PureComponent {
  state = {
    msg: "父组件的数据",
    red:false
  }
  change = () => {
    this.setState({
      ...this.state,
      red:true
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        <p className={this.state.red && appStyle['title-red']}>{this.state.msg}</p>
        <button onClick={this.change}>change</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

2.gif

使用三目运算符
/* App组件的样式,App.module.css */
.title-fa {
  color: gold;
}

.box-show {
  visibility: visible;
  color: red;
}

.box-hide {
  visibility: hidden;
}

:global(.all) {
  font-size: 20px;
  font-weight: 600;
  color: purple;
}
// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
class App extends PureComponent {
  state = {
    msg: "父组件的数据",
    isShow: false
  }
  change = () => {
    this.setState({
      msg: '数据被修改',
      isShow: !this.state.isShow
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        <p className={this.state.isShow ? appStyle['box-show'] : appStyle["box-hide"]}>{this.state.msg}</p>
        <button onClick={this.change}>change</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

2.gif

使用函数

绑定的数据对象也不必定义在{}插值表达式中,可以定义一个函数,将类名返回即可。

// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
class App extends PureComponent {
  state = {
    msg: "父组件的数据",
    isShow: false
  }
  change = () => {
    this.setState({
      msg: '数据被修改',
      isShow: !this.state.isShow
    });
  }
  showHandle = () => {
    return this.state.isShow ? appStyle['box-show'] : appStyle["box-hide"]
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        <p className={this.showHandle()}>{this.state.msg}</p>
        <button onClick={this.change}>change</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

2.gif

数组写法

React中不支持className数组语法,错误代码:

.box-red {
  color: red;
}

.box-size {
  font-size: 20px;
  font-weight: 600;
}
// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
class App extends PureComponent {
  state = {
    msg: "父组件的数据",
    isShow: false
  }
  change = () => {
    this.setState({
      msg: '数据被修改',
      isShow: !this.state.isShow
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        <p className={this.state.isShow?appStyle["box-red"]:[ appStyle["'box-size'"], appStyle['box-red']]}>{this.state.msg}</p>
        <button onClick={this.change}>change</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

控制台显示结果(无效,中间多了个逗号):

image.png

不支持数组语法,可以使用三目运算符来表示,将类名拼接成字符串,但会增加css代码量,可用ES6模板字符串,代码如下:

// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
class App extends PureComponent {
  state = {
    msg: "父组件的数据",
    isShow: false
  }
  change = () => {
    this.setState({
      msg: '数据被修改',
      isShow: !this.state.isShow
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        <p className={this.state.isShow?appStyle["box-red"]:`${appStyle["box-size"]} ${appStyle['box-red']}`}>{this.state.msg}</p>
        <button onClick={this.change}>change</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

2.gif

控制台显示结果(有效):

<div class="box-sizee box-red"></div>

注:${appStyle["box-red"]}${appStyle["box-size"]}中间用空格隔开。

classname库

借助classname库来动态操作类名,该库的本质也是帮助生成类名的字符串,而不需要自己手动拼接。

安装:

npm i classname

classname是一个函数,函数接受一个对象,对象的属性值就是要使用的类名,当属性值为true表示有该类样式,为false表示没有该类样式。最后返回的就是拼接的字符串。

对于全局样式

/* App组件的样式,App.module.css */
.title-fa {
  color: gold;
}

.box-red {
  color: red;
}

.box-size {
  font-size: 20px;
  font-weight: 600;
}

:global(.all) {
  font-size: 20px;
  font-weight: 600;
  color: purple;
}
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
import classnames from 'classnames'
// 引入的classname是一个函数,函数接受一个对象,对象的属性值就是要使用的类名,当属性值为true表示有该类样式,为false表示没有该类样式
let str =classnames({
  all: true,
  red: false,
  blue:true
});
console.log(str);
class App extends PureComponent {
  state = {
    msg: "父组件的数据",
    isShow: false
  }
  change = () => {
    this.setState({
      msg: '数据被修改',
      isShow: !this.state.isShow
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        <p className={classnames({
          all: this.state.isShow
        })}>{this.state.msg}</p>
        <button onClick={this.change}>change</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

控制台打印结果:

image.png

2.gif

当使用了css modules模块化的局部样式就会失效了

/* App组件的样式,App.module.css */
.title-fa {
  color: gold;
}

.box-red {
  color: red;
}

.size {
  font-size: 20px;
  font-weight: 600;
}

.blue {
  color: blue;
}

:global(.all) {
  font-size: 20px;
  font-weight: 600;
  color: purple;
}
// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
import classnames from 'classnames'
class App extends PureComponent {
  state = {
    msg: "父组件的数据",
    isShow: false
  }
  change = () => {
    this.setState({
      msg: '数据被修改',
      isShow: !this.state.isShow
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        <p className={classnames({
          size:true,
          blue:this.state.isShow
        })}>{this.state.msg}</p>
        <button onClick={this.change}>change</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

image.png

因为最后绑定的类名还是原本的类名,而css modules模块化的局部样式需要绑定经过编译打包后的类名,是加了hash值唯一的类名,所以使用原本的类名就失效了

正确的做法是要引入classname的bind,使用classnames的bind函数绑定引入的模块名,然后它会返回一个新函数,用这个新函数来动态操作className。

/* App组件的样式,App.module.css */
.title-fa {
  color: gold;
}

.box-red {
  background-color: blue;
}

.box-bgc {
  background-color: black;
}

.size {
  font-size: 20px;
  font-weight: 600;
}

.blue {
  color: #fff;
}

.red {
  color: red;
}

:global(.all) {
  font-size: 20px;
  font-weight: 600;
  color: purple;
}
// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import Son from './son';
import { PureComponent } from 'react'
import classnames from 'classnames/bind.js'
let bindAppstyleClassname = classnames.bind(appStyle);
class App extends PureComponent {
  state = {
    msg: "父组件的数据",
    isShow: false
  }
  change = () => {
    this.setState({
      msg: '数据被修改',
      isShow: !this.state.isShow
    });
  }
  sonchange = (arg) => {
    this.setState({
      msg: arg
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        <p className={bindAppstyleClassname({
          size: true,
          blue: this.state.isShow,
          "box-bgc": !this.state.isShow,
          "box-red": this.state.isShow,
          red: !this.state.isShow,
        })}>{this.state.msg}</p>
        <button onClick={this.change}>change</button>
        <hr />
        <Son msg={this.state.msg} fatherfn={this.sonchange}></Son>
      </div>
    )
  }
}

export default App;

2.gif

动态绑定style

react中style属性必须接受对象的形式

// App.js
import appStyle from './App.module.css'; // 引入App组件的样式
import { PureComponent } from 'react'
import classnames from 'classnames/bind.js'
let bindAppstyleClassname = classnames.bind(appStyle);
class App extends PureComponent {
  state = {
    msg: "父组件的数据",
    isShow: false,
    pur: true
  }
  change = () => {
    this.setState({
      msg: '数据被修改',
      isShow: !this.state.isShow,
      pur: !this.state.pur
    });
  }
  render() {
    return (
      <div>
        {/* className属性使用在App.css定义的类名 */}
        <h1 className={appStyle["title-fa"]}>父组件</h1>
        <p className={bindAppstyleClassname({
          size: true,
          blue: this.state.isShow,
          "box-bgc": !this.state.isShow,
          "box-red": this.state.isShow,
          red: !this.state.isShow,
        })}>{this.state.msg}</p>
        <p style={this.state.pur ? { color: "purple" } : { color: "gold" }}>动态style</p>
        <button onClick={this.change}>change</button>
      </div>
    )
  }
}

export default App;

2.gif