React進階(十)實戰(zhàn)演練之Icon組件

在實際應用中嚷闭,圖標的使用無處不在勤众,小到簡書的編譯頁面掩浙,大到chrome瀏覽器的任務欄等花吟,都有大量的圖標需要處理,那如果我們自己的應用里也需要使用代表各種含義的圖標時厨姚,我們應該怎么處理呢衅澈?是每一個圖標都引入一張圖片來做嗎?

當然不是 谬墙。

可愛的小圖標們

利用react今布,我將試圖創(chuàng)建一個Icon組件经备,我們可以在使用時,控制圖標的顏色部默,大小侵蒙,旋轉(zhuǎn),圖標類型等傅蹂。大概如下:

<Icon color="red" size="small" type="skip" />
<Icon color="#FFF" size="small" type="loading" />

那么我們應該怎么實現(xiàn)呢纷闺?

一、構建工具集成sass

我通常更喜歡使用預編譯工具sass來代替css份蝴,如果讀者朋友們還沒有了解過它急但,可以花10分鐘時間搜索學習,非常的簡單搞乏。

首先下載sass-loader, node-sass

> yarn add sass-loader node-sass

然后修改webpack的配置如下:

{
  test: /\.s[ac]ss$/,
  include: paths.appSrc,
  loaders: ["style-loader", "css-loader", "sass-loader"]
},

注意波桩,隨著版本的更新,方式可能有所調(diào)整请敦,以create react app 官方文檔為準

重啟項目即可生效镐躲。

二、字體圖標

最初見到字體圖標的應用侍筛,還是在淘寶網(wǎng)站上萤皂。神奇的發(fā)現(xiàn)有的圖標居然可以像字體一樣,隨意的給它設置顏色大小等屬性匣椰。而到了現(xiàn)在裆熙,字體圖標早已不是什么黑科技了,它幾乎被普及到了所有網(wǎng)站禽笑。

在css3中入录,有一個語法可以自定義字體@font-face。而這些字體庫如果是由圖標組成佳镜,那么我們就可以創(chuàng)建字體圖標了僚稿。字體圖標與文字具有相同的特性,我們可以把圖標當成字體一樣處理蟀伸。例如修改它的font-size蚀同,color等。對應的css語法如下:

@font-face {
    font-family: 'custom name',   /* 自定義字體名字 */
    src: url('./fonts/custom.eot')  /* 下載到本地的字體庫 */
}

通常情況下啊掏,字體庫中蠢络,每一個圖標,都會對應一個唯一的標識碼〕倜郏現(xiàn)在我們要通過字體圖標網(wǎng)站iconfont收集一個自己項目中會涉及到的圖標刹孔。然后組成一個圖標庫。

點擊第一個購物車圖標小泉,即可將圖標收集芦疏。按需收集一些圖標冕杠,統(tǒng)一添加到一個項目中。

我們可以看到酸茴,圖標安靜的躺在我們的項目中分预。

可以使用線上圖標庫。點擊查看在線鏈接并且生成代碼即可薪捍。我的項目生成的在線代碼如下:

@font-face {
  font-family: 'iconfont';  /* project id 496908 */
  src: url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.eot');
  src: url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.eot?#iefix') format('embedded-opentype'),
  url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.woff') format('woff'),
  url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.ttf') format('truetype'),
  url('//at.alicdn.com/t/font_496908_9n2jkt8rov1xxbt9.svg#iconfont') format('svg');
}

將這段代碼貼到我們的css文件中笼痹,就已經(jīng)自定義了一個font-family為iconfont的字體圖標。我們也可以將字體圖標庫下載下來酪穿,把url中的路徑都修改為對應的字體庫文件就行凳干。

可以看到,每一個圖標除了有一個對應的名字之外被济,還有一個唯一的unicode碼救赐。&#x表示他們后面跟的是16進制數(shù)字。假設我們期望在html中放入一個代表圖標的標簽只磷。

<i class="icon-loading" />

那么经磅,只要它對應的css這樣寫,就可以在頁面中顯示出字體庫中的圖標钮追。

.icon-loading {
  font-family: "iconfont";
  color: red;
  font-size: 20px;
}
.icon-loading:before {
  content: "\e602";
}

