手撕Vue-編譯指令資料

2023-10-15 18:00:39

經過上一篇的分析,完成了查詢指令和模板的功能,接下來就是編譯指令的資料了。

所以本章節主要處理的方法則是 buildElement 方法,我們先分析一下我們所拿到的資料在進行編碼,這樣會更加清晰一些。

我將 name, value 列印出來,分別對應的值是 name: v-model, value: name,在今後我們的命令中可不止只有 v-model,還有 v-text、v-html、v-on 等等,所以我們需要對這些指令進行分類,然後再進行編譯。

所以我這裡特意定義了一個工具類叫 CompilerUtil,用來處理指令的分類,程式碼如下:

let CompilerUtil = {
    /**
     * 處理 v-model 指令
     * @param node 當前元素
     * @param value 指令的值
     * @param vm Nue 的範例物件
     */
    model: function (node, value, vm) {
    },
    html: function (node, value, vm) {
    },
    text: function (node, value, vm) {
    }
}

然後我們在 buildElement 方法中呼叫這個方法,程式碼如下:

// 解構 name
let [, directive] = name.split('-');
// v-model -> [v, model]

// 2.根據指令名稱, 呼叫不同的處理常式
CompilerUtil[directive](node, value, this.vm);

這樣我們就可以根據指令的名稱,呼叫不同的處理常式了。

接下來我們就來處理 v-model 指令,程式碼如下:

/**
 * 處理 model 指令
 * @param node 當前元素
 * @param value 指令的值
 * @param vm Nue 的範例物件
 */
model: function (node, value, vm) {
    node.value = vm.$data[value];
},

這樣我們就可以將資料渲染到頁面上了,開啟瀏覽器,可以看到效果如下:

v-model 指令已經可以正常使用了,但是還有問題,就是我們的資料結構目前是比較簡單的,那麼如果我們的資料是一個物件呢,例如:

time: {
    h: 10,
    m: 10,
    s: 10
}

在用 input 繫結 v-model 進行渲染髮現,只有第一個 input 能夠正常渲染,其他的 input 都是 undefined,這是為什麼呢?

<input type="text" v-model="time.h">
<input type="text" v-model="time.m">
<input type="text" v-model="time.s">

那麼這裡就要去看一下我們 model 方法的實現了,如果是 time.h,value 等於的值為 time.h, 然後我們在執行 vm.$data[value] 就變為了 vm.$data[time.h], 正常的獲取這種資料結構的方式應該是先 vm.$data[time] 拿到 time 物件,然後再 time[h] 拿到 h 的值,所以我們需要對這種資料結構進行處理,為了已維護,我這裡單獨抽離了一個方法出來進行處理獲取 value,方法名字叫做 getValue,程式碼如下:

getValue(vm, value) {
    // time.h --> [time, h]
    return value.split('.').reduce((data, currentKey) => {
        // 第一次執行: data=$data, currentKey=time
        // 第二次執行: data=time, currentKey=h
        return data[currentKey];
    }, vm.$data);
},

reduce 方法被用於迭代這個字串陣列。它接受一個回撥函數,這個回撥函數在每次迭代中被呼叫。在這個回撥函數中,data 是上一次迭代的結果,而 currentKey 是當前迭代的陣列元素(鍵路徑中的一個部分)在每次迭代中,回撥函數通過 data[currentKey] 的方式存取巢狀物件的屬性,然後將這個屬性的值作為下一次迭代的 data, 最終,reduce 方法將遍歷整個鍵路徑,直到達到最深層的屬性,然後返回該屬性的值。這樣我們就可以正常的獲取到資料了,最後在改造一下之前 model 方法獲取值的地方,呼叫下剛剛編寫的 getValue 方法即可:

model: function (node, value, vm) {
    node.value = this.getValue(vm, value);
},

再次開啟瀏覽器,可以看到效果如下:

這個搞定之後,我們緊接著把 v-html 和 v-text 也搞定,程式碼基本上都是一樣的,只是渲染的方式不一樣,程式碼如下:

/**
 * 處理 html 指令
 * @param node 當前元素
 * @param value 指令的值
 * @param vm Nue 的範例物件
 */
html: function (node, value, vm) {
    node.innerHTML = this.getValue(vm, value);
},
/**
 * 處理 text 指令
 * @param node 當前元素
 * @param value 指令的值
 * @param vm Nue 的範例物件
 */
text: function (node, value, vm) {
    node.innerText = this.getValue(vm, value);
}

編寫測試程式碼:

html: `<div>我是div</div>`,
text: `<div>我是div</div>`

編寫HTML程式碼:

<div v-html="html">abc</div>
<div v-text="text">123</div>

開啟瀏覽器,可以看到效果如下: