React CSS编写方案

116 阅读2分钟

1. 内联样式

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

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

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

    1. 写法上都需要使用驼峰标识
    2. 某些样式没有提示
    3. 大量的样式, 代码混乱
    4. 某些样式无法编写(比如伪类/伪元素)
  • 所以官方依然是希望内联合适和普通的css来结合编写

import React, { PureComponent } from 'react'

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

    this.state = {
      titleSize: 30
    }
  }

  addTitleSize() {
    this.setState({ titleSize: this.state.titleSize + 2 })
  }

  render() {
    const { titleSize } = this.state

    return (
      <div>
        <button onClick={e => this.addTitleSize()}>增加titleSize</button>
        <h2 style={{color: "red", fontSize: `${titleSize}px`}}>我是标题</h2>
        <p style={{color: "blue", fontSize: "20px"}}>我是内容, 哈哈哈</p>
      </div>
    )
  }
}

export default App
内联.png

2. 普通的CSS

  • 普通的 css 通常会编写到一个单独的文件,之后再进行引入(通过 import

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

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

3. CSS Module

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

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

    • .css/.less/.scss 等样式文件都需要修改成 .module.css/.module.less/.module.scss 等,之后就可以引用并且进行使用了
  • css modules确实解决了局部作用域的问题,但是这种方案也有自己的缺陷:

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

    import React, { PureComponent } from 'react'
    
    // 命名方式
    import appStyle from './App.module.css'
    
    export class App extends PureComponent {
      render() {
        return (
          <div>
            <h2 className={appStyle.title}>我是标题</h2>
            <p className={appStyle.content}>我是内容, 哈哈哈哈</p>
          </div>
        )
      }
    }
    
    export default App
    
  • App.module.css

    .title {
      font-size: 32px;
      color: green;
    }
    
    .content {
      font-size: 22px;
      color: orange;
    }
    

    模块.png

4. Less配置和使用

  • React 脚手架搭建的项目能够引入 less 文件,但是不生效,需要修改 webpack 的配置

  • 修改webpack配置的方式

    • npm run eject:暴露webpack的配置,进行修改(如果修改出错,可能会影响项目)
    • craco:
      • 安装 craco 并修改 package.json 里的 scripts 属性
        • npm install @craco/craco
        • "start": "craco start",
  • 安装craco-lessnpm install craco-less,使引入的less文件生效

  • craco.config.js配置

    const CracoLessPlugin = require('craco-less');
    
    module.exports = {
      plugins: [
        {
          plugin: CracoLessPlugin,
          options: {
            lessLoaderOptions: {
              lessOptions: {
                // 进行一些全局变量
                // modifyVars: { '@primary-color': '#1DA57A' },
                javascriptEnabled: true,
              },
            },
          },
        },
      ],
    };
    
  • 代码演示

    • App.jsx
    import React, { PureComponent } from 'react'
    
    import './App.less'
    
    export default class App extends PureComponent {
      render() {
        return (
          <div className='app'>
            <div className="section">
              <h2 className="title">我是标题</h2>
              <div className="content">我是内容,我是内容</div>
            </div>
          </div>
        )
      }
    }
    
    • App.less
    @primaryColor: red;
    
    .section {
      border: 1px solid @primaryColor;
    
      .title {
        font-size: 30px;
        color: @primaryColor;
      }
    
      .content {
        font-size: 20px;
        color: @primaryColor;
      }
    }
    

    image.png

  • 注意:文章采用的是 最新的脚手架版本 5.0.1,安装上述依赖会报错,可以使用下面方法解决

    • npm install @craco/craco@alpha
    • craco-less

5. 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

  • CSS-in-js 好用么?

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

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

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

  • 常见的 CSS-in-JS 库

    • styled-components
    • emotion
    • glamorous

5.1 ES6标签模板字符串

  • 基本使用

    const name = 'rsquo'
    const age = 18
    const str = `my name is ${name}, age is ${age}`
    console.log(str) // my name is rsquo, age is 18
    
  • 标签模板字符串

    const name = 'rsquo'
    const age = 18
    
    // 2.标签用法
    function foo(...args) {
      console.log(args);
    }
    
    foo('rsquo', 18, 1.83) // ['rsquo', 18, 1.83]
    foo`my name is ${name}, age is ${age}`
    

    image.png

5.2 styled-components

  • 在styled component中,就是通过上面的标签模板字符串来解析,最终生成我们想要的样式的

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

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

    • App.jsx
    import React, { PureComponent } from 'react'
    import { AppWrapper } from './style'
    
    export default class App extends PureComponent {
      render() {
        return (
          <AppWrapper>
            <div className="section">
              <h2 className="title">我是标题</h2>
              <div className="content">我是内容,我是内容</div>
            </div>
            <div className="footer">
              <p>免责声明</p>
              <p>版权声明</p>
            </div>
          </AppWrapper>
        )
      }
    }
    
    • style.js
      • 它支持类似于CSS预处理器一样的样式嵌套:

        • 支持直接子代选择器后代选择器,并且直接编写样式
        • 可以通过&符号获取当前元素
        • 直接伪类选择器、伪元素
      • 推荐在 VSCode 中安装 一个 vscode-styled-components的插件,方便编写css

    import styled from 'styled-components'
    
    export const AppWrapper = styled.div`
      .section {
        border: 1px solid red;
    
        .title {
          font-size: 20px;
          color: blue;
    
          &:hover {
            background-color: skyblue;
          }
        }
      }
    
      .footer {
        border: 1px solid green;
      }
    `
    
  • 高级用法

    • 子元素单独抽取样式组件

    • props:可以被传递给styled组件

      • 获取props需要通过 ${}传入一个插值函数,props会作为该函数的参数
      • 这种方式可以有效的解决动态样式的问题
    • attrs:通过attrs给标签模板字符串中提供的属性,可以在未传入 props 时使用

    • 从一个单独的文件中引入变量

    • 支持样式的继承

    • styled设置主题,通过引入 styled-components 中的 ThemeProvider组件,将<App/> 组件包裹

  • 高级用法代码演示

    • index.js:项目入口文件
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import { ThemeProvider } from 'styled-components';
    
    import App from './App'
    
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
      <React.StrictMode>
        {/* 使用styled 设置主题 */}
        <ThemeProvider theme={{color: '#096dd9', size: '30px'}}>
          <App />
        </ThemeProvider>
      </React.StrictMode>
    );
    
    • App.jsx
    import React, { PureComponent } from 'react'
    import Home from './home'
    import { AppWrapper, SectionWrapper } from './style'
    
    export default class App extends PureComponent {
      constructor() {
        super()
        this.state = {
          color: 'yellow',
          size: 30
        }
      }
      render() {
        const { color, size } = this.state
        return (
          <AppWrapper>
            <SectionWrapper color={color} size={size}>
              <h2 className="title">我是标题</h2>
              <p className="content">我是内容,我是内容</p>
              <button onClick={e => this.setState({color: 'blue'})}>修改颜色</button>
            </SectionWrapper>
    
            <Home/>
    
            <div className="footer">
              <p>免责声明</p>
              <p>版权声明</p>
            </div>
          </AppWrapper>
        )
      }
    }
    
    • style.js
    import styled from 'styled-components'
    
    // 引入变量中的样式
    import {
      secondColor,
      largeSize
    } from './style/varibles'
    
    export const AppWrapper = styled.div`
      .footer {
        border: 1px solid green;
      }
    `
    
    // 子元素单独抽取样式组件
    // 通过 attrs 设置默认值
    export const SectionWrapper = styled.div.attrs(props => ({
      tColor: props.color || 'purple'
    }))`
      border: 1px solid red;
    
      .title {
        font-size: ${props => props.size}px;
        color: ${props => props.tColor};
    
        &:hover {
          background-color: skyblue;
        }
      }
    
      .content {
        font-size: ${largeSize};
        color: ${secondColor};
      }
    `
    
    • varibles.js
    export const primaryColor = "#722ed1"
    export const secondColor = "#b37feb"
    
    export const smallSize = "12px"
    export const middleSize = "14px"
    export const largeSize = "18px"
    
    • Home.jsx
    import React, { PureComponent } from 'react'
    import { HomeWrapper, YSButtonWrapper } from './style'
    
    export default class Home extends PureComponent {
      render() {
        return (
          <HomeWrapper>
            <div className='top'>
              <div className="banner">BannerContent</div>
            </div>
            <div className="bottom">
              <h2 className='header'>商品列表</h2>
              <ul className='product-list'>
                <li className='item'>商品列表1</li>
                <li className='item'>商品列表2</li>
                <li className='item'>商品列表3</li>
              </ul>
            </div>
            {/* 继承样式生成的按钮 */}
            <YSButtonWrapper>按钮</YSButtonWrapper>
          </HomeWrapper>
        )
      }
    }
    
    • home/style.js
    import styled from 'styled-components'
    
    // 引入变量
    import {
      primaryColor
    } from '../style/varibles'
    
    const YSButton = styled.button`
      padding: 4px 12px;
      border-radius: 5px;
    `
    
    // 使用继承
    export const YSButtonWrapper = styled(YSButton)`
      background-color: ${primaryColor};
      color: #fff;
    `
    
    export const HomeWrapper = styled.div`
      .top {
        .banner {
          color: red;
        }
      }
    
      .bottom {
        .header {
          /* 使用 ThemeProvider中的变量 */
          color: ${props => props.theme.color};
          font-size: ${props => props.theme.size};
        }
    
        .product-list {
          .item {
            color: blue;
          }
        }
      }
    `
    

    image.png

6. React 中添加class

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

    image.png

  • 借助classnames库(推荐)

    • 安装:npm install 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' 
    
    // lots of arguments of various types 
    classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); // => 'foo bar baz quux' 
    
    // other falsy values are just ignored 
    classNames(null, false, 'bar', undefined, 0, 1, { baz: null }, ''); // => 'bar 1' 
    
    • 代码演示
    import React, { PureComponent } from 'react'
    // 引入classnames
    import classNames from 'classnames'
    
    export default class App extends PureComponent {
      constructor() {
        super()
    
        this.state = {
          isbbb: false,
          isccc: true
        }
      }
      render() {
        const { isbbb, isccc } = this.state
    
        // 为了添加样式
        const classList = ['aaa']
        if(isbbb) classList.push('bbb')
        if(isccc) classList.push('ccc')
        const classname = classList.join(' ')
    
        return (
          <div>
            {/* 不推荐这种写法,难以阅读 */}
            <h2 className={`aaa ${isbbb ? 'bbb' : ''} ${isccc ? 'ccc' : ''}`}>哈哈哈</h2>
    
            {/* 通过列表添加,但是会有很多js代码 */}
            <h2 className={classname}>呵呵呵</h2>
    
            {/* 使用第三方库classnames */}
            <h2 className={classNames('aaa', {bbb: isbbb}, {ccc: isccc})}>嘿嘿嘿</h2>
            <h2 className={classNames(['aaa', {bbb: isbbb}, {ccc: isccc}])}>嚯嚯嚯</h2>
          </div>
        )
      }
    }