基於svg寫了一個塗鴉組件,說項目以前先附上幾張效果圖:webpack
項目地址:SVGraffitigit
因爲篇幅問題,本文先整體介紹一下項目的大概狀況,重點介紹一下組件間的通訊方式。github
1、項目說明
web
該項目是基於webpack@3.x.x構建的多頁應用,使用ES6開發,以組件的方式組織代碼。
git clone項目後(文末附上該項目github倉庫地址),npm i安裝相關依賴,npm run dev運行項目,默認會打開應用的首頁,也就是上面的效果預覽對應的界面。開發過程會單獨地爲一些功能編寫一些測試代碼,因此該項目提供了不一樣的頁面對應於不一樣的功能,好比:npm
color picker組件測試頁:
api
組件消息通訊框架測試頁:
緩存
svg底層繪製api測試頁:
框架
2、組件間通訊
ide
一、組件間爲了實現最大程度的封裝與解耦,不直接進行互相通訊,而是經過「消息訂閱/發佈管理中心」(如下簡稱「消息中心」)進行間接通訊。組件經過聲明本身爲不一樣的角色從而擁有對應的通訊能力:svg
這裏以項目中的中間區域的畫板組件爲例,由於畫板組件只是接收Toolbar組件發來的切換繪製能力、清空繪製內容以及Settings組件發來的設置繪製參數信息,因此該組件只是一個消息訂閱者角色,編碼設計以下:
首先導入對應的角色類:
import Subscriber from '../../supports/pubsub/base/subscriber'; import Topics from '../../supports/pubsub/base/topics';
編寫對應的組件:
// 經過@Topics的形式訂閱感興趣的消息類型 @Topics(['function', 'resident_function', 'set_preference']) export default class Sketchpad extends Subscriber { // 構造器 constructor(sketchpad) { super(); this.sketchpad = sketchpad; // ... } /** * 該接口由【PubSub消息管理中心】負責調用,畫板組件在此接口處理接收到的消息類型 * 一、處理Toolbar組件發送的 「切換畫板繪製狀態」 ,對應的消息類型爲:「function」 * 二、處理Toolbar組件發送的 「清空繪製內容」 ,對應的消息類型爲:「resident_function」 * 三、處理Settings組件發送的 「設置畫板繪製參數」 ,對應的消息類型爲:「set_preference」 * @param {String} topic 消息主題標識 * @param {Object} entity 消息實體對象 */ notify(topic, entity) { // 在此處理接收到的消息 } }
注:@Topics是靜態的,如有些主題是須要運行時訂閱也能夠調用Subscriber角色提供的subscribe方法動態訂閱消息。
二、PubSub(消息訂閱/發佈管理中心)的實現
既然是底層通用能力就必定要實現的不帶任何具體的業務,不管是在命名規範仍是編碼實現上都要保證它是一個通用模塊
PubSub的實現:
/** * 主題訂閱發佈中心 */ export default class PubSub { // 緩存主題和主題的訂閱者列表 static topics = {}; /** * 發佈主題消息 * @param {String} topic 主題 * @param {*} entity 消息體 */ static publish(topic, entity) { if (!PubSub.topics[topic]) return; // 獲取該主題的訂閱者列表 const subscribers = PubSub.topics[topic]; // 向全部該主題的訂閱者發送主題消息 for (let subscriber of subscribers) { subscriber.notify && subscriber.notify(topic, entity); } } /** * 一次登記一個主題 * @param {String} topic */ static registerTopic(topic) { const topics = PubSub['topics']; !topics[topic] && (topics[topic] = []); } /** * 同時登記多個主題 * @param {Array} topics */ static registerTopics(topics = []) { topics.forEach(topic => { this.registerTopic(topic); }); } /** * 添加主題訂閱者 * @param {String} topic 主題 * @param {Object} subscriber 實現了notify接口的訂閱者 */ static addSubscriber(topic, subscriber) { const topics = PubSub['topics']; !topics[topic] && (topics[topic] = []); // 將該主題的訂閱者登記到對應的主題 topics[topic].push(subscriber); } /** * 刪除對應的訂閱者 * @param subscriber */ static removeSubscriber(subscriber) { const subs = []; // 遍歷全部主題下的訂閱者列表,將對應訂閱者刪除 const topics = PubSub.topics; Object.keys(topics).forEach(topicName => { const topic = topics[topicName]; for (let i = 0; i < topic.length; ++i) { if (topic[i] === subscriber) { subs.push(topics[topic].splice(i, 1)); break; } } }); return subs; } }
Subscriber的實現:
import PubSub from '../pubsub'; const addSubscribe = (topics = [], context) => { topics.forEach(topic => { PubSub.addSubscriber(topic, context); }); } /** * 主題訂閱者 */ export default class Subscriber { constructor() { addSubscribe(this.__proto__.constructor.topics, this); } subscribe(topic) { PubSub.addSubscriber(topic, this); } }
爲了方便訂閱主題,再提供一個@Topics註解:
import PubSub from '../pubsub'; /** * 訂閱者主題裝飾器 * @param {Array} topics */ export default function Topics(topics) { return target => { target.topics = topics; PubSub.registerTopics(topics); } }
Publisher的實現:
import PubSub from '../pubsub'; /** * 主題消息發佈者 */ export default class Publisher { publish(topic, entity) { PubSub.publish(topic, entity); } }
SubScatterer的實現:
import PubSub from '../pubsub'; import Subscriber from './subscriber'; /** * 主題訂閱者 and 主題消息發佈者 */ export default class SubScatterer extends Subscriber { publish(topic, entity) { PubSub.publish(topic, entity); } }
本篇介紹了項目的大概狀況,重點分析瞭如何以發佈/訂閱的形式實現組件間的通訊,接下來還會抽時間寫幾個篇分別介紹「svg底層繪製能力的封裝」、「畫板不一樣繪製狀態的實現與管理」、「如何開發一個通用的ColorPicker」等等與本項目相關的文章,寫得很差求親噴。
項目github地址:SVGraffiti
感興趣的同窗們歡迎star一塊兒交流。