基於svg寫了一個塗鴉元件,說專案之前先附上幾張效果圖:
專案地址:https://github.com/linmingdao/SVGraffiti
效果預覽:
功能演示:
由於篇幅問題,本文先總體介紹一下專案的大概情況,重點介紹一下元件間的通訊方式。
一、專案說明
該專案是基於webpack@3.x.x構建的多頁應用,使用ES6開發,以元件的方式組織程式碼。
git clone專案後(文末附上該專案github倉庫地址),npm i安裝相關依賴,npm run dev執行專案,預設會開啟應用的首頁,也就是上面的效果預覽對應的介面。開發過程會單獨地為一些功能編寫一些測試程式碼,所以該專案提供了不同的頁面對應於不同的功能,比如:
color picker元件測試頁:
元件訊息通訊框架測試頁:
svg底層繪製api測試頁:
二、元件間通訊
1、元件間為了實現最大程度的封裝與解耦,不直接進行互相通訊,而是通過“訊息訂閱/釋出管理中心”(以下簡稱“訊息中心”)進行間接通訊。元件通過宣告自己為不同的角色從而擁有對應的通訊能力:
- 元件宣告為訂閱者(Subscriber)並通過@Topics註解的形式從“訊息中心”訂閱自己感興趣的主題訊息,對應的訊息會通過notify介面告知元件;
- 元件宣告為釋出者(Publisher),可以通過Publisher角色注入的publish方法釋出主題訊息;
- 元件宣告為釋出/訂閱者(SubScatterer),同時擁有訂閱者和釋出者的通訊能力。
這裡以專案中的中間區域的畫板元件為例,因為畫板元件只是接收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訊息管理中心】負責呼叫,畫板元件在此介面處理接收到的訊息型別
* 1、處理Toolbar元件傳送的 “切換畫板繪製狀態” ,對應的訊息型別為:“function”
* 2、處理Toolbar元件傳送的 “清空繪製內容” ,對應的訊息型別為:“resident_function”
* 3、處理Settings元件傳送的 “設定畫板繪製引數” ,對應的訊息型別為:“set_preference”
* @param {String} topic 訊息主題標識
* @param {Object} entity 訊息實體物件
*/
notify(topic, entity) {
// 在此處理接收到的訊息
}
}
複製程式碼
注:@Topics是靜態的,若有些主題是需要執行時訂閱也可以呼叫Subscriber角色提供的subscribe方法動態訂閱訊息。
2、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”等等與本專案相關的文章,寫得不好求親噴。
專案地址:https://github.com/linmingdao/SVGraffiti
感興趣的同學們歡迎star一起交流。