基于svg写了一个涂鸦组件,说项目以前先附上几张效果图:webpack
项目地址:https://github.com/linmingdao/SVGraffitigit
效果预览: github
功能演示: web
因为篇幅问题,本文先整体介绍一下项目的大概状况,重点介绍一下组件间的通讯方式。npm
1、项目说明 api
该项目是基于webpack@3.x.x构建的多页应用,使用ES6开发,以组件的方式组织代码。 git clone项目后(文末附上该项目github仓库地址),npm i安装相关依赖,npm run dev运行项目,默认会打开应用的首页,也就是上面的效果预览对应的界面。开发过程会单独地为一些功能编写一些测试代码,因此该项目提供了不一样的页面对应于不一样的功能,好比:缓存
color picker组件测试页: bash
组件消息通讯框架测试页: 框架
svg底层绘制api测试页: ide
2、组件间通讯
一、组件间为了实现最大程度的封装与解耦,不直接进行互相通讯,而是经过“消息订阅/发布管理中心”(如下简称“消息中心”)进行间接通讯。组件经过声明本身为不一样的角色从而拥有对应的通讯能力:
这里以项目中的中间区域的画板组件为例,由于画板组件只是接收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”等等与本项目相关的文章,写得很差求亲喷。
项目地址:https://github.com/linmingdao/SVGraffiti
感兴趣的同窗们欢迎star一块儿交流。