Vue官方文檔和源碼研讀


2021.08.31開始 運行時的源碼在vue/dist/vue.runtime.esm.js里面,不過有些非函數的定義,打印不出來(看看vue源碼斷點怎么打比較合適)。看官方文檔時候,直接百度中文的那段文字,經常搜不出來,可以先根據官方文檔或自己定位到對應的代碼段,然后搜索代碼段的變量名,就能搜到了 

1.官方文檔 模板語法-插值-使用javascript表達式 最后,有這么一句話:模板表達式都被放在沙盒中,只能訪問全局變量的一個白名單,如 Math 和 Date 。你不應該在模板表達式中試圖訪問用戶定義的全局變量。

開始我以為是vue.prototype.$aa = 1,不能訪問這個$aa。看了源碼之后,才明白。全局對象指的是Array、Object這類的js關鍵字,當然也可以自己定義。比如我寫個js文件 export 1,然后在vue文件中import aaa from ./js文件,在模板語法中使用{{aaa}},就會報錯。因為模板表達式的沙盒里沒有aaa這個變量。想使用的話可以把它掛載到vueprototype上或者直接賦值給當前vue實例的data中的一個屬性

function makeMap (
  str,
  expectsLowerCase
) {
  var map = Object.create(null);
  var list = str.split(',');
  for (var i = 0; i < list.length; i++) {
    map[list[i]] = true;
  }
  return expectsLowerCase
    ? function (val) { return map[val.toLowerCase()]; }
    : function (val) { return map[val]; }
}
var allowedGlobals = makeMap(  //
    'Infinity,undefined,NaN,isFinite,isNaN,' +
    'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' +
    'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt,' +
    'require' // for Webpack/Browserify
  );
