Icon組件(三)

使用之前我們 react + typescript 搭建的腳手架來(lái)寫(xiě)我們第一個(gè)也是最簡(jiǎn)單的 Icon 組件

開(kāi)發(fā)UI組件庫(kù)-從0開(kāi)始搭建 React + TypeScript(一)

  1. 首先我們先分析一下 一個(gè) Icon 組件需要哪些屬性

    • 應(yīng)該有名字name, 知道是哪一個(gè) icon
    • 應(yīng)該可以附加顏色 color
    • ...
    // TODO:
    <Icon name="iconName" color="#fff" { ...restProps } />
    
  2. 我們分析了Icon 應(yīng)該有哪些屬性, 我們第一步先創(chuàng)建文件

    • lib/Icon/icon.tsx
    import React from 'react'
    
    const Icon = (props) => {
        return <div>icon</div>
    }
    
    export default Icon
    
  3. 我們?cè)贋?Icon 添加第一個(gè)屬性, name

    import React from 'react'
    
    interface IconProps {
      name: string
    }
    
    const Icon: React.FunctionComponent<IconProps> = (props) => {
      return (
         <div>{ props.name }</div>
      )
    }
    
  4. 在寫(xiě)下面的 Icon 代碼之前, 我們先思考一下, 我們從哪里獲取 Icon?
    4.1 我主要從 阿里巴巴圖標(biāo)庫(kù) , 來(lái)下載 svg 圖片,豐富我的 Icon 圖標(biāo), 統(tǒng)一放到一個(gè)文件夾下, 例如: assert/icon
    4.2 webpack 里面使用 svg 需要做一些配置

    svg-sprite.png

    4.3 svg-sprite-loader 作用: svg-sprite-loader會(huì)把你的icon塞到一個(gè)個(gè)symbol中,symbolid如果不特別指定悟衩,就是你的文件名多望。它最終會(huì)在你的html中嵌入這樣一個(gè)svg, 根據(jù)導(dǎo)入的 svg 文件自動(dòng)生成 symbol 標(biāo)簽并插入 html
    4.4 可以參考 未來(lái)必?zé)幔篠VG Sprite技術(shù)介紹

    <svg class="icon" aria-hidden="true">
      <use xlink:href="#icon-xxx"></use>
    </svg>
    
  1. 看了上面的分析, 我們來(lái)簡(jiǎn)單寫(xiě)一下Icon組件
    import React from 'react'
    import './assert/icon/alipay.svg'  // 我們有一個(gè) alipay 的圖標(biāo)
    import './assert/icon/qq.svg'  // 我們有一個(gè) qq 的圖標(biāo)
    
    interface IconProps {
      name: string
    }
    
    const Icon: React.FunctionComponent<IconProps> = (props) => {
      return (
        <svg>
          <use xlinkHref="#alipay"></use>
        </svg>
        <svg>
          <use xlinkHref="#qq"></use>
        </svg>
      )
    }   
    
    5.1 我們引入 svg 時(shí), 如果是通過(guò) import qq from './icon/qq.svg', 引入, 會(huì)被 ts 報(bào)一個(gè)找不到模塊的錯(cuò)誤, 因?yàn)槲覀?import 一個(gè)東西, 對(duì)應(yīng)的應(yīng)該有 導(dǎo)出, 但沒(méi)有, 所以我們可以添加一個(gè)聲明
    export-svg.png
  1. 我們每次 import 一個(gè) svg, 就可以顯示一個(gè)圖標(biāo), 我們希望用戶傳進(jìn)來(lái) name, 就可以使用對(duì)應(yīng)的 icon, 組件化
    icon的name屬性

    使用Icon組件

    html會(huì)有svg的元素, 可以看到icon已經(jīng)到頁(yè)面了
  1. 但是隨著icon 越來(lái)越多, 有幾百個(gè), 我們程序員當(dāng)然不會(huì) import 幾百遍, 那該怎么解決呢?

    • 利用 webpack 的 `require.context
      require.context會(huì)返回一個(gè)函數(shù)扼倘,并且該函數(shù)有keys()按价,id, resolve() 屬性
      
      keys()方法返回的該模塊可以處理的所有可能請(qǐng)求的模塊的數(shù)組瓢湃,簡(jiǎn)單一點(diǎn)就是滿足該參數(shù)的模塊矢炼;
      resolve()返回的是請(qǐng)求的module的id;
      id是該context module的id;
      
    • 我們?cè)?lib/Icon/ 創(chuàng)建一個(gè) importAllIcon.js
      打印里面的每一項(xiàng)可以查看對(duì)應(yīng)值
    打印require.context會(huì)把目錄下的icons 全部映射
  1. 這樣我們就不用一個(gè)一個(gè)引入icon, 可以使用有 nameicon
    icon 初步完成
  1. 初步完成了 Icon 組件, 我們來(lái)思考一個(gè)問(wèn)題, 是應(yīng)該一個(gè)一個(gè) import, 還是 importAll

    • 使用一個(gè)一個(gè)import , 好處是 tree-shaking: 把沒(méi)有依賴的刪除掉, 使用依賴的, tree-shaking基礎(chǔ)是靜態(tài)引入
    • importAll, 主要是方便, 但是沒(méi)辦法 tree-shaking
  2. 現(xiàn)在我們來(lái)為我們的 Icon 來(lái)寫(xiě)一些簡(jiǎn)單地樣式, 可以使用less scss styluscss擴(kuò)展語(yǔ)言, 這里我使用的是 scss, 但webpack 需要一些配置

// webpack.config.js
// 我們需要安裝這些依賴
  module: {
    rules: [
      // 配置 ts tsx
      {
        test: /\.tsx?$/,
        loader: "awesome-typescript-loader"
      },
      // 配置svg
      {
        test: /\.svg$/,
        loader: "svg-sprite-loader"
      },
      // 配置 scss-loader
      // npm i css-laoder style-loader sass-loader
      {
        test: /\.s[ac]ss$/,
        use: ["style-loader", "css-loader", "sass-loader"]
      },
    ]
  }

這幾個(gè)loader作用, 先使用 sass-loader, 遇見(jiàn) icon.scss 文件, 把這個(gè)文件解析為 類似 icon.css, css-loader 會(huì)把 icon.css變?yōu)?對(duì)象或者字符串, style-loader 會(huì)把這些對(duì)象變?yōu)橐粋€(gè) style標(biāo)簽, 里面是 css
document.createElement('style')
style.innerHTML = '對(duì)象css'
documnet.head.appendChild('style')
一個(gè) loader 只做一件事情
  1. 我們可以使用了icon, 通過(guò)不同的name 展示 不同的 icon, 現(xiàn)在還有什么我可以做的呢? 我們可以給 icon 加一個(gè) click 事件
// icon.tsx
import React from "react";
import "./importAllIcons.js";

interface IconProps {
  name: string;
  // ? 可選擇的屬性   類型是 svg元素的鼠標(biāo)事件處理函數(shù) 
  onClick?: React.MouseEventHandler<SVGElement>;
}
const Icon: React.FunctionComponent<IconProps> = (props: any) => {
  return (
    // 接收這個(gè) click 事件
    <svg className="yym-icon" onClick={props.onClick}>
      <use xlinkHref={`#${props.name}`}></use>
    </svg>
  );
};
export default Icon;
// 使用 click 事件
import React from "react";
import Icon from "./icon.tsx";

const IconDemo = () => {
  // 函數(shù)類型, 鼠標(biāo)事件
  const fn: React.MouseEventHandler = e => {
    console.log("我被點(diǎn)擊了", e);
    console.log("我被點(diǎn)擊了", e.target);
  };
  return (
    <div>
      <Icon name="zhihu-circle" onClick={fn} />
    </div>
  );
};
export default IconDemo;
  1. 我們僅僅做了click 事件還是不夠, 如果我們希望icon 有其他的事件, 比如onMouseMove , onMouseLeave 等事件, 我們?nèi)绻粋€(gè)一個(gè)把我們需要的事件加上去, 就太麻煩了, 那我們應(yīng)該怎么做呢?
  • 我們知道我們使用的是 svg 標(biāo)簽, 所以我們的 IconProps 也屬于 React.SVGAttributes 的屬性
// IconProps 繼承 SVG 的 所有屬性
interface IconProps extends React.SVGAttributes<SVGElement> {
  name: string;
}
const Icon: React.FunctionComponent<IconProps> = (props: any) => {
  return (
    <svg
      className="yym-icon"
      onClick={props.onClick}
      onMouseEnter={props.onMouseEnter}
      onMouseLeave={props.onMouseLeave}
    >
      <use xlinkHref={`#${props.name}`}></use>
    </svg>
  );
};
  • 但是上面也有優(yōu)化空間, 我們不需要一個(gè)一個(gè)的接收所有事件,
// IconProps 繼承 SVG 的 所有屬性
interface IconProps extends React.SVGAttributes<SVGElement> {
  name: string;
}
const Icon: React.FunctionComponent<IconProps> = (props: any) => {
  return (
    // 使用展開(kāi)運(yùn)算符
    <svg className="yym-icon" {...props}>
      <use xlinkHref={`#${props.name}`}></use>
    </svg>
  );
};
  1. 繼續(xù)上一步,完成后, 我們思考一下, 上面我們有自己寫(xiě)的 class, 如果使用的人也寫(xiě)了自己定義的 class, 那會(huì)覆蓋我們的 className, 所一我們需要改造一下我們的className
    1. 我們可以使用已經(jīng)有的 classnames 庫(kù)
    1. 我們也可以使用const { className, ...restProps } = props
const Icon: React.FunctionComponent<IconProps> = (props: any) => {
  const { className, ...restProps } = props;
  return (
    <svg className={`yym-icon ${className}`} {...restProps}>
      <use xlinkHref={`#${props.name}`}></use>
    </svg>
  );
};
  • 上面后又一點(diǎn)點(diǎn)問(wèn)題, 如果用戶不傳className, class 會(huì)有 undefined, 或者多一個(gè)空格 所以我們自己來(lái)簡(jiǎn)單封裝一個(gè)類 classnames 的庫(kù)
// helpers/classes.tsx

// filter  Boolean 你給我什么值, 返回什么值  Boolean(1) true  Boolean(0) false
// 需要傳一個(gè) string 或 undefined 數(shù)組的值, 簡(jiǎn)單實(shí)現(xiàn)
const classes = (...names: (string | undefined)[]) => {
  return names.filter(Boolean).join(" ");
};

export default classes;
// Icon.tsx
import classes from "../helpers/classes";
interface IconProps extends React.SVGAttributes<SVGElement> {
  name: string;
}
const Icon: React.FunctionComponent<IconProps> = (props: any) => {
  const { className, ...restProps } = props;
  return (
    <svg className={classes("yym-icon", className)} {...restProps}>
      <use xlinkHref={`#${props.name}`}></use>
    </svg>
  );
};
export default Icon;
  1. 現(xiàn)在我們的 Icon, 已經(jīng)可以滿足 通過(guò)傳 name 顯示不同的 icon, 可以為icon 定義事件, 我們當(dāng)然也可以通過(guò)傳class 修改我們的 icon 的樣式, 那我們有需要為 icon 單獨(dú)的 color 屬性, 來(lái)只修改 icon的顏色嗎?, 我們可以做也可以不做, 不過(guò)為了方便, 我們可以做的更完美一點(diǎn)
  • svg 更改顏色是通過(guò) fill 屬性來(lái)更改的, 我們動(dòng)態(tài)修改 fill 的值, 就可以直接修改 icon 的顏色了
