偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

圖形編輯器開(kāi)發(fā):模塊間如何通信?

開(kāi)發(fā) 前端
本文簡(jiǎn)單介紹了圖形編輯器架構(gòu)中,如何進(jìn)行模塊間的通信。對(duì)于某個(gè)模塊間,可以通過(guò)入口 Editor 對(duì)象,輕松主動(dòng)訪問(wèn)任何其他模塊。此外還可以用事件發(fā)布訂閱的方式綁定監(jiān)聽(tīng)器,在對(duì)應(yīng)模塊狀態(tài)更新后被動(dòng)地獲得通知。

大家好,我是前端西瓜哥。

圖形編輯器,隨著功能的增加,通常都會(huì)愈發(fā)復(fù)雜,良好的架構(gòu)是保證圖形編輯器持續(xù)開(kāi)發(fā)高效的重要技術(shù)。

根據(jù)功能拆分成一個(gè)一個(gè)的小模塊基本是家常便飯。那么模塊之間是如何配合以及進(jìn)行數(shù)據(jù)傳輸?shù)哪兀?/p>

編輯器 github 地址:

https://github.com/F-star/suika

線上體驗(yàn):

https://blog.fstars.wang/app/suika/

注入 Editor 實(shí)例

首先我們有一個(gè)主模塊,也是入口模塊,叫做 Editor。

為了高內(nèi)聚低耦合,其下會(huì)根據(jù)功能拆分出很多的子模塊。

這是為了讓我們要改造特定的功能時(shí),只需要改對(duì)應(yīng)模塊的小范圍代碼,不會(huì)被其他模塊代碼干擾,也不需要去理解它們。

子模塊會(huì)在 Editor 初始化的時(shí)候,將 Editor 實(shí)例對(duì)象注入(大概算是一種依賴(lài)注入)。

class Editor {
  sceneGraph: SceneGraph;
  setting: Setting;
  viewportManager: ViewportManager;
  toolManager: ToolManager;
  commandManager: CommandManager;
  zoomManager: ZoomManager;
  hostEventManager: HostEventManager;
  selectedElements: SelectedElements;

  // ...

  constructor(options: IEditorOptions) {
    // 也有些模塊不需要和其他模塊通信
    this.setting = new Setting();
    
    // 將 Editor 示例作為子模塊的構(gòu)造參數(shù)
    this.sceneGraph = new SceneGraph(this);
    this.viewportManager = new ViewportManager(this);
    this.toolManager = new ToolManager(this);
    this.commandManager = new CommandManager(this);
    this.zoomManager = new ZoomManager(this);
    this.selectedElements = new SelectedElements(this);
    // ...
    this.hostEventManager = new HostEventManager(this);
    
    this.hostEventManager.bindHotkeys();
    this.zoomManager.zoomToFit(1);
  }
}

子模塊會(huì)將其保存為一個(gè)私有成員屬性。

以子模塊 ZoomManger 類(lèi)為例,它大概是這樣的:

export class ZoomManager {
 private editor: Editor
  // ...

  constructor(editor: Editor) {
    // 將傳入的 Editor 對(duì)象保存為私有屬性
    this.editor = editor
    // ...
  }
  
  zoomIn(cx?: number, cy?: number) {
    // 通過(guò) this.editor 訪問(wèn)到其他模塊
    const zoomStep = this.editor.setting.get('zoomStep');
   // ...
}

子類(lèi)的子類(lèi)如果也要用 editor,我們就再傳,主打一個(gè)透?jìng)?,人手一?Editor。

這樣所有的子模塊就都能拿到 Editor 對(duì)象,然后通過(guò)這個(gè) Editor 對(duì)象去訪問(wèn)其他的子類(lèi)。

最小知識(shí)原則

其實(shí)這種做法并不滿(mǎn)足設(shè)計(jì)模式的 最小知識(shí)原則(或者叫迪米特法則)。

所謂最小知識(shí)原則,指的是每個(gè)模塊只和應(yīng)該要用到的模塊要交流,不要和用不到的模塊發(fā)生關(guān)系。

甚至你可以抽一層接口或類(lèi)繼承的方式,將細(xì)粒度達(dá)到被關(guān)聯(lián)模塊的某幾個(gè)需要用到的方法。

目前我的項(xiàng)目還處于早期階段,復(fù)雜度很低,所以沒(méi)必要這么做,之后會(huì)不斷添加功能中讓關(guān)聯(lián)模塊發(fā)生著變化。不應(yīng)該過(guò)早優(yōu)化。這是項(xiàng)目變得非常復(fù)雜,且開(kāi)發(fā)人員非常多的時(shí)候才需要考慮優(yōu)化。

事件發(fā)布訂閱

前面注入的方式,都是通過(guò) 主動(dòng)的方式 去訪問(wèn)其他模塊。

有時(shí)候我們需要用 被動(dòng)的方式 去拿到其他模塊的數(shù)據(jù),這時(shí)候我們常常會(huì)用 發(fā)布訂閱 模式。

發(fā)布訂閱模式,就是對(duì)象間存在一對(duì)多的依賴(lài)時(shí),但一個(gè)對(duì)象改變狀態(tài),所有的依賴(lài)對(duì)象會(huì)自動(dòng)收到通知。

做法通常就是模塊加入的事件(event)的概念,并提供一些方法接受監(jiān)聽(tīng)器(函數(shù)),當(dāng)這個(gè)模塊的某些狀態(tài)發(fā)生改變時(shí),就會(huì)這些監(jiān)聽(tīng)器一一執(zhí)行,并將最新?tīng)顟B(tài)傳入。

這個(gè)其實(shí)我們并不陌生,像是定時(shí)器(setTimeout)、DOM 元素的事件(click、mouseover 等)都是用了這個(gè)設(shè)計(jì)模式。

Nodejs 也有個(gè)專(zhuān)門(mén)的 EventEmitter 類(lèi),來(lái)支持事件訂閱。

const { EventEmitter } = require('events');

// 創(chuàng)建事件觸發(fā)器實(shí)例
const emitter = new EventEmitter();

// 給 event-1 事件添加監(jiān)聽(tīng)器
emitter.on('event-1', (a, b) => {
  console.log('收到事件1消息,參數(shù)為:', a, b);
});

// 觸發(fā)事件,并提供參數(shù)。
emitter.emit('event-1', 3, 4);

// 移除指定監(jiān)聽(tīng)器
// emitter.off('event-1', handler);

可惜 Web 端并沒(méi)有這個(gè)輪子,得自己造或者找個(gè)輪子。

因?yàn)檩喿訉?shí)現(xiàn)并不復(fù)雜,我是更建議自己實(shí)現(xiàn),方便修改和擴(kuò)展。

通常我們只要實(shí)現(xiàn) on、off、emit 三個(gè)方法就好了。

我們?nèi)绻?TypeScript 實(shí)現(xiàn)的話,需要用類(lèi)型編程,讓事件名是類(lèi)型安全的,即事件名對(duì)應(yīng)的監(jiān)聽(tīng)器函數(shù)參數(shù)類(lèi)型要匹配。

實(shí)現(xiàn)后的用法:

const ee = new EventEmitter<{
  // 指定事件和對(duì)應(yīng)的函數(shù)類(lèi)型
  update(newVal: string, prevVal: string): void;
  destroy(): void;
}>();
const handler = (newVal: string, prevVal: string) => {
  console.log(newVal, prevVal)
}
ee.on("update", handler);
ee.emit('update', '前端西瓜哥上班前的精神狀態(tài)', '前端西瓜哥上班后的精神狀態(tài)')
ee.off("update", handler);

// 編譯報(bào)錯(cuò)(數(shù)字不匹配字符串類(lèi)型)
// 'number' is not assignable to parameter of type 'string'
ee.emit('update', 1, 2)

// (val: number) => void' is not assignable to parameter of type '() => void
ee.on('destroy', (val: number) => {})

輪子的話我建議 mitt,同時(shí)這個(gè)輪子是 Vue3 官方推薦的(實(shí)現(xiàn)跨組件通信的一種方式),主要原因是它也是 類(lèi)型安全 的。

這個(gè)輪子很簡(jiǎn)單,高級(jí)方法也很少,源碼實(shí)現(xiàn)也就 100 多行,你完全可以拷貝過(guò)去自己改。

模塊如何使用事件

在 Nodejs 的內(nèi)部模塊,是通過(guò)繼承的方式使用 EventEmitter 的,它的做法是:

class A extends EventEmitter {
  // ...
}

A.on('event-1', () => {})

但我更建議用 **組合 **而不是繼承的方式。

class A {
  emitter = new Emitter()
}

A.emitter.on('event-1', () => {})

繼承并不是好文明,不加限制可能導(dǎo)致復(fù)雜的多層繼承。我們應(yīng)該多用組合,少用繼承。

這樣做的另一個(gè)次要好處是 EventEmitter 的方法不會(huì)污染 A 對(duì)象。

除了模塊間用發(fā)布訂閱方式通信,內(nèi)核層(Editor對(duì)象)也常常利用它和 UI 層通信。

因?yàn)闋顟B(tài)源保存在 Editor 對(duì)象中,所以需要用發(fā)布訂閱的方式去同步狀態(tài)給 UI 層。

以畫(huà)布縮放的功能為例。

畫(huà)布縮放管理類(lèi)的實(shí)現(xiàn)如下:

class ZoomManager {
  private zoom = 1;
  // 自己造的 EventEmitter 輪子
  private emitter = new EventEmitter<{
    zoomChange(zoom: number, prevZoom: number): void;
  }>();
  
  setZoom(zoom: number) {
    const prevZoom = this.zoom;
    this.zoom = zoom;

    // 觸發(fā) “zoom改變” 事件
    this.emitter.emit('zoomChanged', zoom, prevZoom);
  }
}

對(duì)應(yīng)的需要拿到 zoom 值的 React 組件,會(huì)在組件掛載時(shí)綁定監(jiān)聽(tīng)器(Vue 也是類(lèi)似邏輯)。

const ZoomActions = () => {
  const editor = useContext(EditorContext);
  const [zoom, setZoom] = useState(1);
  
  // 組件掛載 hook
  useEffect(() => {
    if (editor) {
      // 初始化時(shí)要主動(dòng)獲取 zoom 值
      setZoom(editor.zoomManager.getZoom());

      // 通過(guò)事件同步 core 層的狀態(tài)
      const handler = (zoom: number) => {
        setZoom(zoom);
      };
      editor.zoomManager.emitter.on('zoomChanged', handler);
  
      // 組件銷(xiāo)毀時(shí)解綁
      return () => {
        editor.zoomManager.emitter.off('zoomChanged', handler);
      };
    }
  }, []);
}

結(jié)尾

本文簡(jiǎn)單介紹了圖形編輯器架構(gòu)中,如何進(jìn)行模塊間的通信。

對(duì)于某個(gè)模塊間,可以通過(guò)入口 Editor 對(duì)象,輕松主動(dòng)訪問(wèn)任何其他模塊。此外還可以用事件發(fā)布訂閱的方式綁定監(jiān)聽(tīng)器,在對(duì)應(yīng)模塊狀態(tài)更新后被動(dòng)地獲得通知。

責(zé)任編輯:姜華 來(lái)源: 前端西瓜哥
相關(guān)推薦

2023-10-19 10:12:34

圖形編輯器開(kāi)發(fā)縮放圖形

2023-08-31 11:32:57

圖形編輯器contain

2023-09-07 08:24:35

圖形編輯器開(kāi)發(fā)繪制圖形工具

2023-09-26 07:39:21

2024-01-08 08:30:05

光標(biāo)圖形編輯器開(kāi)發(fā)游標(biāo)

2023-08-28 08:10:50

Hex圖形編輯器

2023-10-10 16:04:30

圖形編輯器格式轉(zhuǎn)換

2023-10-08 08:11:40

圖形編輯器快捷鍵操作

2023-07-07 13:56:01

圖形編輯器畫(huà)布縮放

2024-01-03 08:43:17

圖形編輯器旋轉(zhuǎn)控制點(diǎn)縮放控制點(diǎn)

2023-07-31 08:46:07

圖形編輯器圖形自動(dòng)對(duì)齊

2023-01-18 08:30:40

圖形編輯器元素

2023-02-01 09:21:59

圖形編輯器標(biāo)尺

2023-04-07 08:02:30

圖形編輯器對(duì)齊功能

2023-02-06 16:59:57

Canvas編輯器

2023-06-12 08:22:56

圖形編輯器工具

2023-04-10 08:45:44

圖形編輯器排列移動(dòng)功能

2023-05-09 08:15:32

圖形編輯器撤銷(xiāo)重做功能

2023-02-09 07:02:30

圖形編輯器修改圖形

2022-12-02 07:24:46

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)