var hasHandler = {
    has: function has (target, key) { //target就是當前vue實例,key就是要解析的值
      var has = key in target; //對象的in方法,只要key在target的原型鏈上,就會返回true,所以設置vue.prototype.$aa = 1之后,使用$aa是沒問題的
var isAllowed = allowedGlobals(key) || (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); //這里判斷的是全局變量或者以_開頭的屬性,這么做是由於渲染函數中會包含很多以 _開頭的內部方法,如渲染函數里遇到的 _c_v 等等 if (!has && !isAllowed) { if (key in target.$data) { warnReservedPrefix(target, key); } else { warnNonPresent(target, key); } } return has || !isAllowed } };

最后的判斷!has我們可以理解為你訪問了一個沒有定義在實例對象上(或原型鏈上)的屬性,所以這個時候提示錯誤信息是合理,但是即便!has成立也不一定要提示錯誤信息,因為必須要滿足!isAllowed,也就是說當你訪問了一個雖然不在實例對象上(或原型鏈上)的屬性,但如果你訪問的是全局對象那么也是被允許的。這樣我們就可以在模板中使用全局對象了

 

2.在谷歌瀏覽器的控制台,打印一些數據時。發現同樣的數據,顯示的格式經常是不一樣的,有時是data:{...},有時是data:{a:1},有時是data:{__ob__:Observer}。

經過我的驗證,發現沒被掛載到vue實例上的(被淺拷貝的也算),會直接顯示數據data:{a:1},被掛載到vue實例上並且對象長度小於等於4的,會顯示data:{__ob__:Observer},其余顯示的都是data:{...}

拓展:給vue data屬性的對象添加屬性時,有以下幾種情況。

const a = {content:{a:1,b:2,c:3,d:4,e:5}}
情況一 
this.form = a;
這樣相當於將a的地址賦值給this.form,這時不管是打印a 還是this.form都是一樣的,不會去判斷對象長度是多少,點開查看(調用getter)之前都是顯示{...}
情況二
this.form = {...a}或者this.form = Object.assign({},this.form,a).
這時this.form===a是false,他倆這時的引用類型屬性是共用一個地址,但是基本類型的值是互不干擾的。
打印a,會判斷長度,小於等於4的顯示{__ob__:Observer},大於4的顯示{...};
打印this.form,不會判斷長度,統一顯示{...}
情況三
this.form = JSON.parse(JSON.stringify(a)) //深拷貝
這時打印a,會直接顯示所有的值,不會有{...}和{__ob__:Observer},因為它沒有被vue影響
打印this.form,不會判斷長度,統一顯示{...}
但是,以上三種情況,當我直接打印this.form.a時,都會顯示一個{__ob__:Observer}。

 具體原因要等以后看源碼再了解了(2021.09.07)

  

3.當你設置 vm.someData = 'new value',該組件不會立即重新渲染(這里指的是html里的dom元素 不會重新渲染)。當刷新隊列時,組件會在下一個事件循環“tick”中更新。多數情況我們不需要關心這個過程,但是如果你想基於更新后的 DOM 狀態來做點什么,這就可能會有些棘手。雖然 Vue.js 通常鼓勵開發人員使用“數據驅動”的方式思考,避免直接接觸 DOM,但是有時我們必須要這么做。為了在數據變化之后等待 Vue 完成更新 DOM,可以在數據變化之后立即使用 Vue.nextTick(callback)。這樣回調函數將在 DOM 更新完成后被調用。

因為 $nextTick() 返回一個 Promise 對象,所以你可以使用新的 ES2017 async/await 語法完成相同的事情:

methods: {
  updateMessage: async function () {
    this.message = '已更新'
    console.log(this.$el.textContent) // => '未更新'
    await this.$nextTick()
    console.log(this.$el.textContent) // => '已更新'
  }
}

  當我使用vue檢測不到的方法來增減對象和數組時,可能是數據驅動不了視圖,也可能是數據驅動了視圖,但通過this.$refs.textContent是獲取不到更新后的dom的,這時候就要用$nextTick了。(具體原因要等源碼的研究了 2021.09.07)

 

4.VUE事件修飾符 https://cn.vuejs.org/v2/guide/events.html#%E4%BA%8B%E4%BB%B6%E4%BF%AE%E9%A5%B0%E7%AC%A6

注意: 特殊的系統修飾鍵有ctrl、alt 、shift、 meta(window鍵),例如 @key.alt.67  //alt+c   @click.ctrl   //ctrl+click

修飾鍵與常規按鍵不同,在和 keyup 事件一起用時,事件觸發時修飾鍵必須處於按下狀態。換句話說,只有在按住 ctrl 

的情況下釋放其它按鍵,才能觸發 keyup.ctrl。而單單釋放 ctrl 也不會觸發事件。如果你想要這樣的行為,請為 ctrl 換用 keyCodekeyup.17

2.5.0新增的exact修飾符 允許你控制由精確的系統修飾符組合觸發的事件。

<!-- 即使 Alt 或 Shift 被一同按下時也會觸發。只監聽ctrl,不管同時有其他幾個按鍵在按 -->  

<button v-on:click.ctrl="onClick">A</button>

<!-- 有且只有 Ctrl 被按下的時候才觸發 -->

<button v-on:click.ctrl.exact="onCtrlClick">A</button>

<!-- 沒有任何系統修飾符被按下的時候才觸發 -->

<button v-on:click.exact="onClick">A</button>

鼠標修飾符有 .left .right .middle 

 

5.復選框<input type="checkbox" v-model="toggle" true-value="yes" false-value="no">

// 當選中時 vm.toggle === 'yes' // 當沒有選中時 vm.toggle === 'no'

這里的 true-value 和 false-value attribute 並不會影響輸入控件的 value attribute,因為瀏覽器在提交表單時並不會包含未被選中的復選框。如果要確保表單中這兩個值中的一個能夠被提交,(即“yes”或“no”),請換用單選按鈕。

翻譯:比如有這樣的頁面

<input type="checkbox" v-model="picked" :true-value="value1" :false-value="value2">
        <label> 復選框 </label>
        <p> {{picked}} </p>
        <p>{{value1}} </p>
        <p>{{value2}} </p>
data: {
              picked:false,
              value1:123,
              value2:345
            }

 那么剛進頁面時,picked的值就是false,這個value1和value2的值是不會影響picked的值的。

 但是一旦對這個chekcbox做了勾選或取消勾選的操作,這個picked的值就會變成123或者456。

拓展:如果換成radio的話,剛進頁面是一樣的,但是一旦做了操作,picked的值就會變成null。

 

6.閱讀vuex官方文檔時,對於rootState一直無法理解,官方文檔對於它的定義是 根節點狀態。但是有rootState.count這種用法,而我打印的時候,rootState底下就是幾個module對象,沒有.count這一層級。

原因:參考了一些文檔,發現.count確實是根節點的狀態,並且想獲取這個層級的值,就要在new Vuex.Store()時給根節點賦一個state對象。像這樣

const store = new Vuex.Store({
  state: {
    count: 1,
  },
  modules: {
    a: moduleA,
    b: moduleB,
  },
})

  這時候在moduleA的action或者getters里面打印rootState,里面就有count和a、b兩個modules里面的state對象。

 

7.Vue 深入響應式原理說明:Vue 不能檢測數組和對象的一些變化,也就是說數據更新時視圖不會更新。但是實際使用中發現還是更新了。

原因: 查了一些文檔,沒發現回答。自己又測試了一下,發現官方文檔寫的是沒有問題的。操作的時候,如果只有arr.length=1或者arr[0] = 1這種操作時,不會觸發視圖更新。但是如果有其他的能被vue檢測到的數據更新(this.count='xx),就會順帶把vue檢測不到的數據變動也一起更新了,這樣就實現了類似arr.length = 1也被vue檢測到的效果。但是這樣只更新了視圖,並沒有給這個新數據加上getter和setter,在控制台打印可以看到,有getter和setter的會默認不顯示,沒有的會直接顯示出來。

壞處:目前能想到的壞處,就是雖然視圖更新了,但是vue的watch是監聽不到數據變化的。因為這個數據沒有setter,watch是靠監聽setter來實現的。

    export default {
      data() {
        return {
          items: ['a', 'b', 'c'],
   count:1 } }, methods: { hah() { this.items.length = 2 // 不是響應性的,沒有被vue檢測到
      //this.count = 'xx' //是響應性的,會觸發vue更新 setTimeout(() => { this.items.shift() },3000) }, }, watch: { items(new,old) { console.log(new,old) // 1,3 ,vue只能監聽到一開始的長度3和shift之后的長度1 } } }

源碼解析:簡單打斷點看了一下,應該是this.count觸發了它自己的set和watch.然后又觸發了vue的$nextTick和render方法,vue就順帶把當前實例的數據都更新到視圖上了。但是並沒有給新屬性加上setter和getter,也沒有觸發其他屬性的watch。

經驗:以后對於vue檢測不到的變動,還是都用this.$set來操作吧。因為不可能每次都正好有別的屬性在更新,萬一一開始有,后面又拿掉了那個更新的屬性操作,vue就檢測不到更新了,這是在埋bug.

 

8.vue官方文檔說到父組件引用子組件時的標簽名和props在標簽上的使用,是這樣的

使用 kebab-case    Vue.component('my-component-name', { /* ... */ })

當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在引用這個自定義元素時使用 kebab-case,例如 <my-component-name>

使用PascalCase     Vue.component('MyComponentName', { /* ... */ })

當使用 PascalCase (首字母大寫命名) 定義一個組件時,你在引用這個自定義元素時兩種命名法都可以使用。也就是說 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,盡管如此,直接在 DOM (即非字符串的模板) 中使用時只有 kebab-case 是有效的

重點在最后一句,我在.vue文件中直接使用<MyComponent userName="a"/>是可用的,這是為啥?

原因:vue官方文檔經常提到的字符串模板和非字符串模板,其實是這樣定義的

1.字符串模板就是寫在vue中的template中定義的模板,如.vue的單文件組件模板和定義組件時template屬性值的模板。字符串模板不會在頁面初始化參與頁面的渲染,會被vue進行解析編譯之后再被瀏覽器渲染,所以不受限於html結構和標簽的命名。

Vue.component('MyComponentA', {
    template: '<div MyId="123"><MyComponentB>hello, world</MyComponentB></div>'
})

<div id="app">
    <MyComponentA></MyComponentA>
</div>

2.dom模板(或非字符串模板、Html模板)就是寫在html文件中,一打開就會被瀏覽器進行解析渲染的,所以要遵循html結構和標簽的命名,否則瀏覽器不解析也就不能獲取內容了。

下面的例子不會被正確渲染, 會被解析成mycomponent,但是注冊的vue的組件是MyComponent,因此無法渲染。

<!DOCTYPE <html>
    <head>
        <meta charset="utf-8">
        <title>Vue Component</title>
    </head>
    <body>
        <div id="app"> 
            Hello Vue
            <MyComponent></MyComponent>
        </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script >
        //全局注冊
        Vue.component('MyComponent', {
            template: '<div>組件類容</div>'
        });
        new Vue ({
            el: '#app'
        });
    </script>
    </body>
</html>

  

所以,下面的例子就可以正常顯示了:

<!DOCTYPE <html>
    <head>
        <meta charset="utf-8">
        <title>Vue Component</title>
    </head>
    <body>
        <div id="app"> 
            Hello Vue
            <my-component></my-component>
        </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
    <script >
        //全局注冊
        Vue.component('my-component', {
            template: '<div>組件類容</div>'
        });
        new Vue ({
            el: '#app'
        });
    </script>
    </body>
</html>

因為html對大小寫不敏感,所以在DOM模板中使用組件必須使用kebab-case命名法(短橫線命名)。
因此,對於組件名稱的命名,可參考如下實現:

/*-- 在單文件組件、JSX和字符串模板中 --*/
<MyComponent/>
/*-- 在 DOM 模板中 --*/
<my-component></my-component>
或者
/*-- 在所有地方 --*/
<my-component></my-component>

  

9,vuex官方文檔說只能通過mutation來修改state,而實際使用中,發現this.$store.state.a = 132是可以生效的,並且其他組件也都可以訪問,只不過它沒有被設置set和get,想要這兩個用this.$set就可以了。不過在vuex的嚴格模式下會報錯。

開啟嚴格模式,僅需在創建 store 的時候傳入 strict: true
const store = new Vuex.Store({ // ... strict: true 或者
 strict: process.env.NODE_ENV !== 'production'
})

在嚴格模式下,無論何時發生了狀態變更且不是由 mutation 函數引起的,將會拋出錯誤。這能保證所有的狀態變更都能被調試工具跟蹤到。

總結:為了防止vuex的數據改變來源難以追蹤,使用中統一用mutation來改變state。也可以在開發過程中開啟vuex嚴格模式(生產環境中必須關閉)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM