iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 25
0

大家好,我是韋恩,今天是鐵人賽的二十五天,讓我們來設計extension的事件與資料流處理吧!

Day25專案實作: 設計Extension中的事件與資料流


在昨天的專案開發裡,我們發現到了如果在樹狀元件裡進行json檔案的修改與dataStorage的處理。會發生資料與單一元件耦合的情況。先前的樹狀元件的dataStorage的型態是一個儲存treeItem的陣列,這樣的資料型態在我們實作Webview的snippet查詢與相關功能時,會十分的不方便。

我們可以預期至少有三個以上的元件或模組(Jsonfile、WebviewPanel、TreeView),將依賴於workspace中的snippet的資料。為此,我們需要把snippet的資料抽離出來,在一個獨立的model處理,並在資料發生變化時,將改動與資料傳給相關模組與元件,讓這些模組與元件使用個別的方式處理資料,並進行各元件模組對應的操作。

有一個設計模式在處理這種情況非常管用,即是我們先前在介紹treeview(二)時提到的觀察者模式。而在nodejs與vscode的api裡,已經內建了實作了觀察者模式的物件-事件發射器(EventEmitter)。前面我們提到,vscode內建的EventEmitter是個簡化版的實作。在處理各種情境的事件與訂閱上,nodejs的eventEmitter功能豐富且更為強大,因此在這裡我們會在資料的事件處理上使用nodejs的實作處理。

讓我們創建一個workspace.ts檔案,並在其中引用nodejs的事件模組。

import * as event from 'events';

接著,我們引用相關所需的api,並宣告一個Workspace的class,與相關屬性。

export class Workspace { 

 ...

 private _storage : SnippetStorage | undefined;
 
 public get storage() {
  return this._storage;
 }

 public get isExist() {
  return !!this.path;
 }

 constructor(context: vscode.ExtensionContext) {
  this.path = context.globalState.get('workspace');
 }
 ...
}

我們會在workpspace實例化的時候拿到使用者的code-manager的工作區路徑,並且用其判斷workspace是否存在。

同時我們也指定了storage的資料屬性。

接著,讓我們在Workspace裡面創建一個事件發射器:


export class Workspace {
 ...
 private readonly subject = new event.EventEmitter();
 ...
}

上面我們並不使用繼承的方式,而是在Workspace中直接創建eventEmitter,透過組合的方式在Workspace中使用事件的功能。同時,我們按照觀察者模式的傳統將eventEmitter命名為subject,subject意思為主題。

主題(subject)會讓使用者觀察該主題的消息並訂閱感興趣的事件,只要Workspace一有資料變化,subject便會將主題的資料發射給所有想知道資料變化的觀察者們,讓訂閱主題的觀察者們各自進行對應的資料處理。

接著,在我們的snippet應用程式中,允許使用者對snippet做新增修改與刪除等動作,因此我們會有這些跟資料改動相關的事件。

const enum EventType {
 'LOAD' = 'LOAD',
 'CHANGE' = 'CHANGE',
 'READ' = 'READ',
 'ADD' = 'ADD',
 'EDIT' = 'EDIT',
 'DELETE' = 'DELETE',
 'ADD_CATEGORY' = 'ADD_CATEGORY',
 'EDIT_CATEGORY' = 'EDIT_CATEGORY',
 'DELETE_CATEGORY' = 'REMOVE_CATEGORY',
}

上面除了增刪改查外,我們還定義了LOAD事件與CHANGE事件,用於不同情境。

第一個LOAD事件,用於處理載入全部snippet的狀況。

第二個CHANGE事件,在任何方式修改資料時,將修改後的snippet資料,全數通知給訂閱這個事件的人。

  • 提供外部訂閱事件取得事件資料

為了讓外部可以監聽事件,我們照vscode元件的命名風格設計了onDidChangeData方法,讓使用者可以夠過訂閱不同類型事件(EventType),並設定事件發生時的對應處理方法eventHandler。

export class Workspace {
 ...
 public onDidChangeData<T extends []>(event: EventType, eventHandler: (...args: T) => void) {
  this.subject.on(event, eventHandler as any);
 }
 ...
}

接著,我們提供新增、刪除與修改snippet等方法,讓我們也開始對應的實作吧!

  • 提供載入資料的事件
export class Workspace {
 ...
 public loadSnippets(storage: SnippetStorage) {
  this._storage = storage;
  this.subject.emit(EventType.LOAD, this.storage);
 }
 ...
}
  • 新增、刪除與修改相關事件
export class Workspace {
 ...

 public addSnippet(changes: { category: string; setting: SnippetSetting}) {
  const categoryItem = this.storage?.find(i => i.category === changes.category);
  if(categoryItem) {
    categoryItem.settings = categoryItem.settings
        .filter(s=> s.title !== changes.setting.title)
        .concat([changes.setting]);
  }
  this.subject.emit(EventType.ADD, categoryItem || { 
   category: changes.category,
   settings: [changes.setting]
  });
  this.subject.emit(EventType.CHANGE, this.storage);
 }

 public editSnippet(changes: { category: string; setting: SnippetSetting}) {
  const categoryItem = this.storage?.find(i => i.category === changes.category);
  if(categoryItem) {
    categoryItem.settings = categoryItem.settings
        .filter(s=> s.title !== changes.setting.title)
        .concat([changes.setting]);
  }
  this.subject.emit(EventType.ADD, categoryItem || { 
   category: changes.category,
   settings: [changes.setting]
  });
  this.subject.emit(EventType.CHANGE, this.storage);
 }

 public deleteSnippet(category: string, title: string) {
  const categoryItem = this.storage?.find(i => i.category === category);
  if(categoryItem) {
   categoryItem.settings = categoryItem.settings.filter(s=> s.title !== title)
  }
  this.subject.emit(EventType.DELETE, categoryItem || { 
   category,
   settings: []
  });
  this.subject.emit(EventType.CHANGE, this.storage);
 }
 ...
}

在上面的資料新增、編輯與刪除等方法中,我們會修改snippet資料,並將變化使用對應事件發射通知。

為什麼我們不只用一個change事件,並直接給出所有snippet的資料就好了呢?

因為在jsonfile的模組裡,我們並不會一次將所有snippet檔案全部重寫一次,僅需根據需求對不同category的檔案做讀寫與刪除等動作。

為此,我們使用不同的事件,發射個事件所需獲取的資料。

在我們開發的extension裡,還有ADD_CATEGORY等新增、刪除與修改category的事件,因為此處的邏輯與上面相關事件重複,這裡我們並不條列出來,有興趣的讀者可以至extension完成後的repo查看相關實作。

結語


好的,今天我們實作了Workspace資料處理與相關事件的處理,明天我們會繼續專案開發,祝各位收穫滿滿。

我們明天見,掰掰!


上一篇
Day24 | 開發Snippet用的元件(二)
下一篇
Day26 | 實現Extension內的MVVM架構
系列文
自己用的工具自己做! 30天玩轉VS Code Extension之旅36
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言