手撕Vue-資料驅動介面改變下

BNTang發表於2023-10-19

經過上一篇的介紹,資料驅動介面改變 v-model 的雙向繫結已告一段落, 剩餘的就以這篇文章來完成。

首先完成我們的 v-html,v-text, 其實很簡單,就是將我們之前的 v-model 建立觀察者的方法,在 v-html 和 v-text 中再寫一次即可,建立屬於 v-html 和 v-text 的觀察者。

v-html:

html: function (node, value, vm) {
    new Watcher(vm, value, (newValue, oldValue) => {
        node.innerHTML = newValue;
    });
    node.innerHTML = this.getValue(vm, value);
},

v-text:

text: function (node, value, vm) {
    new Watcher(vm, value, (newValue, oldValue) => {
        node.innerText = newValue;
    });
    node.innerText = this.getValue(vm, value);
},

測試一下 v-html,開啟瀏覽器控制檯,輸入 vue.$data.html = '<p>我是測試v-html<p/>',可以看到介面上的內容已經改變了。

image-20231019224801735

測試一下 v-text,開啟瀏覽器控制檯,輸入 vue.$data.text = '<p>我是測試v-text<p/>',可以看到介面上的內容已經改變了。

image-20231019224912964

好了到此為止,指令的資料驅動介面改變已經完成了,接下來我們來完成模板語法的資料驅動介面改變。

這個就與之前的指令的資料驅動介面改變不同了,好了先不說問題,我們先直接來看程式碼一步一步分析。

我看了下之前處理 content 的程式碼發現,獲取不到對應的屬性名稱叫什麼,因為是直接呼叫 this.getContent(vm, value); 獲取的,所以會出現一個問題就是給 content 建立觀察者物件的時候不能直接告訴他我要監聽的是哪個屬性,所以我就想到了一個辦法。

首先將之前的程式碼註釋掉,再然後我編寫一個正規表示式,關於這個正規表示式在之前的文章中有講到,大概意思就是匹配 {{}} 中的內容,這裡就不再贅述了。

let reg = /\{\{(.+?)\}\}/gi;

繼續往下看,我利用 value 呼叫了 replace 方法,傳遞了兩個引數,第一個引數是剛剛編寫的正規表示式,第二個引數是一個函式,這個函式的作用就是將匹配到的內容替換成對應的值,我先將其返回值列印出來,看看是什麼,我們的程式碼就可以寫成這樣。

content: function (node, value, vm) {
    // console.log(value); // {{ name }} -> name -> $data[name]
    // node.textContent = this.getContent(vm, value);

    let reg = /\{\{(.+?)\}\}/gi;

    value.replace(reg, (...args) => {
        console.log(args[1].trim());
    });
}

image-20231019230456960

是的,我們的確獲取到了對應的屬性名稱,接下來我們就可以利用這個屬性名稱來建立觀察者物件了,我們的程式碼就可以寫成這樣。

content: function (node, value, vm) {
    // console.log(value); // {{ name }} -> name -> $data[name]
    // node.textContent = this.getContent(vm, value);

    let reg = /\{\{(.+?)\}\}/gi;

    node.textContent = value.replace(reg, (...args) => {
        const attr = args[1].trim();
        new Watcher(vm, attr, (newValue, oldValue) => {
            node.textContent = this.getContent(vm, value);
        });
        return this.getValue(vm, args[1]);
    });
}

好了,我們來測試一下,開啟瀏覽器控制檯,輸入 vue.$data.name = '我是測試 {{ name }}',可以看到介面上的內容已經改變了。

image-20231019230944283

一切看起來都很完美,最終版程式碼其實是我沒有將坑點說出來,現在我們來看看這個坑點是什麼,再看之前,我來講述一下為什麼是又呼叫了 this.getContent 方法而不是直接將 newValue 賦值給 node.textContent。

假如我們的資料結構是這樣的 {{ name }} - {{ age }} 如果是透過直接將 newValue 賦值給 node.textContent 的話,這個時候呢,我假設 name 的值是 BNTang, age 的值是 33,那麼介面上第一次載入的內容會是 BNTang - 33,但是如果我將 name 的值改成了 xhh,那麼介面上的內容就會變成 xhh,這個時候 age 的值就丟掉了,如下圖是我的測試結果。

image-20231019233644556

原因就是直接替換掉了,所以在動態更改 name 屬性或者 age 屬性其中一個的情況下,還需要將 {{ name }} - {{ age }} 這樣的內容替換成 BNTang - 33,這樣的話,我們就需要呼叫 this.getContent 方法,這個方法就會利用正則挨個匹配 {{}} 中的內容,然後再將其替換成對應的值,這樣就不會出現上面的問題了。

?> 最後總結一下 content 函式的 value.replace 在外層是為了拿到屬性名稱,內層是為了保證資料完整性。

相關文章