styled-components 結(jié)合 React 框架使用,能讓其支持 CSS in JS 的寫法。比如:
const Button = styled.button`
color: grey;
`;
以上是 SC(styled-components 簡稱,下同)的一個(gè)簡單用法,它很方便的創(chuàng)建了一個(gè) Button 組件,然后你就可以在任何地方使用這個(gè)組件。
<Button>Test</Button>
這一切是怎么做到的呢?下面我們一步步拆解。
語法規(guī)則解釋
有人可能對 SC 的怪異語法規(guī)則有些費(fèi)解,它怎么做到直接在 styled 后面獲得一個(gè) dom 節(jié)點(diǎn),然后又拼接上一個(gè)字符串呢?其實(shí) styled.button 等同于 styled('button'),是一個(gè)柯里化后的函數(shù),而函數(shù)后可以接模板字符串(兩個(gè)反引號(hào)包起來部分``)是 ES6 的一個(gè)新語法特性,相關(guān)文檔,比如我們可以如下使用函數(shù)來接收一個(gè)字符串。
const fun = (args)=>{
console.log(args); // [ 'Hello' ]
}
fun`Hello`
并且其間也可以加入表達(dá)式。
const fun = (args, exp)=>{
console.log(args); // [ 'Hello ', ', Hi~' ]
console.log(exp); // World
}
const v = "World"
fun`Hello ${v}, Hi~`
可以加入表達(dá)式的特性衍生出了 SC 的一種更高級(jí)用法,比如以上的 button,如果加入一個(gè)背景屬性,而此屬性會(huì)根據(jù) props 而變化,則可以寫成以下樣子。
const Button = styled.button`
color: grey;
background: ${props => props.background};
`;
剛剛有人可能好奇 styled.button 是怎么就等同于 styled('button') 的,其實(shí)也簡單,SC 在倒入 styled 時(shí)把所有的 dom 節(jié)點(diǎn)柯里化后的函數(shù)都賦值給了 styled 的同名屬性,這樣就能使用上面的語法方式了,具體實(shí)現(xiàn)就是下面這段代碼。
domElements.forEach(domElement => {
styled[domElement] = styled(domElement);
});
組件創(chuàng)建過程
那我們在用 SC 的方式聲明了一個(gè) Button 組件后,SC 做了哪些操作呢?
1,首先生成一個(gè) componentId,SC 會(huì)確保這個(gè) id 是唯一的,大致就是全局 count 遞增、hash、外加前綴的過程。hash 使用了 MurmurHash,hash 過后的值會(huì)被轉(zhuǎn)成字符串。生成的 id 類似 sc-bdVaJa
componentId = getAlphabeticChar(hash(count))
2,在 head 中插入一個(gè) style 節(jié)點(diǎn),并返回 className;創(chuàng)建一個(gè) style 的節(jié)點(diǎn),然后塞入到 head 標(biāo)簽中,生成一個(gè) className,并且把模板字符串中的 style 結(jié)合 className 塞入到這個(gè) style 節(jié)點(diǎn)中。其中 insertRule 方法
evaluatedStyles 是獲得到的 style 屬性
const style = document.createElement("style");
// WebKit hack
style.appendChild(document.createTextNode(""));
document.head.appendChild(style);
const className = hash(componentId + evaluatedStyles);
style.sheet.insertRule('.className { evaluatedStyles }', 0)
3,根據(jù)解析的 props 和 className 來創(chuàng)建這個(gè) element。
const newElement = React.createElement(
type,
[props],
[...children]
)
newElement.className = "第二部生成的節(jié)點(diǎn)"
結(jié)論
SC 整體原理還是比較簡單的,由于直接用了 ES6 的的字符模板特性,對 style 和 props 部分的解析也比較快,由于需要在運(yùn)行過程中不斷動(dòng)態(tài)創(chuàng)建 Element 并且創(chuàng)建 style 消耗了部分性能。使用過程中也有些要注意的地方,拿最初的 button 舉例,如果每次點(diǎn)擊那個(gè) Button 都會(huì)改變一次 background,你會(huì)發(fā)現(xiàn)在點(diǎn) 200 次后,SC 會(huì)報(bào)一個(gè) warning。
Over 200 classes were generated for component styled.button.
Consider using the attrs method, together with a style object for frequently changed styles.
Example:
const Component = styled.div.attrs({
style: ({ background }) => ({
background,
}),
})`width: 100%;`
<Component />
warning 表示創(chuàng)建了超過 200 個(gè)類,這是由于每次 background 變化都會(huì)生成新的 Element 導(dǎo)致的,此時(shí)我們按照提示可以優(yōu)化成直接使用節(jié)點(diǎn)的 style 屬性來修改樣式,避免重復(fù)生成節(jié)點(diǎn)類。
styled.button.attrs({
style: props => ({background: props.background})
})``
css in js 的寫法有很多優(yōu)勢,此處就不多說了,影響大家使用的很重要一點(diǎn)是性能,SC 團(tuán)隊(duì)也一直對外宣稱要不斷改進(jìn)性能,20年的 V5 版本做了很多提升,當(dāng)然由于實(shí)現(xiàn)機(jī)制,所以再怎么優(yōu)化也不可能達(dá)到原生寫法的性能。如果有對性能要求很嚴(yán)的網(wǎng)站建議不要使用,如果想寫的快寫的爽,用 SC還是很不錯(cuò)的選擇。
號(hào)稱是 Beast Mode 的 V5 版本更新宣傳