content的值是一個斜杠加上圖標對應的十六進制數(shù)字预厌。運行之后我們就能在頁面中看到一個紅色的斜體loading圖標。

對應的scss寫法為:

/* 對應的scss寫法為 */
.icon-loading {
  font-family: "iconfont";
  color: red;
  font-size: 20px;
  &:before {
    content: "\e602";
  }
}

業(yè)

三元媚、字體圖標組件

很顯然圖標組件的封裝不會涉及到太過于復雜的JS邏輯處理轧叽,更多的是對外部狀態(tài)props的判斷與處理】兀基礎元素可以指定一個i標簽炭晒。圖標通過before/after偽類中的content顯示。實現(xiàn)方法我們將每一個圖標都對應寫一個class鞠绰,然后根據(jù)傳入的type類型腰埂,動態(tài)的修改對應的class即可飒焦。

例如

/* loading */
.icon-loading {
    /* ... */
    &:before { content: '\e602' }
}

.icon-refresh {
    /* ... */
    &:before { content: '\e6aa' }
}

js的邏輯的處理主要是根據(jù)傳入的參數(shù)蜈膨,判斷有哪些class名應該存在。

先思考一下組件封裝好后牺荠,我們會遇到哪些情況翁巍。

第一個基本情況,就是簡單的傳入type休雌,得到對應的圖標顯示灶壶。

<Icon type="close" />

第二種情況,是組件本身需要設置一些樣式杈曲,因此可能會有通過添加class的方式定義css樣式驰凛。

<Icon className="close" type="close" />

第三種情況胸懈,則是要直接修改圖標的樣式,例如設置顏色恰响,字體大小等趣钱。

// 第一種可以直接傳入對應的屬性
<Icon type="close" color="red" />

// 另外一種是利用jsx支持的style語法,傳入css樣式胚宦。
const style = {
    color: 'red',
    fontSize: '20px'
}
<Icon type="close" style={style} />

第四種情況是我們要考慮特殊的類型首有,例如loading圖標需要一直旋轉(zhuǎn)。例如refresh刷新圖標枢劝,點擊時才旋轉(zhuǎn)井联,刷新完成就停止旋轉(zhuǎn)。因此我們要專門針對這種情況做特殊處理您旁。添加一個控制選擇的屬性烙常。

// 通過對spin的修改,來控制圖標是否旋轉(zhuǎn)
<Icon type="refresh" spin={true} />

其余的我們可能在實踐中還會添加新的需求鹤盒,到時候再根據(jù)需求做改進即可军掂。

OK,帶著這些基礎知識和需求昨悼,我們開始動手來完成我們的第一個正式的React組件蝗锥。

在src目錄下,創(chuàng)建一個專門用來存放組件的文件夾率触,components终议。然后在components目錄下創(chuàng)建Icon目錄。并分別創(chuàng)建index.jsx與style.scss葱蝗。我們將字體圖標下載下來穴张,存放于Icon目錄的fonts目錄中。

最終的文件結構大致如下:

+ Icon
  + fonts
  - index.jsx
  - style.scss

通過上面的分析我們知道两曼,基礎元素的class可能會涉及到很多個皂甘,如果通過if/else來判斷的話,可能我們的代碼可讀性會非常的低悼凑。因此這里我們借助一個專門處理class名的工具方法來完成邏輯的判斷偿枕。這個工具庫叫做classnames。

我們先安裝這個庫户辫,然后重啟項目渐夸。

> yarn add classnames

該工具方法的使用比較簡單,它的目的在于拼接class名渔欢。

import classnames from 'classnames';

// 拼接所有參數(shù)
classnames('foo', 'bar');  // 'foo bar'

// 拼接值為true的參數(shù)
classnames({
    foo: true,
    bar: false
})  //  'foo'

// 也可以比較隨意的混合使用
classnames('foo', {
    bar: true,
    tag: true,
    mm: false
}) // 'foo bar tag'

更具體的用法可以查看npm中的文檔

現(xiàn)在我們先來實現(xiàn)index.jsx中的代碼編寫墓塌。

首先引入必要的模塊。

import React from 'react';
import classnames from 'classnames';
import './style.scss';

