本輪子是通過(guò) React + TypeScript + Webpack 搭建的,至于環(huán)境的搭建這邊就不在細(xì)說(shuō)了,自己動(dòng)手谷歌吧。當(dāng)然可以參考我的源碼。
這里我也是通過(guò)別人學(xué)的,主要做些總結(jié)及說(shuō)明造各個(gè)輪子的一種思路,方便今后使用別人的的輪子時(shí)自己腦中有造輪子的思想,能通過(guò)修改源碼及時(shí)修改 bug,按時(shí)上線。
本文的 Icon 組件主要是參考 Framework7 中的 Icon React Component 寫的。
想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳GitHub博客,一年百來(lái)篇優(yōu)質(zhì)文章等著你!
為什么要造輪子1.為了不求人
假設(shè)你使用某個(gè)UI框架發(fā)現(xiàn)有一個(gè) bug,于是你反饋給開發(fā)者,開發(fā)者說(shuō)兩周后修復(fù),而你的項(xiàng)目一周后就要上線,你怎么辦?
為什么很多大公司都不使用其他公司的輪子,要自己造?為了把控自己的業(yè)務(wù),不被別人牽著走。
2.為了不流于平庸
大家都是寫增刪改查,你跟別人比有什么優(yōu)勢(shì)?你如果能說(shuō)一局【我公司的人都在用我寫的UI框架】是不是就很牛逼?造 UI 輪子會(huì)遇到很多技術(shù)層面而非業(yè)務(wù)層面的知識(shí)?比如一些算法。
3.為了創(chuàng)造
你為別人做了這么久的事情,有沒(méi)有自己做什么?自驅(qū)動(dòng)力。
4.為什么是 UI 輪子,不是其他方面的輪子
比如,為什么不自己寫一個(gè) React 框架,要寫 React UI 框架呢?
React.FunctionComponent 與 IconPropps本輪子使用 React + TypeScript 來(lái)寫的,那么在 ts 中如何聲明函數(shù)組件及級(jí) Icon 組件傳遞參數(shù)呢,答案是使用React提供的靜態(tài)方法 React.FunctionComponent 及 TypeScript 提供的接口定義。
// lib/icon.tsx import React from "react" interface IconProps { name: string } const Icon: React.FunctionComponent= () => { return ( icon ) } export default Icon
在 index.txt 中調(diào)用:
import React from "react"; import ReactDOM from "react-dom"; import Icon from "./icon" ReactDOM.render(, document.body)
對(duì)于上面的定義方式,后面的輪子會(huì)經(jīng)常使用,所以不必?fù)?dān)心看不懂。
使用 svg-sprite-loader 加載 SVG在上面我們指定了 Icon 的name為wechat,那怎么讓它顯示微信的圖標(biāo)呢,首先在阿里的 Iconfont 下載對(duì)應(yīng)的 SVG
接著如何顯示 svg");rules 中添加:
{ test: /.svg$/, loader: "svg-sprite-loader" }
在 Icon 中引用,當(dāng)然對(duì)應(yīng) tsconfig.json 也要配置(這不是本文的重點(diǎn)):
import React from "react" import wechat from "./icons/wechat.svg" console.log(wechat) interface IconProps { name: string } const Icon: React.FunctionComponent= () => { return ( ) } export default Icon
運(yùn)行效果:
當(dāng)然 svg 里面不能直接寫死,我們需要根據(jù)外部傳入的 name 來(lái)指定對(duì)應(yīng)的圖像:
// 部分代碼 import "./icons/wechat.svg" import "./icons/alipay.svg" const Icon: React.FunctionComponent= (props) => { return ( ) }
外部調(diào)用:
ReactDOM.render(, document.getElementById("root"))
運(yùn)行效果:
importAll
大家有沒(méi)有注意到,我需要使用哪個(gè) svg, 需要在對(duì)應(yīng)的 icon 組件導(dǎo)入對(duì)應(yīng)的 svg,這樣要是我需要100個(gè) svg ,我就要導(dǎo)入100次,這樣做太傻,文件也會(huì)變得冗長(zhǎng)。
因此我們需要一個(gè)動(dòng)態(tài)導(dǎo)入全部 SVG 的方法:
// lib/importIcons.js let importAll = (requireContext) => requireContext.keys().forEach(requireContext) try { importAll(require.context("./icons/", true, /.svg$/)) } catch (error) { console.log(error) }
要想看懂上訴的代碼,可能需要一點(diǎn) node.js 的基礎(chǔ),這邊建議你直接收藏好啦,下次有用到,直接拷貝過(guò)來(lái)用就行了。
接著在 Icon 組件里面導(dǎo)入就行了: import "./importIcons"
React.MouseEventHandler 的使用當(dāng)我們需要給 Icon 注冊(cè)事件的時(shí)候,如果直接在組件上寫 onClick 事件是會(huì)報(bào)錯(cuò)的,因?yàn)樗鼪](méi)有聲明接收 onClick 事件類型,所以需要聲明,如下所示:
/lib/icon.tsx import React from "react" import "./importIcons" import "./icon.scss"; interface IconProps { name: string, onClick: React.MouseEventHandler} const Icon: React.FunctionComponent = (props) => { return ( ) } export default Icon
調(diào)用方式如下:
import React from "react"; import ReactDOM from "react-dom"; import Icon from "./icon" const fn: React.MouseEventHandler = (e) => { console.log(e.target); }; ReactDOM.render(讓Icon響應(yīng)所有事件, document.getElementById("root"))
上述我們只監(jiān)聽了 onClick 事件 ,但對(duì)于其它事件是不支持了,所以我們需要進(jìn)一步完善。這里我們不能一個(gè)一個(gè)添加對(duì)應(yīng)的事件類型,需要一個(gè)統(tǒng)一的事件類型,那這個(gè)是什么呢?
通過(guò) react 我們會(huì)找到一個(gè) SVGAttributes 類,這里我們需要繼承它:
/lib/icon.tsx import React from "react" import "./importIcons" import "./icon.scss"; interface IconProps extends React.SVGAttributes{ name: string; } const Icon: React.FunctionComponent = (props) => { return ( ) } export default Icon
調(diào)用方式:
import React from "react"; import ReactDOM from "react-dom"; import Icon from "./icon" const fn: React.MouseEventHandler = (e) => { console.log(e.target); }; ReactDOM.render(, document.getElementById("root"))console.log("enter")} onMouseLeave = { () => console.log("leave")} />
上述還是會(huì)有問(wèn)題,我們還有 onFocus, onBlur, onChange 等等事件,也不可能一個(gè)一個(gè)傳遞進(jìn)來(lái),那還有什么方法呢。
在 icon.tsx 中我們會(huì)發(fā)現(xiàn)我們用的都是通過(guò) props 傳遞進(jìn)來(lái)的。聰明的朋友的可能立馬想到了使用展開運(yùn)算符的形式 {...props},改寫如下:
... const Icon: React.FunctionComponent= (props) => { return ( ) } ...
上述還是會(huì)有問(wèn)題,如果使用的人也傳入 className 呢,用過(guò) Vue 就知道 Vue 是真的好,它會(huì)把傳入和里面的合并起來(lái),但 React 就不一樣了,傳入的會(huì)覆蓋里面的,所以需要自己手動(dòng)處理:
... const Icon: React.FunctionComponent= (props) => { const { className, ...restProps} = props return ( ) } ...
上達(dá)寫法還存在問(wèn)題的,如果外面沒(méi)有寫 className ,那么內(nèi)部會(huì)多出一個(gè) undefined
聰明你的可能就想到了使用三目運(yùn)算符來(lái)做判斷,如:
className={`fui-icon ${className ");
但這種情況如果有多個(gè)參數(shù)要怎么辦呢?
所以有人就非常聰明專門寫了一個(gè)庫(kù)存 classnames,這個(gè)庫(kù)有多火呢,每周有300多萬(wàn)的下載量,它的作用就是處理 className 的情況。
當(dāng)然我們這邊只做簡(jiǎn)單的處理,如下所示
// helpers/classes function classes(...names:(string | undefined )[]) { return names.join(" ") } export default classes
使用方式:
... const Icon: React.FunctionComponent= (props) => { const { className, name,...restProps} = props return ( ) } ...
這樣最終渲染出來(lái)的 className還是會(huì)多出一個(gè)空格,作為完美者,并不希望有空格的出現(xiàn)的,所以需要進(jìn)一步處理空格,這里使用 es6 中數(shù)組的 filters 方法。
// helpers/classes function classes(...names:(string | undefined )[]) { return names.filter(Boolean).join(" ") } export default classes單元測(cè)試
首先我們對(duì)我們的 classes 方法時(shí)行單元測(cè)試,這里使用 Jest 時(shí)行測(cè)試,也是 React 官網(wǎng)推薦的。
classes 測(cè)試用例如下:
import classes from "../classes" describe("classes", () => { it("接受 1 個(gè) className", () => { const result = classes("a") expect(result).toEqual("a") }) it("接受 2 個(gè) className", ()=>{ const result = classes("a", "b") expect(result).toEqual("a b") }) it("接受 undefined 結(jié)果不會(huì)出現(xiàn) undefined", ()=>{ const result = classes("a", undefined) expect(result).toEqual("a") }) it("接受各種奇怪值", ()=>{ const result = classes( "a", undefined, "中文", false, null ) expect(result).toEqual("a 中文") }) it("接受 0 個(gè)參數(shù)", ()=>{ const result = classes() expect(result).toEqual("") }) })使用Snapshot測(cè)試UI
這里測(cè)試 UI 相關(guān)還需要使用一個(gè)庫(kù) Enzyme , Enzyme 來(lái)自 airbnb 公司,是一個(gè)用于 React 的 JavaScript 測(cè)試工具,方便你判斷、操縱和歷遍 React Components 輸出。Enzyme 的 API 通過(guò)模仿 jQuery 的 API ,使得 DOM 操作和歷遍很靈活、直觀。Enzyme 兼容所有的主要測(cè)試運(yùn)行器和判斷庫(kù)。
icon 的測(cè)試用例
import * as renderer from "react-test-renderer" import React from "react" import Icon from "../icon" import {mount} from "enzyme" describe("icon", () => { it("render successfully", () => { const json = renderer.create(IDE 提示找不到 describe 和 it 怎么辦?).toJSON() expect(json).toMatchSnapshot() }) it("onClick", () => { const fn = jest.fn() const component = mount( ) component.find("svg").simulate("click") expect(fn).toBeCalled() }) })
解決辦法:
yarn add -D @types/jest
在文件開頭加一句 import "jest"
這是因?yàn)?describe 和 it 的定于位于 jest 的類型聲明文件中,不信你可以按住 ctrl 并點(diǎn)擊 jest 查看。
如果還不行,你需要在 WebStorm 里設(shè)置對(duì) jest 的引用:
這是因?yàn)?typescript 默認(rèn)排除了 node_modules 里的類型聲明。
總結(jié)以上主要是在學(xué)習(xí)造輪子過(guò)程總結(jié)的,環(huán)境搭建就沒(méi)有細(xì)說(shuō)了,主要記錄實(shí)現(xiàn) Icon 輪子的一些思路及注意事項(xiàng)等,想看源碼,跑跑看的,可以點(diǎn)擊這里查看。
參考方應(yīng)杭老師的React造輪子課程
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/6838.html
簡(jiǎn)介 本輪子是通過(guò) React + TypeScript + Webpack 搭建的,至于環(huán)境的搭建這邊就不在細(xì)說(shuō)了,自己動(dòng)手谷歌吧。當(dāng)然可以參考我的源碼。 這里我也是通過(guò)別人學(xué)的,主要做些總結(jié)及說(shuō)明造各個(gè)輪子的一種思路,方便今后使用別人的的輪子時(shí)自己腦中有造輪子的思想,能通過(guò)修改源碼及時(shí)修改 bug,按時(shí)上線。 本文的 Icon 組件主要是參考 Framework7 中的 Icon React ...
摘要:本文是造輪系列第三篇。造輪子系列組件思路造輪系列對(duì)話框組件思路想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳博客一年百來(lái)篇優(yōu)質(zhì)文章等著你初始化參考組件分別分為五個(gè)組件。參考方應(yīng)杭老師的造輪子課程交流干貨系列文章匯總?cè)缦?,覺(jué)得不錯(cuò)點(diǎn)個(gè),歡迎加群互相學(xué)習(xí)。 本文是React造輪系列第三篇。 1.React 造輪子系列:Icon 組件思路 2.React造輪系列:對(duì)話框組件 - Dialog 思路 想閱讀更多優(yōu)質(zhì)...
摘要:本文是造輪系列第二篇。實(shí)現(xiàn)方式事件處理跟差不多,唯一多了一步就是當(dāng)點(diǎn)擊或者的時(shí)候,如果外部有回調(diào)就需要調(diào)用對(duì)應(yīng)的回調(diào)函數(shù)。 本文是React造輪系列第二篇。 1.React 造輪子系列:Icon 組件思路 本輪子是通過(guò) React + TypeScript + Webpack 搭建的,至于環(huán)境的搭建這邊就不在細(xì)說(shuō)了,自己動(dòng)手谷歌吧。當(dāng)然可以參考我的源碼。 想閱讀更多優(yōu)質(zhì)文章請(qǐng)猛戳Git...
摘要:可以看到,這樣不僅沒(méi)有占用組件自己的,也不需要手寫回調(diào)函數(shù)進(jìn)行處理,這些處理都?jí)嚎s成了一行。效果通過(guò)拿到周期才執(zhí)行的回調(diào)函數(shù)。實(shí)現(xiàn)等價(jià)于的回調(diào)僅執(zhí)行一次時(shí),因此直接把回調(diào)函數(shù)拋出來(lái)即可。 1 引言 上周的 精讀《React Hooks》 已經(jīng)實(shí)現(xiàn)了對(duì) React Hooks 的基本認(rèn)知,也許你也看了 React Hooks 基本實(shí)現(xiàn)剖析(就是數(shù)組),但理解實(shí)現(xiàn)原理就可以用好了嗎?學(xué)的是...
摘要:靈活性和針對(duì)性。所以我覺(jué)得大部分組件還是自己封裝來(lái)的更為方便和靈活一些。動(dòng)手開干接下來(lái)我們一起手摸手教改造包裝一個(gè)插件,只要幾分鐘就可以封裝一個(gè)專屬于你的。 項(xiàng)目地址:vue-countTo配套完整后臺(tái)demo地址:vue-element-admin系類文章一:手摸手,帶你用vue擼后臺(tái) 系列一(基礎(chǔ)篇)系類文章二:手摸手,帶你用vue擼后臺(tái) 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶...
閱讀 3185·2023-04-25 17:19
閱讀 630·2021-11-23 09:51
閱讀 1356·2021-11-08 13:19
閱讀 790·2021-09-29 09:34
閱讀 1691·2021-09-28 09:36
閱讀 1503·2021-09-22 14:59
閱讀 2720·2019-08-29 16:38
閱讀 2064·2019-08-26 13:40