在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>
		)
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM