又雙叒叕造新輪子:重構了自己寫的mvvm庫

養豬的毛拉發表於2019-01-04

承接上文

當前版本:0.0.1-alpha.0

專案地址
文件
npm

image

重構

Renderer

因為舊版本中執行時編譯的時候嚴重耦合DOM api,導致寫服務端渲染的時候必須重新實現一套相同的編譯邏輯。於是便下決心剔除DOM api,抽象出一個通用的編譯邏輯。

想了很久參考了現有的三大框架的一些方法,於是便通過暴露出一個基類Renderer來封裝一些渲染方法,隔離平臺api。再通過已經實現的虛擬DOM,每次執行時編譯的時候都對虛擬DOM進行更改diff,最後通過實現Renderer的例項的方法進行渲染。

export abstract class Renderer { 
public abstract nativeElementToVnode(nativeElement: any, parseVnodeOptions?: ParseOptions): Vnode[];
public abstract getElementsByTagName(name: string): any;
public abstract hasChildNodes(nativeElement: any): boolean;
public abstract getChildNodes(nativeElement: any): any[];
public abstract removeChild(parent: any, child: any): void;
public abstract appendChild(parent: any, child: any): void;
public abstract insertBefore(parent: any, child: any, index: number): void;
public abstract isContainted(parent: any, child: any): boolean;
public abstract creatElement(tagName: string): any;
public abstract creatTextElement(value: string): any;
public abstract getAttribute(element: any, name: string): any;
public abstract setAttribute(element: any, name: string, value: any): void;
public abstract setNvAttribute(element: any, name: string, value: any): void;
public abstract removeAttribute(element: any, name: string, value?: any): void;
public abstract removeNvAttribute(element: any, name: string, value?: any): void;
public abstract setNodeValue(element: any, nodeValue: any): void;
public abstract setValue(element: any, value: any): void;
public abstract removeEventListener(element: any, eventType: string, handler: any): void;
public abstract addEventListener(element: any, eventType: string, handler: any): void;
public abstract setStyle(element: any, name: string, value: any): void;
public abstract removeStyle(element: any, name: string): void;
public abstract getStyle(element: any, name: string): void;

}複製程式碼

最後,通過平臺的外掛實現該類就可以做到跨平臺渲染。(現在只實現了platform-browser..服務端進行中)。

虛擬DOM

因為模板是字串模板,所以第一版的虛擬DOM實際上是利用DOM的innerHTML完成的,為了做跨平臺渲染所以使用在正則匹配模板來找出tag及屬性。

packages/core/vnode/parse.ts

const tagRegex: RegExp = /<
(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">
])+>
/g
;
複製程式碼

packages/core/vnode/parse-tag.ts

const tagRegex: RegExp = /<
(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">
])+>
/g
;
複製程式碼

利用這兩個正則匹配出對應模板的標籤及其屬性後,就可以不適用innerHTML來獲得一個類似DOM結構的Vnode了。

packages/core/vnode/vnode.ts

class Vnode { 
public tagName?: string;
public nativeElement?: any;
public parentVnode?: Vnode;
public attributes?: TAttributes[];
public nodeValue?: string | null;
public childNodes?: Vnode[];
public type?: string;
public value?: string | number;
public repeatData?: any;
public eventTypes?: TEventType[] = [];
public key?: any;
public checked?: boolean;
public voidElement?: boolean = false;
public template?: string;
public index?: number;

}複製程式碼

最後在compile階段對Vnode進行操作獲得相應的最新Vnode及對diff之後的差異進行patch就可以對頁面結構進行更新。

詳情參考虛擬DOM跨平臺渲染這兩章。

此外順便把之前的一些遺留repeat指令的問題修復了。

更新

  • 移除state屬性,現在可以直接使用類的所有成員屬性和成員方法。在Oninit生命週期之前會把模板解析到的成員屬性和通過@Watch()註解過的成員屬性都加入監聽中,屬性更改觸發render
  • 移除props屬性,現在可以直接通過@Input(name?: string)直接指定將元件的輸入對映到哪個屬性或方法上
  • JavaScript可以直接在靜態屬性中使用類當做token,順序對應建構函式的引數
  • 生命週期nvDoCheck代替nvWatchState,刪除引數。(其實每次更改狀態都是已知的,所以上次狀態的意義不大)
  • 生命週期nvReceiveProps更名為nvReceiveInputs
  • 優化渲染,通過代理指令上的事件。當事件觸發時將無法觸發渲染,事件handler結束之後將觸發renderwatcher。當一個事件中有多次屬性更改,將此次修改合併成一次修改推倒渲染佇列中
  • @NvModule現在懶載入模組和根模組強制指定bootstrap後設資料
  • 增加ElementRefRenderer,可以直接在元件或指令注入,分別獲得元件或指令掛載的nativeElement和全域性的薰染例項。因此並不推薦直接使用瀏覽器api去操作DOM,而是推薦使用ElementRefRenderer

最後

emmmm….最後看了下跟ng越來越像了。

現在編譯渲染全部基於JIT,未來會支援AOT和管道,在此算我立了個flag。

來源:https://juejin.im/post/5c2f041bf265da610f63dbb9

相關文章