簡介
本輪子是通過 React + TypeScript + Webpack 搭建的,至于環境的搭建這邊就不在細說了,自己動手谷歌吧。當然可以參考我的源碼。
這里我也是通過別人學的,主要做些總結及說明造各個輪子的一種思路,方便今后使用別人的的輪子時自己腦中有造輪子的思想,能通過修改源碼及時修改 bug,按時上線。
本文的 Icon 組件主要是參考 Framework7 中的 Icon React Component 寫的。
想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你!
為什么要造輪子1.為了不求人
假設你使用某個UI框架發現有一個 bug,于是你反饋給開發者,開發者說兩周后修復,而你的項目一周后就要上線,你怎么辦?
為什么很多大公司都不使用其他公司的輪子,要自己造?為了把控自己的業務,不被別人牽著走。
2.為了不流于平庸
大家都是寫增刪改查,你跟別人比有什么優勢?你如果能說一句【我公司的人都在用我寫的UI框架】是不是就很牛逼?造 UI 輪子會遇到很多技術層面而非業務層面的知識?比如一些算法。
3.為了創造
你為別人做了這么久的事情,有沒有自己做什么?自驅動力。
4.為什么是 UI 輪子,不是其他方面的輪子
比如,為什么不自己寫一個 React 框架,要寫 React UI 框架呢?
React.FunctionComponent 與 IconPropps本輪子使用 React + TypeScript 來寫的,那么在 ts 中如何聲明函數組件及級 Icon 組件傳遞參數呢,答案是使用React提供的靜態方法 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 中調用:
import React from "react"; import ReactDOM from "react-dom"; import Icon from "./icon" ReactDOM.render(, document.body)
對于上面的定義方式,后面的輪子會經常使用,所以不必擔心看不懂。
使用 svg-sprite-loader 加載 SVG在上面我們指定了 Icon 的name為wechat,那怎么讓它顯示微信的圖標呢,首先在阿里的 Iconfont 下載對應的 SVG
接著如何顯示 svg? 這里我們使用一個 svg-sprite-loader 庫,然后在對應的 webpack下的 rules 中添加:
{ test: /.svg$/, loader: "svg-sprite-loader" }
在 Icon 中引用,當然對應 tsconfig.json 也要配置(這不是本文的重點):
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
運行效果:
當然 svg 里面不能直接寫死,我們需要根據外部傳入的 name 來指定對應的圖像:
// 部分代碼 import "./icons/wechat.svg" import "./icons/alipay.svg" const Icon: React.FunctionComponent= (props) => { return ( ) }
外部調用:
ReactDOM.render(, document.getElementById("root"))
運行效果:
importAll大家有沒有注意到,我需要使用哪個 svg, 需要在對應的 icon 組件導入對應的 svg,這樣要是我需要100個 svg ,我就要導入100次,這樣做太傻,文件也會變得冗長。
因此我們需要一個動態導入全部 SVG 的方法:
// lib/importIcons.js let importAll = (requireContext) => requireContext.keys().forEach(requireContext) try { importAll(require.context("./icons/", true, /.svg$/)) } catch (error) { console.log(error) }
要想看懂上訴的代碼,可能需要一點 node.js 的基礎,這邊建議你直接收藏好啦,下次有用到,直接拷貝過來用就行了。
接著在 Icon 組件里面導入就行了: import "./importIcons"
React.MouseEventHandler 的使用當我們需要給 Icon 注冊事件的時候,如果直接在組件上寫 onClick 事件是會報錯的,因為它沒有聲明接收 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
調用方式如下:
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響應所有事件, document.getElementById("root"))
上述我們只監聽了 onClick 事件 ,但對于其它事件是不支持了,所以我們需要進一步完善。這里我們不能一個一個添加對應的事件類型,需要一個統一的事件類型,那這個是什么呢?
通過 react 我們會找到一個 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
調用方式:
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")} />
上述還是會有問題,我們還有 onFocus, onBlur, onChange 等等事件,也不可能一個一個傳遞進來,那還有什么方法呢。
在 icon.tsx 中我們會發現我們用的都是通過 props 傳遞進來的。聰明的朋友的可能立馬想到了使用展開運算符的形式 {...props},改寫如下:
... const Icon: React.FunctionComponent= (props) => { return ( ) } ...
上述還是會有問題,如果使用的人也傳入 className 呢,用過 Vue 就知道 Vue 是真的好,它會把傳入和里面的合并起來,但 React 就不一樣了,傳入的會覆蓋里面的,所以需要自己手動處理:
... const Icon: React.FunctionComponent= (props) => { const { className, ...restProps} = props return ( ) } ...
上達寫法還存在問題的,如果外面沒有寫 className ,那么內部會多出一個 undefined
聰明你的可能就想到了使用三目運算符來做判斷,如:
className={`fui-icon ${className ? className : ""}`}
但這種情況如果有多個參數要怎么辦呢?
所以有人就非常聰明專門寫了一個庫存 classnames,這個庫有多火呢,每周有300多萬的下載量,它的作用就是處理 className 的情況。
當然我們這邊只做簡單的處理,如下所示
// helpers/classes function classes(...names:(string | undefined )[]) { return names.join(" ") } export default classes
使用方式:
... const Icon: React.FunctionComponent= (props) => { const { className, name,...restProps} = props return ( ) } ...
這樣最終渲染出來的 className還是會多出一個空格,作為完美者,并不希望有空格的出現的,所以需要進一步處理空格,這里使用 es6 中數組的 filters 方法。
// helpers/classes function classes(...names:(string | undefined )[]) { return names.filter(Boolean).join(" ") } export default classes單元測試
首先我們對我們的 classes 方法時行單元測試,這里使用 Jest 時行測試,也是 React 官網推薦的。
classes 測試用例如下:
import classes from "../classes" describe("classes", () => { it("接受 1 個 className", () => { const result = classes("a") expect(result).toEqual("a") }) it("接受 2 個 className", ()=>{ const result = classes("a", "b") expect(result).toEqual("a b") }) it("接受 undefined 結果不會出現 undefined", ()=>{ const result = classes("a", undefined) expect(result).toEqual("a") }) it("接受各種奇怪值", ()=>{ const result = classes( "a", undefined, "中文", false, null ) expect(result).toEqual("a 中文") }) it("接受 0 個參數", ()=>{ const result = classes() expect(result).toEqual("") }) })使用Snapshot測試UI
這里測試 UI 相關還需要使用一個庫 Enzyme , Enzyme 來自 airbnb 公司,是一個用于 React 的 JavaScript 測試工具,方便你判斷、操縱和歷遍 React Components 輸出。Enzyme 的 API 通過模仿 jQuery 的 API ,使得 DOM 操作和歷遍很靈活、直觀。Enzyme 兼容所有的主要測試運行器和判斷庫。
icon 的測試用例
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"
這是因為 describe 和 it 的定于位于 jest 的類型聲明文件中,不信你可以按住 ctrl 并點擊 jest 查看。
如果還不行,你需要在 WebStorm 里設置對 jest 的引用:
這是因為 typescript 默認排除了 node_modules 里的類型聲明。
總結以上主要是在學習造輪子過程總結的,環境搭建就沒有細說了,主要記錄實現 Icon 輪子的一些思路及注意事項等,想看源碼,跑跑看的,可以點擊這里查看。
參考方應杭老師的React造輪子課程
你的點贊是我持續分享好東西的動力,歡迎點贊!
歡迎加入前端大家庭,里面會經常分享一些技術資源。文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109733.html
簡介 本輪子是通過 React + TypeScript + Webpack 搭建的,至于環境的搭建這邊就不在細說了,自己動手谷歌吧。當然可以參考我的源碼。 這里我也是通過別人學的,主要做些總結及說明造各個輪子的一種思路,方便今后使用別人的的輪子時自己腦中有造輪子的思想,能通過修改源碼及時修改 bug,按時上線。 本文的 Icon 組件主要是參考 Framework7 中的 Icon React ...
摘要:本文是造輪系列第三篇。造輪子系列組件思路造輪系列對話框組件思路想閱讀更多優質文章請猛戳博客一年百來篇優質文章等著你初始化參考組件分別分為五個組件。參考方應杭老師的造輪子課程交流干貨系列文章匯總如下,覺得不錯點個,歡迎加群互相學習。 本文是React造輪系列第三篇。 1.React 造輪子系列:Icon 組件思路 2.React造輪系列:對話框組件 - Dialog 思路 想閱讀更多優質...
摘要:本文是造輪系列第二篇。實現方式事件處理跟差不多,唯一多了一步就是當點擊或者的時候,如果外部有回調就需要調用對應的回調函數。 本文是React造輪系列第二篇。 1.React 造輪子系列:Icon 組件思路 本輪子是通過 React + TypeScript + Webpack 搭建的,至于環境的搭建這邊就不在細說了,自己動手谷歌吧。當然可以參考我的源碼。 想閱讀更多優質文章請猛戳Git...
摘要:可以看到,這樣不僅沒有占用組件自己的,也不需要手寫回調函數進行處理,這些處理都壓縮成了一行。效果通過拿到周期才執行的回調函數。實現等價于的回調僅執行一次時,因此直接把回調函數拋出來即可。 1 引言 上周的 精讀《React Hooks》 已經實現了對 React Hooks 的基本認知,也許你也看了 React Hooks 基本實現剖析(就是數組),但理解實現原理就可以用好了嗎?學的是...
摘要:靈活性和針對性。所以我覺得大部分組件還是自己封裝來的更為方便和靈活一些。動手開干接下來我們一起手摸手教改造包裝一個插件,只要幾分鐘就可以封裝一個專屬于你的。 項目地址:vue-countTo配套完整后臺demo地址:vue-element-admin系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎篇)系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權限篇)系類文章三:手摸手,帶...
閱讀 2049·2021-11-15 11:39
閱讀 3232·2021-10-09 09:41
閱讀 1496·2019-08-30 14:20
閱讀 3266·2019-08-30 13:53
閱讀 3330·2019-08-29 16:32
閱讀 3383·2019-08-29 11:20
閱讀 3025·2019-08-26 13:53
閱讀 780·2019-08-26 12:18