React是一個構建用戶界面的js庫,從UI=render()這個等式中就很好的映射了這一點,UI的顯示取決於等式右邊的render函數的返回值.css
而編寫React應用,就是在編寫React組件,組件中最重要的數據就是props和state,有了數據,怎麼讓其以什麼樣的顯示,那就是CSS作的事情了html
在React中,一切皆能夠是Js,也就是說在js裏面能夠寫css,這相比傳統的內容(html),層疊樣式(css),行爲動做(js)進行分離,這種分離僅僅是把三個不一樣的技術進行了物理上的分離,進行分開管理,若是從另外一個視覺角度上講,並無實現高內聚的特色前端
既然前端自己就是頁面的展現,那麼把js和css放在一塊兒,也是一種細粒度的組合,css也能夠和Js同樣,經過模塊化的形式嵌入到js裏面去node
CSS modules很好的解決了樣式衝突,利用了分而治之的理念,在現在組件化開發大行其道上,一樣css也在不斷的進化,如同js同樣,也有變量,函數等具有Js同樣的活力,那麼在React中是怎麼實現樣式的模塊化的?react
經過單獨的*.css文件定義組件的樣式,而且經過clssName指定他們,有什麼很差的?git
在JSX上進行事件的監聽綁定,經過on*EventType只針對原生的HTML標籤起做用的,若是是自定義的組件,是不起做用的,有什麼好的解決辦法?github
樣式化組件的魅力(特色)npm
那麼本節就是你想要知道的canvas
若是想閱讀體驗更好,戳該連接,React中編寫樣式css(styled-components)數組
React中組件形式
關於React中定義組件的形式,有以下幾種方式,其中前兩個在以前的學習當中,相信你已經很熟悉了的,若是不清楚,能夠查看前面的內容的
類class聲明的組件(類組件/容器組件)
函數式聲明的組件(函數組件/無狀態組件/UI組件)
樣式化組件(styled-components)
本節主要講的就是樣式化組件,給一個React組件添加樣式,有哪些方式?下面一塊兒來看看的
行內樣式 VS 外部樣式
想要給React組件添加樣式,常見的方式有
在JSX上添加style屬性定義行內樣式
經過import關鍵字引入外部樣式
像以下所示,在JSX上添加樣式: 下面的代碼是用class類組件聲明瞭一個Header組件,這個組件返回了一個button按鈕,給這個按鈕經過style添加了一些樣式
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
class Header extends Component {
render(){
return (
<Fragment>
<button style = {{ width: '100px', height: '40px', borderRadius: '3px', outline: 'none', outline: 'none', border: 'none', cursor: 'pointer', background: '#abcdef', color: '#fff'}}>button按鈕</button>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
複製代碼
對於上面的行內樣式,也能夠把它定義成一個對象Object的方式去定義樣式,與下面是等價的
class Header extends Component {
render(){
const btnStyle = {
width: '100px',
height: '40px',
borderRadius: '3px',
outline: 'none',
border: 'none',
cursor: 'pointer',
background: '#abcdef',
color: '#fff'
}
return (
<Fragment>
<button style = { btnStyle }>button按鈕</button>
</Fragment>
);
}
}
複製代碼
雖然這樣也是在JS中寫css樣式,可是管理起來並不方便,不少時候,咱們是用clssName的方式去定義樣式的 ,按照咱們熟悉的方式,就是把樣式文件命名成*.css,而後經過import的方式給引入進去
import "./style.css";
複製代碼
對於樣式名,有時候,對於各個不一樣的組件的className有可能會同樣,若是是這樣的話,後面引入的樣式名會覆蓋前面的,這樣的話顯然不是咱們想要的結果了
那有什麼好的解決辦法?
在React中有css-in-js,它是一種模式,這個css由js生成而不是在外部文件中定義,是CSS Modules,主要是藉助第三方庫生成隨機類名稱的方式來創建一種局部類名的方式
這種css-in-js的第三方模塊有不少:能夠訪問:github.com/MicheleBert…
今天的主要學習的是github上star數最多的,styled-components
使用styled-components的好處是:它可讓組件本身的樣式對本身生效,不是全局生效,作到互不干擾
首先你得經過npm或者cnpm進行安裝styled-components模塊
npm install -S styed-components
複製代碼
在安裝完後,在使用styled-components的文件內,經過import的方式引入該模塊
以下代碼所示: 在文件的上方引入styled-components,實例化了一個styled對象,經過給styled對象下添加你想要的html元素,利用了Es6中的一個模板字符串,反引號
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import styled from "styled-components"; // 引入styled-components庫,實例化styled對象
// 聲明樣式ButtonA組件,經過styled對象進行建立,注意styled.html元素,後面是反引號
const ButtonA = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: #abcdef;
color: #fff;
`;
// 樣式化聲明ButtonB組件
const ButtonB = styled.button`
background: red;
color: #fff;
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
`;
class Header extends Component {
render(){
return (
<Fragment>
<ButtonA>按鈕A</ButtonA>
<ButtonB>按鈕B</ButtonB>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
複製代碼
這是渲染的結果:
注意:要避免在render方法中聲明樣式化組件 以下所示:這樣程序雖然不報錯,可是會引發性能問題,引發組件沒必要要的渲染下面這種作法是不推薦的,應當避免使用
class Header extends Component {
render() {
const ButtonA = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: #abcdef;
color: #fff;
`;
// 聲明樣式ButtonB組件,不該該在render函數裏面聲明樣式組件
const ButtonB = styled(ButtonA)`
background: red;
`;
return (
<Fragment>
<ButtonA>按鈕A</ButtonA>
<ButtonB>按鈕B</ButtonB>
</Fragment>
);
}
}
複製代碼
由於在render方法中聲明樣式化組件,每次都會動態渲染建立一個新的組件。這意味着React必須在每一個後續渲染中丟棄並從新計算DOM樹的那部分,而不是僅計算它們之間發生變化的差別。這會致使性能瓶頸
正確的作法就是如同剛開始那樣,把樣式組件放到組件最外面去
固然,爲了便於樣式的集中管理,對於樣式化組件,咱們每每會把它寫在一個文件中去,把上面的樣式組件放到一個style.js的文件中去,而後經過Es6中模塊化的export的方式對外暴露出去,只要哪一個組件模塊須要,直接經過import引入便可
import styled from "styled-components"; // 引入styled-components
// 聲明樣式ButtonA組件
const ButtonA = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: #abcdef;
color: #fff;
`;
// 聲明樣式ButtonB組件
const ButtonB = styled.button`
background: red;
color: #fff;
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
`;
// 對外暴露出去
export {
ButtonA,
ButtonB
}
複製代碼
細心的朋友你會發現,其實兩個按鈕有不少相同的樣式,只有背景顏色不同而已,若是重複寫不少樣式,那麼確定是有不少冗餘的代碼,styled-components中提供了繼承的能力
要建立一個繼承另外一個樣式的新組件,只需將其包裝在styled(繼承的組件)構造函數中便可,以下所示
// 聲明樣式ButtonA組件
const ButtonA = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: #abcdef;
color: #fff;
`;
// 聲明樣式ButtonB組件,同時樣式ButtonB組件繼承了ButtonA組件的樣式,又給自身拓展了樣式,更改了自身的背景色
const ButtonB = styled(ButtonA)`
background: red;
`;
複製代碼
在要使用樣式組件的地方經過import引入ButtonA,ButtonB組件
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import {
ButtonA,
ButtonB
}
from './style'
class Header extends Component {
render(){
return (
<Fragment>
<ButtonA>按鈕A</ButtonA>
<ButtonB>按鈕B</ButtonB>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
複製代碼
樣式組件能夠接收props
對於組件外部定義的屬性,在樣式組件內能夠進行接收,寫一些簡單的邏輯表達式 以下所示:在肯定按鈕組件內設置了一個color屬性,在樣式組件內能夠經過props進行接收
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import {
Button
}
from './style'
class Header extends Component {
render() {
return (
<Fragment>
<Button>取消</Button>
<Button color="red">肯定</Button>
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
複製代碼
在樣式組件內,屬性值能夠經過Es6中的插值表達式,${表達式}的方式進行指定的邏輯判斷,達到本身想要的目的
import styled from "styled-components"; // 引入styled-components
// 聲明樣式Button組件
const Button = styled.button`
width: 100px;
height: 40px;
border-radius: 3px;
outline: none;
border: none;
cursor: pointer;
background: ${props => props.color ? "red": '#fff'};
color: ${(props) => props.color ? '#fff': 'red'};
border: 2px solid red;
margin-right: 15px;
`;
// 對外暴露出去
export {
Button
}
複製代碼
渲染的結果以下所示
這裏只是爲了說明在樣式化組件內部能夠接收props值,有時候,在一些場景下是頗有用的例如:本身封裝一些本身組件,不一樣大小按鈕等等的,經過在組件外部設置屬性值,而後在樣式組件內部進行接收,控制組件的樣式UI形態
固然這種簡單的樣式處理,徹底是能夠用上面繼承的方式去處理的
值得注意的是,在插入背景圖片時,是不支持直接插入的,這樣是不生效的
const Content = styled.div`
width: 550px;
height: 290px;
background:url('./react.jpg');
`;
複製代碼
引入本地圖片必須得經過定義變量的方式來實現,以下所示
import BgImg from './react.jpg'; // 將圖片定義成一個變量的方式來引用
const Content = styled.div`
width: 550px;
height: 290px;
background: url(${BgImg}); // 注意這裏用Es6中的模板語法
`;
複製代碼
.attrs方法支持給組件添加屬性
attrs是一個構造方法,能夠給樣式組件添加自身的額外屬性(這個屬性只容許html標籤原生自有的屬性),不支持自定義屬性,要想添加自定義屬性,只能在jsx元素上進行添加
attrs可接收兩種類型的參數:
參數能夠接收一個對象,經過它添加的屬性,會被合併到樣式組件當中去
參數能夠是一個函數,若是有props值,則可以使用該模式 以下代碼所示
import styled from "styled-components"; // 引入styled-components
// 參數是一個對象
const Input = styled.input.attrs({
placeholder: '請輸入信息',
type: 'text'
})`
width:${props => props.width};
height: ${props => props.size === 'small'? '24px': '40px'};
font-size: 14px;
text-indent: 10px;
border-radius: 3px;
border: 1px solid palevioletred;
display: block;
margin: 0 0 1em;
::placeholder {
color: palevioletred;
}
`
// 對外暴露出去
export {
Input
}
複製代碼
在要引用組件處
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import {
Input
}
from './style'
class Header extends Component {
render() {
return (
<Fragment>
<Input width="150px" placeholder="請輸入帳號" size="small"/>
<Input width="200px" placeholder="請輸入密碼" size='large' type="password" />
</Fragment>
);
}
}
const container = document.getElementById('root');
ReactDOM.render(<Header />, container);
複製代碼
渲染結果以下所示:
若是有參數能夠將樣式組件寫成下面這樣,attrs內可接收一個函數,而且經過props接收外部的屬性值const Input = styled.input.attrs(props => ({ // 參數是一個函數,能夠經過props進行接收
placeholder: '請輸入信息',
type: 'text'
}))`
width:${props => props.width};
height: ${props => props.size === 'small'? '24px': '40px'};
// 以下省略
`
複製代碼
注意:關於樣式的優先級
行內樣式>外部樣式(樣式組件),若是行內元素設置的了默認值,則行內樣式優先
不然,在attrs內設置的屬性會覆蓋外部的屬性
至於何時用attrs
使用attrs將屬性傳遞給樣式化組件
當你但願樣式化組件的每一個實例都具備該prop時使用attrs,換句話說,經過attrs設置的屬性,它是公共的,若是每一個實例須要不一樣的實例時則可直接傳遞props
如何覆蓋默認樣式
有時候,須要覆蓋樣式最粗魯的方式就是在屬性後面加權重,經過!important來完成,但這很容易出錯,而且很容易出問題
具體的實現方式是經過&符號的方式,每添加一個&符號,都會隨機生成一個類樣式
const ButtonB = styled(ButtonA)`
&&& {
color: palevioletred;
font-weight: bold;
}
`
複製代碼
以下圖所示
如何覆蓋內聯樣式
內聯樣式的優先級是最高的,始終優先於外部CSS,所以沒法經過簡單地樣式組件覆蓋它,可是有具體的解決辦法的, 就是使用&[style]和!important加權重的方式
有時候,若是在JSX上聲明瞭行內樣式,可是外部想要覆蓋它,那麼這個時候,&[style]和import加權重的方式就頗有用了的,可是在實際開發中,應該避免使用行內樣式,在這裏只是爲了說明諸如此類的解決辦法
const ButtonB = styled(ButtonA)`
&[style]{
background: blue !important;
font-weight: bold;
}
`;
複製代碼
一樣,每追加一個&符號,都會新添加一個類,在實際的項目中,應當少用行內樣式的,不要一時爽,後面給本身挖坑的
重置全局樣式
對於React中重置默認樣式,它使用的是createGlobalStyle這個函數,須要從styled-components中注入 以下所示:
import { createGlobalStyle } from 'styled-components'
const globalStyle = createGlobalStyle`
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
`
export default globalStyle;
複製代碼
通常而言,把重置樣式單獨的定義成一個文件,單獨的引入到index.js當中去的,全局生效的
須要注意的是:在早先的版本中使用全局的方式是injectGlobal,而這個API已經廢棄,並由styled-components v4中的createGlobalStyle替換了
CSS-module編寫樣式
在使用create-react-app腳手架建立的項目後,該項目是支持css-module的
可是須要注意如下幾點:
樣式文件的名稱必須以xxx.module.css或者xxx.module.scss的形式命名:例如styles.module.css或者styles.module.scss
以變量的形式導入樣式文件,好比:import styles from './style.module.css',若是是直接導入xxx.css,在JSX元素上的className的屬性名稱,是沒法經過變量對象引入樣式的,若是是直接引入樣式,則在className的屬性值中直接引入類名便可
className以變量引用的方式進行添加,例如:className ={styles.counter}
使用sass時,腳手架建立的項目,默認是支持sass的,使用時只須要安裝一下node-sass這個包便可
在根文件夾下定義styles.module.css文件,寫入以下幾行樣式代碼
.counter{
text-align: center;
}
.button{
padding: 0 10px;
}
.spanText{
padding: 0 15px;
}
複製代碼
在使用css-module樣式的文件內,經過import的方式引入該xxx.module.css文件
import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import styles from './styles.module.css'; // 引入styles.module.css,實例化一個styles對象
class Counter extends Component {
constructor(props){
super(props);
this.state = {
count: 0
}
}
increment = () => this.setState({ count: this.state.count + 1 })
decrement = () => this.setState({ count: this.state.count - 1 })
render() {
return (
<Fragment>
<div className={styles.counter}>
<button className={styles.button} onClick={this.decrement}>
-
</button>
<span className={styles.spanText}>{this.state.count}</span>
<button className={styles.button} onClick={this.increment}>
+
</button>
</div>
</Fragment>
)
}
}
const container = document.getElementById('root');
ReactDOM.render(<Counter />, container);
複製代碼
具體效果以下所示:
對於以上的寫法,是咱們在React中常見的寫法,可是若是用styled-components的方式,那又該怎麼樣?以下代碼所示import React, { Fragment, Component } from 'react';
import ReactDOM from 'react-dom';
import styled from "styled-components"; // 引入styled-components
// 在Render函數外定義樣式組件
const CounterWrap = styled.div`
text-align: center;
`
const SpanText = styled.span`
padding: 0 15px;
`
const Button = styled.button`
padding: 0 10px;
`
class Counter extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
increment = () => this.setState({ count: this.state.count + 1 })
decrement = () => this.setState({ count: this.state.count - 1 })
render() {
return (
<Fragment>
<CounterWrap>
<Button onClick={this.decrement}>-</Button>
<SpanText>{this.state.count}</SpanText>
<Button onClick={this.increment}>+</Button>
</CounterWrap>
</Fragment>
)
}
}
const container = document.getElementById('root');
ReactDOM.render(<Counter />, container);
複製代碼
固然你能夠跟以前同樣,把樣式組件單獨的抽離出去的,而後經過export對外暴露出去的,當須要使用時,在另外一文件內經過import引入便可
對於樣式組件的命名:由於是組件,因此約定俗成的首字母大寫,這是爲了區分普通的html標籤元素
小tip:在vs-code推薦插件:vscode-styled-components
下面來總結一些styled-components的一些特性
styled-components支持的特性
支持嵌套,變量和繼承:可使用相似sass,less的語法嵌套,可使用變量來設置不一樣的樣式,使用這些不一樣樣式時只須要給樣式組件傳遞一個參數就能夠了的,在樣式化組件內部能夠經過props來接收外部的的參數值
事件監聽綁定:對於自定義的樣式化組件能夠進行事件監聽的綁定,這正是解決類class聲明的自定義組件,沒法綁定事件監聽的痛點,onEventType事件類型只針對原生HTML標籤才起做用,而樣式化組件正好彌補了這一點
模塊化css:按需引入組件的代碼,避免了一些多餘的代碼
惟一類名,沒有類名錯誤,重複:styled-components生成的樣式生成惟一的類名。永遠沒必要擔憂重複,重疊或拼寫錯誤
更容易的刪除樣式,維護簡單:編寫的樣式都與特定組件相關聯,若是組件未使用(工具能夠檢測到)並被刪除,則全部樣式都將被刪除,保持功能性的單一,達到了高內聚,低耦合的組件化特色
動態樣式:樣式組件內能夠接收參數,很簡單地調整和拓展組件的樣式,而不須要創建不少個 class 類來維護組件的樣式
本文主要講解了React編寫樣式的姿式,並非什麼高大上的內容,比較基礎
經過styled-components第三方庫的的引入,編寫樣式化組件,這個並非必須的,視項目公司團隊而定,不使用這個,經過css-module編寫React的樣式也是能夠的
固然如果使用了styled-components,便解決了一些問題,例如,樣式覆蓋,命名等痛點,以及解決了在類聲明組件當中,沒法給自定義組件綁定事件的問題
本文只是學習了styled-components的一些經常使用的知識,至於更多styled-components詳細的使用:能夠多查閱styled-components的官方文檔的