在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 的原理:把各个组件中,编写的样式【不经过处理之前,是全局都生效样式】进行私有化处理。
- 把所有样式类名,进行编译混淆,保证唯一性
操作:
- 我们的样式都写在 xxx.module.css 文件中,这样的文件时CSS文件,不能再使用 less/sass/stylus 这样的预编译语言了
- 我们在组件中,基于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>
)
}
- 我们编写的CSS样式也会被编译,所以之前的样式,也能编译为混淆后的类名了【和上述对象中编译后的值一样】
- 我们在组件中,所有元素的样式表,基于 sty.xxx 去操作!!!
- 在局部的css样式表内写全局样式
:global(.clearfix) {
clear: both;
}
- 继承其他类的样式
.list {
font-size: 14px;
}
.link {
composes: list; /* 继承list的样式 */
color: lightcoral;
}
/* 最终只需要引入link */
<span className={sty.link}></span>
拓展:经过webpack编译后的css样式,所有的“样式类名”经过混淆处理了,规则:组件名_样式类名\_Hash值。
方案四:react-jss
注意:此方法只用于函数式组件当中!!(最后有解决办法)
- 安装react-jss
yarn add react-jss
- 导入 react-jss 并提取 createUseStyles 模块
import { createUseStyles } from 'react-jss'
- 通过 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'
}
}
});
- 最后在组件内解构相应属性并使用
/*
基于 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>
)
}
- 执行自定义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
- 安装 styled-components 模块
yarn add styled-components
- 创建一个 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};
}
}
`
- 在组件中将样式导入并使用(此处使用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
- 设置并使用公共样式
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 安装教程及基本使用方法
咨询方案 获取更多方案详情