然后給可能會接收的props設定一個默認值。

const defaultProps = {
    type: '',
    spin: false
}

定義組件苫幢,因為僅僅只是一個UI展示访诱,所以該組件是一個無狀態(tài)組件,我們用function的方式來定義即可韩肝。

上一章主要介紹的是有狀態(tài)的創(chuàng)建方式盐数,沒有涉及到這種方式,不過可以通過該例子直接掌握伞梯,不再特別描述

const Icon = (props = defaultProps) => {
  // 依次從props中取出可能會出現(xiàn)的值玫氢,此處的other表示其余所有剩余的屬性,這是ES6的語法
  const { type, className, spin, color, style, ...other } = props;

  // 利用classnames方法計算出最終的classname字符串谜诫。
  const cls = classnames({
    'icon': true,
    'icon-spin': !!spin || type === 'loading',
    [`icon-${type}`]: true
  }, className);

  const _style = { ...style, color };

  return (
    <i className={cls} {...other} style={_style} /> 
  )
}

完整代碼為

import React from 'react';
import classnames from 'classnames';
import './style.scss';

const defaultProps = {
  type: '',
  /**
   * 是否旋轉(zhuǎn)
   */
  spin: false
}

const Icon = (props = defaultProps) => {
  // 依次從props中取出可能會出現(xiàn)的值漾峡,此處的other表示其余所有剩余的屬性,這是ES6的語法
  const { type, className, spin, color, style, ...other } = props;

  // 利用classnames方法計算出最終的classname字符串喻旷。
  const cls = classnames({
    'icon': true,
    'icon-spin': !!spin || type === 'loading',
    [`icon-${type}`]: true
  }, className);

  const _style = { ...style, color };

  return (
    <i className={cls} {...other} style={_style} /> 
  )
}

export default Icon;

因為涉及到比較多的ES6語法的使用生逸,如果還有ES6語法掌握不夠熟練的同學,可以花半個小時熟悉一下ES6的的基本語法且预。前幾個例子槽袄,我會盡量注釋,等多幾個例子之后锋谐,我就可能不會注釋太多遍尺,相信到時候大家對ES6的掌握也比較熟練了。

接來下是css的實現(xiàn)涮拗。

/**
 * 圖標項目地址
 * http://iconfont.cn/manage/index?spm=a313x.7781069.1998910419.db775f1f3&manage_type=myprojects&projectId=597416&keyword=
 */

@font-face {
  font-family: "iconfont";
  src: url('./fonts/iconfont.eot?t=1543309990807'); /* IE9*/
  src: url('./fonts/iconfont.eot?t=1543309990807#iefix') format('embedded-opentype'), /* IE6-IE8 */
  url('./fonts/iconfont.ttf?t=1543309990807') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
  url('./fonts/iconfont.svg?t=1543309990807#iconfont') format('svg'); /* iOS 4.1- */
}

