本文是筆者寫組件設計的第十篇文章, 今天帶你們實現一個比較特殊的組件——通知提醒框(Notification)。 該組件在諸如Antd或者elementUI等第三方組件庫中也都會出現,主要用來爲用戶提供系統通知信息的.咱們在調用它時並不像其餘組件同樣,經過引入組件標籤來調用。好比Modal組件,咱們通常這樣來調用:javascript
<Modal title="xui基礎彈窗" centered mask={false} visible={false}>
<p>我是彈窗內容</p>
<p>我是彈窗內容</p>
<p>我是彈窗內容</p>
<p>我是彈窗內容</p>
</Modal>
複製代碼
可是通知提醒框(Notification),大多數場景下是使用js API的方式調用:css
notification.open({
message: '趣談前端React',
description: '學前端,學React/vue/Node,快快加入咱們吧'
});
複製代碼
咱們看到的組件效果多是這樣的:html
先來鞏固如下組件的分類法:前端
熟悉以上分類法是設計任何組件系統的前提,無論你是從零到一開發前端團隊的UI庫,仍是基於已有組件庫二次開發業務組件,以上分類法則一樣適用。vue
本文將會使用React來開發該組件,也會使用到Javascript中經常使用的一些設計模式,好比單例模式,可是無論你使用什麼框架來實現,原理都是通用的,若是感興趣的朋友能夠用vue也實現以一下。若是對設計模式不是很瞭解,能夠移步:java
15分鐘帶你瞭解前端工程師必知的javascript設計模式(附詳細思惟導圖和源碼).node
在開始組件設計以前但願你們對css3和js有必定的基礎,並瞭解基本的react/vue語法.咱們先來解構一下Notification組件, 一個Notification分爲如下幾個部分: react
如下是筆者使用React實現後的Notification組件效果:webpack
接下來咱們來看看通知提醒框(Notification)的具體設計思路。css3
按照以前筆者總結的組件設計原則,咱們第一步是要確認需求. 通知提醒框(Notification)組件通常會有以下需求點:
需求收集好以後,做爲一個有追求的程序員, 會得出以下線框圖:
通知框的API調用實現思路其實就是經過jsx動態渲染約定好的標籤,而後經過ReactDom的Render API將dom渲染到指定容器內掛在到頁面,其中要想實現Notification.info這樣的方式還須要考慮到建立實例的問題,咱們應該使用單例模式來控制實例的建立個數。僞代碼以下:
const xNotification = (function() {
let notification = null;
if(notification) {
return {
render(dom){},
config(config){},
info(config){},
error(config){}
// ...
}
}else {
notification = new Notification({})
return {
render(dom){},
config(config){},
info(config){},
error(config){}
// ...
}
}
})()
// 使用
xNotification.info({...})
xNotification.error({...})
複製代碼
可是真正要實現以上需求討論的那些通知框的功能,實際上咱們仍是要寫不少代碼來處理不一樣的狀況的,因此爲了方便你們理解,咱們這裏使用React Notification這個第三方庫來幫咱們處理基本的邏輯,筆者會基於它,來實現上面咱們討論的那些功能。
首先按照筆者的代碼風格,通常會考慮組件設計的框架,而後再一步步往裏面填充內容和邏輯。經過這種漸進式的設計思路,能讓咱們邏輯更嚴謹,更清晰。具體代碼以下:
import Notification from 'rc-notification'
import './index.less'
const xNotification = (function() {
let notification = null
/** * notice類型彈窗 * @param {config} object 通知框配置屬性 * @param {type} string 通知窗類型 * @param {btn} ReactNode 自定義關閉按鈕 * @param {bottom} number 消息從底部彈出時,距離底部的位置,單位像素 * @param {className} string 自定義 CSS class * @param {description} string|ReactNode 通知提醒內容,必選 * @param {duration} number 默認 4.5 秒後自動關閉,配置爲 null 則不自動關閉 * @param {getContainer} HTMLNode 配置渲染節點的輸出位置 * @param {icon} ReactNode 自定義圖標 * @param {key} string 當前通知惟一標誌 * @param {message} string|ReactNode 通知提醒標題,必選 * @param {onClose} func 點擊默認關閉按鈕時觸發的回調函數 * @param {onClick} func 點擊通知時觸發的回調函數 * @param {top} number 消息從頂部彈出時,距離頂部的位置,單位像素 * @param {closeIcon} ReactNode 自定義關閉圖標 */
const pop = (config) => {
const {
type, bottom, className, description, duration = 4.5,
getContainer = () => document.body, icon,
key, message, onClose, onClick, top, closable = true, closeIcon
} = config
notification.notice({
content: <div className={classnames('xNotice', className )}> <div className={classnames('iconWrap', type)}> <Icon type={iconType[type]} /> </div> <div> <div className="xNoticeTit"> { message } </div> <div className="xNoticeDesc"> { description } </div> </div> </div> }) } /** * 通知提示組件, 全局參數 * @param {bottom} number 消息從底部彈出時,距離底部的位置,單位像素, 默認24 * @param {duration} number 默認自動關閉延時,單位秒 * @param {getContainer} HTMLNode 配置渲染節點的輸出位置,默認document.body * @param {placement} string 彈出位置,可選 topLeft topRight bottomLeft bottomRight * @param {top} number 消息從頂部彈出時,距離頂部的位置,單位像素 * @param {closeIcon} HTMLNode 自定義關閉圖標 */ const config = (config) => { const { duration, getContainer, placement, closeIcon } = config Notification.newInstance({ getContainer: getContainer, duration: duration || 4.5, closeIcon }, (notice) => notification = notice) } if(notification) { return { config, pop } } // 若是爲建立實例,則建立默認實例 Notification.newInstance({}, (notice) => notification = notice) return { config, pop } })() export default xNotification 複製代碼
首先咱們根據需求把一項項的屬性都羅列出來。咱們在全局使用的配置方法是xNotification.config(config), 在通知框實例中咱們使用xNotification.pop(config)。這點和antd的使用方式有點不一樣,筆者是把通知框類型放到pop的config來處理了,好比說要渲染一個成功的通知框,咱們能夠這麼作:
xNotification.pop({type: 'success'})
複製代碼
antd一樣的方式會這麼調用:
// antd
Notification.info({//...})
複製代碼
筆者之因此會這麼作是由於info,success,warning這樣的狀態其實dom結構徹底能夠複用,因此經過配置方式能夠極大的減小冗餘代碼。
筆者其實在搭建組件框架的時候已經完成了部分屬性的配置,因此這裏就不一一介紹了,筆者將會介紹一些比較重要的方法的實現。
經過觀察咱們能夠知道要想實現不一樣的通知框類型,只須要根據類型來動態替換icon就好了。icon圖標部分採用筆者已經實現的Icon組件,具體用法和antd的Icon組件相似,若是想學習如何封裝屬於本身的Icon組件能夠參考筆者源碼。
首先咱們先定義一個類型和icon的映射關係:
const iconType = {
success: 'FaRegCheckCircle',
warning: 'FaRegMeh',
info: 'FaRegLightbulb',
error: 'FaRegTimesCircle'
}
複製代碼
這四種類型對應着不一樣的icon圖標類型,那麼咱們就能夠根據用戶傳入的類型來展現不一樣icon圖標了:
<div className={classnames('iconWrap', type)}>
<Icon type={iconType[type]} /> </div>
複製代碼
不過咱們還須要考慮的一點就是若是用戶傳入了自定義的icon,咱們理論上應該展現自定義icon,因此type因該和icon這兩個屬性是有聯繫的。還有一種狀況就是若是用戶即沒有配置type,有沒有傳入icon,那麼其實是不須要顯示icon的,綜合考慮以後咱們的代碼以下:
{
(icon || ['info', 'success', 'error', 'warning'].indexOf(type) > -1) &&
<div className={classnames('iconWrap', type)}> { icon ? icon : <Icon type={iconType[type]} /> } </div> } 複製代碼
實現效果以下圖:
通知框的位置根據業務場景來看因該是全局配置,因此咱們放在config方法裏設置,關於如何根據用戶傳入的位置信息來控制Notification顯示的位置,咱們也能夠先定義一個枚舉類:
const adapterPos = {
topLeft: {
top: '24px',
left: '24px'
},
topRight: {
top: '24px',
right: '24px'
},
bottomLeft: {
bottom: '24px',
left: '24px'
},
bottomRight: {
bottom: '24px',
right: '24px'
}
}
複製代碼
從上面代碼能夠看到咱們會定義四個基礎位置,默認偏移都是24px,而後咱們就能夠根據用處傳入的placement來匹配本身的位置信息了:
Notification.newInstance({
style: {...adapterPos[placement] },
// ...
複製代碼
上面代碼能夠知道位置信息咱們是經過style來設置的。具體效果以下:
動畫咱們實現一個相似與antd的從右往左入場的動畫,咱們來改寫樣式以下:
.rc-notification-fade-enter {
animation: moveLeft .3s;
}
.rc-notification-fade-leave {
animation: moveOutLeft .3s;
}
.rc-notification-fade-enter.rc-notification-fade-enter-active {
animation: moveLeft .3s;
}
.rc-notification-fade-leave.rc-notification-fade-leave-active {
animation-name: moveOutLeft .3s;
}
@keyframes moveOutLeft {
0% {
}
100% {
right: -200%;
}
}
@keyframes moveLeft {
0% {
right: -200%;
}
100% {
right: 0;
}
}
複製代碼
經過以上步驟, 一個功能強大的通知提醒框(Notification)就完成了.Notification組件算是組件庫中中等複雜的組件,若是不懂的能夠在評論區提問,筆者看到後會第一時間解答.
咱們能夠經過以下方式使用它:
<Button type="primary" onClick={ () => {
xNotification.pop({
type: 'success',
message: '趣談前端學習打卡',
description: '前端基礎,中級進階,高級打卡,一塊兒玩轉前端,996遠離你'
})
}
}>點我顯示通知</Button>
複製代碼
配置全局屬性:
import { xNotification } from '@alex_xu/xui'
xNotification.config({
placement: 'topRight'
})
複製代碼
筆者已經將實現過的組件發佈到npm上了,你們若是感興趣能夠直接用npm安裝後使用,方式以下:
npm i @alex_xu/xui
// 導入xui
import {
Button,
Skeleton,
Empty,
Progress,
Tag,
Switch,
Drawer,
Badge,
Alert
} from '@alex_xu/xui'
複製代碼
該組件庫支持按需導入,咱們只須要在項目裏配置babel-plugin-import便可,具體配置以下:
// .babelrc
"plugins": [
["import", { "libraryName": "@alex_xu/xui", "style": true }]
]
複製代碼
npm庫截圖以下:
後續筆者將會繼續實現
等組件, 來複盤筆者多年的組件化之旅.
若是對於react/vue組件設計原理不熟悉的,能夠參考個人以前寫的組件設計系列文章:
筆者已經將組件庫發佈到npm上了, 你們能夠經過npm安裝的方式體驗組件.
若是想獲取組件設計系列完整源碼, 或者想學習更多H5遊戲, webpack,node,gulp,css3,javascript,nodeJS,canvas數據可視化等前端知識和實戰,歡迎在公號《趣談前端》加入咱們的技術羣一塊兒學習討論,共同探索前端的邊界。