如何在React项目中实现组件样式的私有化:解决样式冲突

在React中,组件化开发是构建复杂应用的基石。通过将界面拆分成多个独立、可复用的组件,我们可以高效地管理和维护代码。然而,随着组件数量的增加,一个问题逐渐浮现:如何在保持组件独立性的同时,避免各组件之间样式发生冲突?本文将深入探讨几种实现React样式私有化的策略,帮助你有效解决样式冲突问题,提升项目的可维护性和可扩展性。

一、React怎么实现样式私有化?

在组件化开发的项目中,最后我们要把所有组件合并在一起进行渲染【SPA单页面应用】。那么如果各组件之间的样式,发生冲突怎么办?比如,样式名一样,但是样式内容不一样。

以下是针对这种问题有以下五种解决方案:

方案一:内联样式

const Nav = function Nav() {

  return <nav
           style={{
           background: 'lightblue'
        }}>
    </nav>
}

优点:

  • 使用简单:简单的以组件为中心来实现样式的添加
  • 扩展方便:通过使用对象进行样式设置,可以方便的扩展对象来扩展样式
  • 避免冲突:最终都编译为元素的行内样式,不存在样式冲突的问题

缺点:

  • 降低代码可读性:如果使用很多的样式,代码的可读性都将大大降低
  • 不能使用伪类:这意味着 :hover、:focus、:actived、:visited 等都将无法使用
  • 不能使用媒体查询:媒体查询相关的属性不能使用
  • 没有代码提示:当使用对象来定义样式时,是没有代码提示的
  • 代码的复用性降低

总结:

  • 这种方式绝对不能成为项目中的主流处理方式!!!但偶尔有这样的需求
  • 基于style方式,动态设置样式
  • 设置样式权重(原理:行内样式优先级会高很多)
  • 动态计算样式

方案二:样式处理的技巧(推荐使用)

基于样式表、样式类名这样的方式,但是需要人为有意识的、有规范的规避样式冲突问题。

  • 首先:保证每个组件最外层样式类名是不冲突的
    •   命名方案:路径+组件名 作为组件外层容器的名字!!
    /*
    |-views
      |-person
        |-TableList.jsx   命名:.person-table-list-box{}
        |-ControlTap.jsx  命名:.person-control-tap-box{}
      |-product
        |-TableList.jsx   命名:.product-table-list-box{}
    */
  • 后期组件内部的元素,其样式,都基于less/sass/styles嵌入到指定外层容器的样式类名之下去编写!!
  /* 这样保证样式不会发生冲突 */
  .person-table-list-box {
      a {
      ...
      }
  }
  .product-table-list-box{
      a {
      ...
      }
   }

优点:

  • 结构样式分离:实现了样式和JavaScript的分离
  • 使用css所有功能:此方法允许我们使用css的任何语法、伪类、媒体查询等
  • 使用缓存:可对样式文件进行强缓存或协商缓存
  • 易编写:Css样式表在书写时会有代码提示

缺点:

  • 产生冲突:CSS选择器都具有相同的全局作用域,很容易造成样式冲突
  • 性能低:预编译语言的嵌套,可能带来的就是超长的选择器前缀,性能低
  • 没有真正的动态样式:在CSS表中难以实现动态设置样式

总结:此方案在很多公司都在用,推荐养成这种习惯。

方案三:CSS Modules

CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效;产生局部作用域的唯一方法,就是使用一个独一无二的class名字;这就是CSS Modules 的做法!

CSS Modules 的原理:把各个组件中,编写的样式【不经过处理之前,是全局都生效样式】进行私有化处理。

  • 把所有样式类名,进行编译混淆,保证唯一性

