開始解析 vue 的原始碼(2) 單向繫結

engineering-pictures-12.jpg
年初就有一個 idea 準備弄弄自己 javascript 框架,參考 vue 來做,Evan 也在開發者大會介紹 vue3.0 時候提到在新版 vue 中原始碼使用 typescript 編寫,按模組劃分,更適合開發者閱讀原始碼。
先不原始碼自己根據 Evan 的思路先自己弄一弄,然後在看原始碼,這樣留給自己思考的空間。先看 vue
目標: 進行不使用第三方庫,不排除會使用 rxjs redux
搭建簡單環境,建立一個專案目錄```npm init -init``
在專案下
建立 src 用於存放原始碼
建立 examples 來測試框架小 demo
準備用 typescript 來寫,所以初始化,為什麼用 Typescript 呢?不是為了方便,是這樣大型專案必須要的用一個型別系統呀,開始想用 ocaml 寫了,ocaml 還不算熟,隨著可以寫一個 ocaml 的版本。暫時先用 typescript 弄。
tsc --init
index.html 檔案,我們目的就是用變數的值將佔位符內容替換調達到單向繫結。看似簡單。

start.JPG
<div id="app"> {{ message }} </div> <script src="./dist/zi.js"></script> <script src="./app.js"></script>
- zi.js 是編譯後 zi 檔案
- app.js 開發應用檔案
我們一步一步地實現 vue 的功能,就是將 data 變數更新到html
new Zi({ el: '#app', data: { message: 'Hello Zi.js!' } })
先配置 tsconfig 檔案,配置 typescript 如何編譯為 javascript
tsconfig
{ "include": [ "src/**/*" ], "exclude": [ "node_modules", "**/*.spec.ts" ], "compilerOptions": { "target": "es5", "lib": ["es2015","dom"], "module": "commonjs", "allowJs": true, "outDir": "./examples/dist/", "strict": true, "esModuleInterop": true, "experimentalDecorators": true, } }
app.js:1 Uncaught ReferenceError: Zi is not defined at app.js:1
我們先解決 Zi 沒定義問題,那麼就建立一個 Zi 的類。
class Zi{ }
這裡是傳入一個 option 物件引數(context),我們用 tyepscript 定義一個 Option 的型別
new Zi({ el: '#app', data: { message: 'Hello Zi.js!' } })
interface Options { el:string, data:object } class Zi{ constructor(opt:Options){ console.log(opt) } }
- el : 是選擇器,確定應用作用的 dom 的範圍
- data : 是資料,需要 data 資料渲染上去
如何將資料繫結到檢視上,data binding,
- el 屬性讀取該節點下 html 結構,然後生產一個虛擬節點樹(vdom),這地方是重點,可以考慮先引進 handler 或者其他 vdom 的庫
- 讀取 html String template 返回一個方法,這裡用的函數語言程式設計 curry
- 獲取vmode 中資料繫結位置,然後將節點與vmode 對應上
- 監控資料的屬性,屬性發生變化更新vmode對應節點進行區域性渲染
export default class ZiHtml{ constructor(){ } compile(){ console.log(111) } }
compile 方法需要返回一個函式```function(context)
export default class ZiHtml{ constructor(){ } compile(htmlStr:string){ console.log(111) return function(context:Object){ //log let parser = new DOMParser() rootDom:HTMLDocument = parser.parseFromString(htmlStr, "text/xml"); console.log(rootDom) } } }
"module": "es2015",
class ZiHtml{ parser:DOMParser constructor(){ this.parser = new DOMParser(); } compile(htmlStr:string){ var self = this; return function(context:Object){ console.log(htmlStr); var doc = self.parser.parseFromString(htmlStr, "application/xml"); console.log(doc) } } }
// import ZiHtml from './ZiHtml'; class ZiHtml{ parser:DOMParser constructor(){ this.parser = new DOMParser(); } compile(htmlStr:string,id:string){ var self = this; return function(context: Dictionary<String>){ console.log(htmlStr); var doc:HTMLDocument = self.parser.parseFromString(htmlStr, "application/xml"); let str:string = doc.documentElement.innerHTML.trim(); let replaceStr:string = ""; Object.keys(context) .map(function(item){ console.log(context[item]) replaceStr = str.replace(item ,context[item]+""); }) replaceStr = replaceStr.replace(/\{|\}/g,""); console.log("html string",replaceStr); doc.documentElement.innerHTML = replaceStr; let TargetDom:HTMLElement | null = document.querySelector(id); if(TargetDom != null){ TargetDom.innerHTML = replaceStr; } } } } interface Dictionary<T>{ [key: string]: T; } interface Binder { name:string, handler:Function } interface Options { el:string, data:Dictionary<string> } class Zi{ html:ZiHtml; template:any; constructor(opt:Options){ this.html = new ZiHtml() this.init(opt) } init(opt:Options){ console.log(opt.el) let htmlDom:HTMLElement = <HTMLElement>document.querySelector(opt.el); let htmlStr:string = htmlDom.outerHTML; this.template = this.html.compile(htmlStr,opt.el); var obj:Dictionary<string> = opt.data; this.template(obj) Object.keys(obj) .map(function(item){ console.log(obj[item]) }) } }

result.JPG
擴充套件一下依舊有效,不錯雖然粗糙但是方向應該是對的
<div id="app"> {{ message }}{{name}} </div>
new Zi({ el: '#app', data: { message: 'Hello Zi.js!', name:"zidea" } });

test.JPG