const Icon: React.FunctionComponent<IconProps> = (props: any) => {
  const { className, color, ...restProps } = props;
  return (
    <svg
      style={{ fill: color }}
      className={classes("yym-icon", className)}
      {...restProps}
    >
      <use xlinkHref={`#${props.name}`}></use>
    </svg>
  );
};
  1. 我們寫(xiě)到這里, 先看一下我們的代碼都滿足哪些東西, 優(yōu)化一下我們的代碼.
import React from "react";
import "./importAllIcons.js";
import classes from "../helpers/classes";

interface IconProps extends React.SVGAttributes<SVGElement> {
  name: string;
}
const Icon: React.FunctionComponent<IconProps> = (props: any) => {
  // 這里可以解構(gòu)
  const { className, color, name, ...restProps } = props;
  return (
    <svg
      style={{ fill: color }}
      className={classes("yym-icon", className)}
      {...restProps}
    >
      <use xlinkHref={`#${props.name}`}></use>
    </svg>
  );
};
export default Icon;
// 優(yōu)化后的完整代碼
import React from "react";
import "./importAllIcons.js";
import classes from "../helpers/classes";

interface IconProps extends React.SVGAttributes<SVGElement> {
  name: string;
}
const Icon: React.FunctionComponent<IconProps> = ({
  className,
  color,
  name,
  ...restProps
}) => {
  return (
    <svg
      className={classes("yym-icon", className)}
      style={{ fill: color }}
      {...restProps}
    >
      <use xlinkHref={`#${name}`}></use>
    </svg>
  );
};
export default Icon;
  1. 最后, 上面是我們的 Icon 組件的源代碼, 我們?cè)趺词褂媚?
// 使用 Icon
import React from "react";
import Icon from "./Icon/icon";

const Demo = () => {
  const fn: React.MouseEventHandler = e => {
    console.log("我被點(diǎn)擊了", e);
    console.log("我被點(diǎn)擊了", e.target);
  };
  return (
    <div>
      <Icon name="alert" />
      <Icon name="alert" className="icon" />
      <Icon name="alert" className="icon" color="red" />
      <Icon name="alert" className="icon" color="red" onClick={fn} />
    </div>
  );
};
export default Demo;

Icon 組件完成了, 我們繼續(xù)使用 ts 寫(xiě)下一個(gè)組件

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市慷垮,隨后出現(xiàn)的幾起案子揖闸,更是在濱河造成了極大的恐慌,老刑警劉巖料身,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件汤纸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡芹血,警方通過(guò)查閱死者的電腦和手機(jī)贮泞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)幔烛,“玉大人啃擦,你說(shuō)我怎么就攤上這事《鲂” “怎么了令蛉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)狡恬。 經(jīng)常有香客問(wèn)我珠叔,道長(zhǎng)蝎宇,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任运杭,我火速辦了婚禮夫啊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘辆憔。我一直安慰自己撇眯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布虱咧。 她就那樣靜靜地躺著熊榛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腕巡。 梳的紋絲不亂的頭發(fā)上玄坦,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音绘沉,去河邊找鬼煎楣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛车伞,可吹牛的內(nèi)容都是我干的择懂。 我是一名探鬼主播,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼另玖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼困曙!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起谦去,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤慷丽,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后鳄哭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體要糊,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年妆丘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了锄俄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡飘痛,死狀恐怖珊膜,靈堂內(nèi)的尸體忽然破棺而出容握,到底是詐尸還是另有隱情宣脉,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布剔氏,位于F島的核電站塑猖,受9級(jí)特大地震影響竹祷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜羊苟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一塑陵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜡励,春花似錦令花、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至稽寒,卻和暖如春扮碧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杏糙。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工慎王, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人宏侍。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓赖淤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親负芋。 傳聞我的和親對(duì)象是個(gè)殘疾皇子漫蛔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355