操作:

  1. 我们的样式都写在 xxx.module.css 文件中,这样的文件时CSS文件,不能再使用 less/sass/stylus 这样的预编译语言了
  2. 我们在组件中,基于ES6Module模块规范导入进来
 /*
    sty存储的是一个对象
    对象中包含多组键值对:
      +键:css中编写的样式类名  .box{}
      +值:经过webpack编译后的样式类名  .Nav_box_c6EW3{}
   */
   import sty from './xxx.module.css'
   import common from '../assets/common.module.css'
   
   const Nav = function Nav() {
       return (
           {/* css样式使用 */}
           <nav className={sty.box}></nav>
           <h2 className={`${sty.title} ${common.hoverColor}`}>文本内容</h2>
       )
   }
  1. 我们编写的CSS样式也会被编译,所以之前的样式,也能编译为混淆后的类名了【和上述对象中编译后的值一样】
  2. 我们在组件中,所有元素的样式表,基于 sty.xxx 去操作!!!
  3. 在局部的css样式表内写全局样式
:global(.clearfix) {
       clear: both;
 }
  1. 继承其他类的样式
.list {
       font-size: 14px;
   }
   .link {
       composes: list;  /* 继承list的样式 */
       color: lightcoral;
   }
   /* 最终只需要引入link */
   <span className={sty.link}></span>
如何在React项目中实现组件样式的私有化:解决样式冲突

拓展:经过webpack编译后的css样式,所有的“样式类名”经过混淆处理了,规则:组件名_样式类名\_Hash值。

方案四:react-jss