.icon {
  font-family: "iconfont" !important;
  font-size: 16px;
  font-style: normal;
  display: block;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.icon-add::before { content: '\e65b' }
.icon-step:before { content: '\e65a'; }
.icon-complete:before { content: '\e658'; }
.icon-clock:before { content: '\e67d'; }
.icon-selected:before { content: '\e674'; }
.icon-phone:before { content: '\e67c'; }
.icon-share:before { content: '\e67b'; }
.icon-advisory:before { content: '\e67a'; }
.icon-car:before { content: '\e679'; }
.icon-star:before { content: '\e678'; }
.icon-starno:before { content: '\e677'; }
.icon-count:before { content: '\e676'; }
.icon-add2:before { content: '\e675'; }
.icon-check:before { content: '\e673'; }
.icon-gift:before { content: '\e672'; }
.icon-programe:before { content: '\e671'; }
.icon-order:before { content: '\e670'; }
.icon-adviser:before { content: '\e66f'; }
.icon-activity:before { content: '\e66e'; }
.icon-coupon:before { content: '\e66d'; }
.icon-aboutus:before { content: '\e66c'; }
.icon-envelope:before { content: '\e66b'; }
.icon-eyehide:before { content: '\e66a'; }
.icon-category:before { content: '\e669'; }
.icon-volume:before { content: '\e668'; }
.icon-pk:before { content: '\e667'; }
.icon-slider:before { content: '\e664'; }
.icon-scenes:before { content: '\e661'; }
.icon-full:before { content: '\e660'; }
.icon-eyeshow:before { content: '\e65f'; }
.icon-forward:before { content: '\e65e'; }

.icon-spin {
  animation-name: rotate;
  animation-duration: 1s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}

@keyframes rotate {
  from {
    transform: rotate(0)
  }
  to {
    transform: rotate(360deg)
  }
}

OK乾戏,一個簡單卻非常nice的Icon組件就這樣完成了。

業(yè)

我相信對于剛接觸React的朋友來說三热,特別是對ES6運用不太熟練的朋友鼓择,這個例子雖然簡單,卻有許多值得總結的經(jīng)驗就漾,建議大家動手實踐呐能,細細回味。淡化對React的緊張感與重視感抑堡,從大局去體會React的魅力摆出,這比React本身的知識點更為重要。

最后寫一個例子來簡單瞄一眼我們的圖標組件

import React from 'react';
import ReactDOM from 'react-dom';
import Icon from 'components/Icon';
import { icons } from 'components/Icon/config';

const container = {
  display: 'flex',
  maxWidth: '300px',
  margin: 'auto',
  justifyContent: 'space-around',
  flexWrap: 'wrap'
}

const itemContainer = {
  width: '40px',
  height: '40px',
  display: 'flex',
  justifyContent: 'center',
  alignItems: 'center'
}

class App extends React.Component {
  render() {
    return (
      <div style={container}>
        <Icon type="add" spin />
        {icons.map((item, i) => (
          <div key={i} style={itemContainer}>
            <Icon type={item} />
          </div>
        ))}
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'));
可愛的小圖標們
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末夷野,一起剝皮案震驚了整個濱河市懊蒸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悯搔,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妒貌,居然都是意外死亡通危,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門灌曙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來菊碟,“玉大人,你說我怎么就攤上這事在刺∧婧Γ” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵蚣驼,是天一觀的道長魄幕。 經(jīng)常有香客問我,道長颖杏,這世上最難降的妖魔是什么纯陨? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮留储,結果婚禮上翼抠,老公的妹妹穿的比我還像新娘。我一直安慰自己获讳,他們只是感情好阴颖,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著丐膝,像睡著了一般膘盖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尤误,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天侠畔,我揣著相機與錄音,去河邊找鬼损晤。 笑死软棺,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的尤勋。 我是一名探鬼主播喘落,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼最冰!你這毒婦竟也來了瘦棋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤暖哨,失蹤者是張志新(化名)和其女友劉穎赌朋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡沛慢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年赡若,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片团甲。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡逾冬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出躺苦,到底是詐尸還是另有隱情身腻,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布匹厘,位于F島的核電站嘀趟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏集乔。R本人自食惡果不足惜去件,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扰路。 院中可真熱鬧尤溜,春花似錦、人聲如沸汗唱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽哩罪。三九已至授霸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間际插,已是汗流浹背碘耳。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留框弛,地道東北人辛辨。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像瑟枫,于是被迫代替她去往敵國和親斗搞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 第一部分 HTML&CSS整理答案 1. 什么是HTML5慷妙? 答:HTML5是最新的HTML標準僻焚。 注意:講述HT...
    kismetajun閱讀 27,422評論 1 45
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,506評論 25 707
  • 作為一個合格的開發(fā)者,不要只滿足于編寫了可以運行的代碼膝擂。而要了解代碼背后的工作原理虑啤;不要只滿足于自己的程序...
    六個周閱讀 8,422評論 1 33
  • “非暴力溝通”的創(chuàng)始人馬歇爾·盧森堡博士說因為憤怒而殺人太膚淺了隙弛。 他認為殺人、打人咐旧、罵人都無法真正傳達我們的心聲...
    歲月蓮上寫詩閱讀 579評論 0 0
  • 生活和工作中經(jīng)常遇到意外和突發(fā)事件驶鹉,那么碰到的時候如何才能提高自己的應變能力绩蜻,根據(jù)總結積累給出三點建議給大家參考:...
    Gwenduly_W2016閱讀 356評論 0 0