在React中写css样式的几种方案

前端组件化开发中的CSS

在目前整个前端都使用组件化开发的模式下,CSS样式的编写就成为了一个问题。因为CSS也叫做层叠样式表,意思就是多个css样式作用于同一个HTML元素的时候,浏览器会根据权重的大小来进行覆盖,为元素应用权重最高的那一组css样式,很明显这种特性不适合组件化开发。

组件化开发模式下对于CSS解决方案的要求

  1. 支持编写局部的css,css具备自己的局部作用域,不会污染其他组件中的元素。
  2. 支持编写动态的css,也就是元素的某些样式可以根据state/data中的某个属性来动态改变,其实也就是js去控制元素的css样式。当属性的值变化的时候,样式也发生变化。
  3. 支持所有的css新特性,比如伪类,位元素,动画,过渡,转化等等
  4. 编写方式简洁易上手,学习成本低,最好符合一贯的css风格特点

React中的CSS缺陷

相比于React,同为前端框架的Vue在css样式编写上要做的比React好,比如:

  1. Vue通过.vue文件中的style标签来编写属于当前组件的样式,高度样式行为相分离
  2. scoped属性用于防止当前组件的样式污染其他组件样式
  3. lang属性用于设置css预处理器如less,sass,stylus
  4. 通过:style的方法将data中的属性和样式连接起来,实现样式动态变化。

一般实现样式动态变化的方案:

  1. 动态为一个元素添加clss类名
  2. 通过style内联样式,将js中属性值和css样式联合起来

React官方一致没有给出在React中统一的风格样式,普通的css,css modules以及css in js,很多种方案带来了上百种不同的库,到目前为止没有统一的方案。

方案一:使用style标签内联样式

React官方推荐我们使用style标签内联样式这种写法来进行组件样式的编写,规定style标签接收一个采用小驼峰命名属性的js对象,而不是css字符串。通过这种方式写的样式会将样式添加到元素的内联样式上。

优点:
基于内联样式书写的样式肯定不会导致样式冲突
可以动态获取state中的状态来完成动态样式

缺点:
采用小驼峰写法,有的css书写没有提示易错
在JSX中写大量的style样式,比较混乱
伪类,伪元素这种样式无法通过内联样式编写

class App extends PureComponent{
	constructor(props) {
	    super(props);
		/* 动态改变元素样式 */
		this.state = {
			textColor:"pink"
		}
	}
	render(){
		/* 将样式抽取到一个变量中 */
		const h2Style={
			fontSize:"18px",
			color:"red"
		}
		
		return(
			<div>
				<h2 style={h2Style}>这是一个App组件</h2>
				<p style={{fontSize:"18px",color:"red"}}>这是一段文字</p>
				<div style={{color:this.state.textColor}}>这是一段动态变化的文字</div>
			</div>
		)
	}
}

方案二:使用普通CSS写法

这种方案和传统的在网页中进行开发时的编写方式是一致的,传统的网页开发编写css的优点它有,对应的缺点它也同样存在。
通常是新建一个和组件一一对应的.css文件,然后给组件最外层的div元素一个className。在.css文件中编写对应的样式文件,然后在组件.js文件中导入该样式文件即可将样式应用到组件中对应的元素标签上。

优点
编写规范简单,不需要用小驼峰这种不熟悉的语法去写

缺点(主要就是样式的层叠覆盖,这种方法写的css都是全局作用域的)
每次都要在最外层增加一个className,避免样式冲突
每次在编写样式都要先写一个.className
就算写了还是有可能会冲突,比如其他组件中有权重更高的选择器
使用直接子代选择器可以避免 但是复杂度就太麻烦了

.app .title{
	font-size: 32px;
	color: red;
	font-weight: bold;
}
import "./index.css"
class App extends PureComponent{
	render(){
		return(
			<div className="app">
				<h2 className="title">这是一个App组件</h2>
			</div>
		)
	}
}

方案三:CSS modules

css modules是一种在使用了类似于webpack配置的开发环境下都可以使用的css解决方案,主要用于解决相互独立的组件的样式互相冲突和覆盖的问题。
在Vue项目中,我们需要自己手动在webpack.config.js中进行配置;而React中基于脚手架搭建的项目已经帮助我们内置了css modules的配置。

使用方法(假设为Home组件添加css样式)

  1. 新建home.module.css/less/scss文件,然后将样式写在该文件中
  2. 在home.js中通过模块化的方式导入,因为添加了module的css文件会被当作一个大的js对象导入,所以这里需要使用一个标识符去接收,比如homeStyles
  3. 在jsx中使用className={homeStyles.title}这种方式为元素添加样式,这里的本质是将homeStyles对象中的title属性取出来,对应的属性值就是一个经过编译后的唯一的class类名,然后将这个类名下的样式应用到元素上。
.title{
	font-size: 32px;
	color: red;
	font-weight: bold;
}

.banner {
	font-size: 28px;
	color:pink;
	font-weight: bold;
}

import appStyles from "./index.module.css";
class App extends PureComponent{
	render(){
		const {title,banner} = appStyles; /* 解构赋值*/
		return(
			<div>
				<h2 className={title}>这是一个title</h2>
				<p className={banner}>这是一个banner</p>
			</div>
		)
	}
}

使用原理
打印appStyles对象,如下:
每一个类名都会当作一个属性,属性值为"当前css文件所在文件名_类名_随机唯一值"
类名唯一,所以样式不会冲突,也是唯一的。
如果文件名为index.module.css,那么第一个值是该文件所在的文件夹名称,如"React中的样式方案";
如果文件名不是以index开头的,那么第一个值是该文件本身的名称,如home

{
	banner: "React中的样式方案_banner__klcc5"
	title: "React中的样式方案_title__26xd3"
	banner: "home_banner__klcc5"
}

优点
解决了css中样式冲突的问题,等于让每一个组件中的css样式都有了自己组件作用域

缺点

  • 所有JSX中的类名不能使用连接符-,只能使用驼峰写法。如box-title是错误的,而boxTitle是正确的。因为-在js中是不能被识别的。
  • 所有的样式都必须采用style.className的形式来编写,只不过这个问题可以用解构赋值来解决
  • 不能动态修改元素样式,依然需要使用内联样式的方式。

方案四:CSS in JS【基于第三方库styled-components】

CSS in JS的定义

CSS in JS在React的官方文档上描述为:CSS in JS是一种模式,指的将CSS样式由js生成而不是在外部的样式文件中定义,这个功能不由React提供,需要由第三方库来提供。

在传统的网页开发中提倡结构样式行为相分离,但是React的思想中认为逻辑(js)本身和UI是无法完全分离的,所以才有了JSX语法,一种将逻辑和结构相互结合嵌套的写法。而CSS-in-JS的模式就是将样式CSS代码也写入到js中的方式,并且这种模式的优势在于CSS可以轻松的使用JS中的state状态,正因为此,React才被人们称之为All in JS。

CSS in JS的优势及其第三方库的实现

CSS in JS基于JS提供给CSS的能力,可以实现类似于CSS预处理器的大部分功能,如:

  • 样式嵌套【极大程度上避免了样式冲突】
  • 伪类和伪元素
  • 函数定义
  • 动态修改状态【这一点是CSS预处理器无法实现的点】

CSS in JS目前流行的库如下:

  • styled-components【社区最流行的库】
  • emotion
  • glamorous

styled-components库的实现原理————ES6标签模板字符串

ES6标签模板字符串在当做函数调用时的参数的时候,浏览器会按照一种特殊的方式对模板字符串参数进行解析和分隔,如果解析后参数进行打印,那么得到的结果是一个二维数组。
该数组的第一项是分割下来的字符串数组,也是一个数组
该数组的第二项及以后是模板字符串中用${}包裹的变量或者JS表达式

const name = "lilei";
const age = 18;
function test(...args){
	console.log(args);
}
test`这是姓名${name},这是年龄${age}`;
/* args数组的打印结果是一个二维数组: */
args = [
	0: ["这是姓名",",这是年龄",""],
	1: "lilei",
	2: 18
]

styled-components库默认导出的对象是什么?

在安装了styled-components库之后,我们在写样式之前需要做两个准备:

  1. 将库在当前要写的js文件中导入,因为是默认导出,所以我们可以任意起一个标识符styled去接收它导出的对象,打印这个对象:发现这个对象上都是由HTML标签名作为方法名的很多templateFunction方法,而我们写样式也是基于调用这些方法来实现,因为这些方法的返回值都是一个React组件对象,既然是React组件那么就可以在JSX的语法中使用,从而实现样式的注入。
span: ƒ templateFunction()
div: ƒ templateFunction()
a: ƒ templateFunction()

  1. 习惯使用模板字符串当做函数参数的方式写css代码
    调用以上方法时,参数就是模板字符串,而返回值是一个React组件对象,返回的组件对象如下,其中有一项rules就是如何解析模板字符串的规则,而componentId就是为组件元素生成的唯一类名。
styledComponentId: "sc-AxjAm"  // 唯一id
target: "div"
componentStyle: {
	baseHash: 400283751
	componentId: "sc-AxjAm"
	isStatic: false
	rules: [
		0: "\n\twidth:500px;\n\theight:200px;\n\tbackground-color:pink;\n"
	]
	staticRulesId: ""
}

利用styled-components库在React中实现基本样式编写

  1. 安装styled-components库
    npm install styled-components@5.1.1 --save

  2. 新建同级样式文件styled.js,导入库之后按照模板字符串的语法书写css样式

import styled from "styled-components";
export const HomeWrapper = styled.div`
	width:500px;
	height:200px;
	background-color:pink;
	// 结构嵌套
	.banner{
		font-size:20px;
		color:blue;
		cursor:pointer;
		// 伪元素
		&:hover{
			color:red;
		}
		// 伪类元素
		&::after{
			content:"小尾巴";
		}
	}
`;
export const H2Wrapper = styled.h2`
	font-size:18px;
	color:red;
`
  1. 导入组件的js文件中并进行使用即可
    HomeWrapper作用于JSX语法中的时候,此时会生成一个唯一的class类名,这个类名在HomeWrapper组件对象中的styledComponentId属性中进行获取,然后给当前组件的根元素添加这个唯一类名,保证不进行样式冲突。

一般情况下给组件的根元素来一个Wrapper组件包裹就类似于给根元素一个id值一样,后续的子元素都基于嵌套的写法写在里面就可以了;但是由于这里生成的是class类名,所以如果还是不放心怕其他组件的id选择器进行覆盖的话,可以为某些样式再生成一个组件进行替换,确保样式不会覆盖。

import {
	HomeWrapper,
	H2Wrapper
}from "./styled.js"

class Home extends PureComponent{
	render(){
		return(
			<HomeWrapper>
				<H2Wrapper>这是一个title</H2Wrapper>
				<p className="banner">这是一个banner</p>
			</HomeWrapper>
		)
	}
}

利用styled-components库在React中实现动态样式编写

主要基于styled-components库中提供的attrs方法以及props属性穿透的特性实现:

  1. styled.div.attrs(objProps)styles
    该方法接收一个对象类型的参数,对象中的键值对会传入到下面的props中,供样式使用
    该方法返回的还是一个函数,所以可以接受模板字符串作为一个参数

attrs方法接收的参数中的对象都可以在写编写样式的时候基于${props=>props.xxx}来进行调用,箭头函数的返回值会作为插值的返回值.

  1. props的穿透
    可以将组件的state属性以及传递给组件的键值对属性都穿透到下面的模板字符串中,方便我们在编写样式的实现动态样式。这一点是任何css预处理器都做不到的。
import styled from "styled-components";
export const StyleInput = styled.input.attrs({
	placeholder:"请输入您的姓名",
	type:"text",
	bgColor:"pink"
})`
	font-size:20px;
	color:blue;
	background-color:${props=>props.bgColor}; /* 使用来自attrs中参数*/
	border:${props=>props.bd};/* 使用来自组件state中属性 */
`
import {
	StyleInput,
} from './styled.js'

/* Home组件 */
class Home extends PureComponent{
	constructor(props) {
	    super(props);
		this.state = {
			borderStyle:"1px solid red" 
		}
	}
	render(){
		return(
			<div>
				{/* 将state中的属性borderStyle当做参数穿透到样式中*/}
				<StyleInput bd={this.state.borderStyle}/>
			</div>
		)
	}
}

利用styled-components库在React中实现样式继承[样式继承复用]

实现原理:基于styled(FatherCpn)styles
styled方法接收一个经过styled.tag()styles增强之后的React组件对象作为参数,返回一个新的React组件对象。新的组件对象会继承其父组件的所有样式,如果有自己的样式可以再进行定义。

StylePrimeryButton组件样式继承了StyleButton组件的样式,对于不同的部分再自己进行定义,这一点和实例属性来覆盖父类的原型属性一个道理。

export const StyleButton = styled.button`
	width:100px;
	height:40px;
	color:#6c6c6c;
	background-color:#fff;
	border:1px solid #eee;
`

export const StylePrimeryButton = styled(StyleButton)`
	color:#24a2ff;
	background-color:#23272d;
`
class About extends PureComponent{
	render(){
		return(
			<div>
				<StyleButton>普通按钮</StyleButton>
				<StylePrimeryButton>主要按钮</StylePrimeryButton>
			</div>
		)
	}
}

利用styled-components库在React中实现主题设置[样式共享复用]

从styled-components中导入ThemeProvider这个分享组件,该组件必须传递一个theme属性,属性值就是父组件要共享给每一个子组件的样式,这个样式可以来自于state对象或者props等等。

在编写子组件HomeWrapper的样式的时候,就可以通过${props=>props.theme.xxx}来获取父组件要进行共享的样式,从而达到更高程度的样式复用,减少冗余代码。

export const HomeWrapper = styled.div`
	background-color:${props=>props.theme.bgColor};
	font-size:${props=>props.theme.lgSize};
`

import {ThemeProvider} from "styled-components";
import {HomeWrapper} from './styled.js'
class App extends PureComponent{
	constructor(){
		super();
		this.state = {
			bgColor:"pink",
			lgSize:"40px"
		}
	}
	render(){
		return(
			<ThemeProvider theme={this.state}>
				<Home></Home>
				<About></About>
			</ThemeProvider>
		)
	}
}
posted @ 2021-07-19 00:21  小高同学1997  阅读(7209)  评论(0编辑  收藏  举报