注意:此方法只用于函数式组件当中!!(最后有解决办法

  1. 安装react-jss
yarn add react-jss
  1. 导入 react-jss 并提取 createUseStyles 模块
import { createUseStyles } from 'react-jss'
  1. 通过 createUseStyles 编写样式
 const useStyles = createUseStyles({
       // 设置box就是样式类名
       box: {
           backgroundColor: 'lightblue',
           width: '300px'
       },
       title: {
           fontSize: '20px',
           color: 'red',
           '&:hover': {
               color: 'green'
           }
       },
       list: {
           '& a': { // 等价于 .list a{}
               fontSize: '16px',
               color: '#000'
           }
       }
   });
  1. 最后在组件内解构相应属性并使用
   /*
       基于 createUseStyles 方法,构建组件需要的样式;返回结果是一个自定义Hook函数!           + 对象中的每个成员就是创建的样式类名
           + 可以类似于less等编译语言中的“嵌套语法”,给其后代/伪类等设置样式!!
       自定义Hook执行,返回一个对象,对象包含:
           + 我们创建的样式类名,作为属性名
           + 编译后的样式类名【唯一的】,作为属性值
           {box: 'box-0-2-1', title: 'title-0-2-2', list: 'list-0-2-3'}
       而我们在JS中编写的样式,最后会被编译为
   */
   const Nav = function Nav() {
       let {box,title,list} = useStyles();
       return (
           <nav className={box}>
               <h2 className={title}>文本文本</h2>
               <div className={list}>
                   <a href="#">文本</a>
                   <a href="#">文本</a>
                   <a href="#">文本</a>
               </div>
               <span></span>
           </nav>
       )
   }
  1. 执行自定义Hook传递进来的值,可以放到上面编写的样式中动态处理
const useStyles = createUseStyles({
    /* 某个样式的值,可以设置为函数,props获取的就是传递的对象,返回值就是给当前样式属性设置的样式!! */       list: props => { // 可以让整个编写的样式类变为一个函数【只能在外层设置函数】,返回值就是我们要给其设置的样式对象
           return {
               '& a': { // 等价于 .list a{}
                   fontSize: props.size,
                   color: '#000'
               }
           }
       }
   });
   
   let { box, title, list } = useStyles({
           size: '14px',
           color: 'orange'
   });

类组件中使用的方法:代理组件

/*
解决办法:
        创建一个包装器(代理组件:函数组件):获取我们基于ReactJSS编写的样式,把获取的样式基于属性传递给类组件
        
知识点:
    + React高阶组件:利用JS中的闭包【柯里化函数】实现的组件代理
    + 我们可以在代理组件中,经过业务逻辑的处理,获取一些信息,最后基于属性等方案,传递给我们最终要渲染的组件!!
*/ 

class Menu extends React.Component {
    render() {
        let {box,list} = this.props
        return (
            <nav className={box}>
                <ul className={list}>
                    <li>手机</li>
                    <li>平板</li>
                    <li>电脑</li>
                </ul>
            </nav>
        )
    }
}

// 执行ProxyComponent方法,传递一个组件进行【Component】
const ProxyComponent = function ProxyComponent(Compoennt) {
    // component:真实要渲染的组件【例如:Menu】
    // 方法执行要返回的一个函数组件:我们基于 export default 导出的是这个组件,在App调用的也是这个组件

    // Component => Demo
    // HOC => hegher-order-component  高阶组件
    return function HOC(props) {
        // console.log(props); // => 这里的props是父组件传入的props
        // 真实渲染的是Menu组件
        let sty = useStyles();
        return <Compoennt {...props} {...sty} />
    }

};

// 把函数执行的返回结果【应该是一个组件】,基于ES6Module规范导出,供App导入使用
//  当前案例:我们导出的是HOC
export default ProxyComponent(Menu)

方案五:styled-components

  1. 安装 styled-components 模块
yarn add styled-components
  1. 创建一个 js 文件,并进行配置(此处我们取名为NavStyle.js
import { colorRed,colorBlue,titleSize } from "./common";

/* 
    基于 "styled.标签名" 这种方式编写需要的样式
        + 样式要写在"ES6模板字符串"中
        + 返回并且导出的结果是一个自定义组件

如果编写样式没有提示,我们可以在vscode当中安装一个官方插件:vscode-styled-components
*/ 

export const NavBox = styled.nav`  
    background-color: lightblue;
    width: 300px;

    .title {
        font-size: ${titleSize};
        color: ${colorRed};
        line-height: 40px;

        &:hover {
            color: ${colorBlue};
        }
    }
`;

export const NavBarBox = styled.div.attrs(props => {
    return {
        size: props.size || 18 // 这里为传入的值设置默认值为18,若有属性传入,则使用相应传入的值
    }
})`
    line-height: 40px;
    a {
        font-size: ${props => props.size}px;
        color: : #000;
        margin-right: 10px;

        &:hover {
            color: ${props => props.hover};
        }
    }
`
  1. 在组件中将样式导入并使用(此处使用Nav组件)
import {NavBox,NavBarBox} from './NavStyle'

const Nav = function Nav() {
    return (
        <NavBox>
            <h2 className='title'>购物商城</h2>
            <NavBarBox hover="#ffe58f" size={16}>
                <a href="#">首页</a>
                <a href="#">秒杀</a>
                <a href="#">我的</a>
            </NavBarBox>
        </NavBox>
    )
}

export default Nav
  1. 设置并使用公共样式
import styled from "styled-components"

/* 编写一些通用的样式 */
export const colorRed = "#ff4d4f"; // 这边的样式可在其他地方导入
export const colorBlue = "#1677ff";
export const titleSize = "18px";

export const CommonListBox = styled.ul`
    box-sizing: border-box;
    padding: 10px;
    border: 1px solid #999;

    li {
        font-size: 14px;
        line-height: 30px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;

        &:hover {
            color: ${colorRed}; //此处就是使用了上面定义的通用样式
        }
    }
`;

结语

综上所述,React样式私有化可以确保组件间样式互不干扰、提升项目质量。从简单的内联样式到复杂的CSS Modules和react-jss,每种方法都有其独特的优势和适用场景。在选择样式私有化策略时,需要综合考虑项目需求、团队习惯以及未来扩展性。无论采用哪种方法,关键在于保持代码的清晰、可维护和高效。希望本文能为你解决React项目中样式冲突的问题提供一些实用的思路和解决方案。

延展阅读:

如何通过优化京东风向标提升店铺排名?揭秘五大实操技巧!

如何从零开始搭建自动化测试框架?搭建过程中有哪些注意事项?

怎么提高前端开发速度?Tailwind CSS 安装教程及基本使用方法

咨询方案 获取更多方案详情                        
(0)
研发专家-小瓦研发专家-小瓦
上一篇 2024年10月5日 上午10:17
下一篇 2024年10月7日 上午10:15

相关推荐