Vue 綱領

本文使用正体字书写

 

 
Vue 綱領_第1张图片
  

抓住渔网的总绳,拿起衣服的领子

 

如在拿起一件衣服时,拿起衣领和拿起衣角的差距
 

新同文堂简繁互转插件
 

説明

本文性質: 基於Vue2.6.11及其源碼的學習報告
本文内容:Vue源碼學習、Vue生態功能開發學習
本文結構

  1. Vue的核心邏輯的實現
  2. Vue的具體實現
  3. Vue的重要組成
  4. Vue的核心插件

Vue源碼部分有改編,僅供參考
 

目錄

  1. 生命週期
  2. 組件
  3. 指令
  4. 數據交流等重要要素
  5. 路由
  6. 狀態管理器

 

甲. 纲

 

Vue是數據驅動視圖渲染的MVVM

Vue的核心工作内容:數據、視圖、渲染

數據:監聽與視圖相關的數據
視圖:
渲染:解析HTML標簽并且渲染到頁碼

此三點是MVVC框架的核心邏輯,不同框架之間其具體實現不盡相同
關係

視圖的渲染的輸出
數據是渲染的動力
渲染連接起數據和視圖的橋梁:使數據變化引發視圖變化

1.數據

數據的核心是:監聽數據

  1. 監聽形式:存取描述符--Object.defineProperty
  2. 依賴數據:需要被監聽的數據
  3. 主要源碼位置:src/core/observer

監聽形式

  1. 數據監聽的邏輯--index.js
class Observer {   // 該類遞歸地把對象的所有成員都轉爲被監聽的數據
  constructor(value) {
    this.value = value
    this.dep = new Dep()        // 依賴收集的相關邏輯
    this.vmCount = 0            // 先忽略
    def(value, '__ob__', this)  // __ob__:標記--已被監聽的數據
    if (Array.isArray(value)) {
      // 數組預處理
      (('__proto__' in {}) &&
        protoAugment(value, arrayMethods)
      ) ||
        copyAugment(
          value,
          arrayMethods,
          Object.getOwnPropertyNames(arrayMethods));
      this.observeArray(value)   // 數組的監聽處理
    } else {
      // 非數組成員的監聽形式        
      this.walk(value);
    }
  }
  walk(obj) {    // 把對象的每個成員都執行監聽處理
    Object.keys(obj)
      .forEach(key => defineReactive(obj, key));
  }
}
// 工具 
function def(obj, key, val, enumerable) {
  Object.defineProperty(obj, key, {
    value: val,
    enumerable: !!enumerable,
    writable: true,
    configurable: true
  });
}
// defineReactive函數:關於對象的監聽實現
// observeArray函數:關於數組的監聽的具體實現
const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);



2. 對象object的監聽的邏輯:工具函數defineReactive的邏輯

function defineReactive(
obj,
key,
) {
var val = obj[key];
(typeof val === 'object') && (new Observer(val)); //遞歸完成深監聽
Object.defineProperty(obj, key, {

enumerable: true,
configurable: true,
get() {
     console.log(`讀取數據--${key}`);
  // 佔位符:依賴數據收集的相關内容
  return value
},
set(newVal) {
  if (newVal === value || (newVal !== newVal && value !== value)) {
    return;            // 相同值和 NaN的處理
  }
 console.log(`修改數據--${key}`); 
 // 佔位符:依賴數據變化時,執行回調--視圖更新的相關内容
 val = newVal
}

})
}

  1. 數組成員的監聽
· 文件位置: array.js
· 成員存取描述符不能用於數組成員
· 數組成員可通過 重寫數組方法來實現監聽
// 直接在數組原型對象上寫
function arrayMethods() {
 methodsToPatch.forEach(method => {
   Object.defineProperty(Array.prototype, method, {
     configurable: true,
     writable: true,
     value: function mutator(...args) {
       console.log("調用數組方法");
       // 觸發回調函數--視圖更新
       return original.apply(this, args);
     }
   });
 });
}
 
·  Vue的實現:影響範圍最小化--修改依賴數據的[原型鏈](https://juejin.im/post/6859558780762325006)

const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
];
methodsToPatch.forEach(method => {
const original = arrayProto[method];
Object.defineProperty(arrayMethods, method, {

configurable: true,
writable: true,
value:function mutator(...args) {
  console.log("調用數組方法");
  // 觸發回調函數--視圖更新
  return original.apply(this, args);
}

});
}

· 原型連接:  其邏輯是實現數據監聽類Observer的預處理部分

    Mvvm1.prototype.deps = function (data) {
      let warcher = new Watcher();
      for (let key in data) {
       
        warcher.setDom(this.$el, key);
        // 響應式實現
        Object.defineProperty(this, key, {
          get() {
            return data[key];
          },
          set(nv) {
            setTimeout(() => {
              data[key] = nv;
              warcher.notify(key, nv);
            }, 0)
        });
      }
      // 初始化頁面
      warcher.updata(data);
    };

    let mv1 = new Mvvm1({
      el: "#app1",
      data: {
        msg: "默認msg",
        info: "默認info"
      }
    });

 

### 依賴數據
文件位置:`dep.js`  
功能要求:  
1. 依賴收集:把視圖裏需要用到的數據收集起來  
2. 依賴回調:在依賴數據發生變化時,觸發相關回調函數--視圖更新   

功能實現:  
1. 通過[發佈訂閲模式](https://juejin.im/post/6860669403827503112/),創建一個依賴管理器Dep  
> 依賴數據的添加,通知,刪除
2. 通過[中介模式](https://juejin.im/post/6860669403827503112/),創建一個依賴感知器Watcher  
> 連接依賴管理器、依賴回調、依賴數據  
> 依賴數據變化時,通知依賴感知器,由依賴感知器執行回調
3. 數據鏈接  
> 1. 對象:在存取描述符裏進行依賴添加和通知  
> 2. 數組:在存描述符裏進行依賴添加,修改數組的方法裏通知變化
4. 

class Dep {
constructor() {

this.id = uid++;
this.subs = [];

}
addSub(sub) {

this.subs.push(sub);

}
removeSub(sub) {

remove(this.subs, sub);

}
depend() { // 依賴添加

Dep.target && this.addSub(Dep.target);

}
notify() { // 依賴通知

this.subs.slice()
  .forEach(sub => sub.update());

}
}

class Watcher {
 constructor(vm, expOrFn, cb,) {
   this.vm = vm;
   this.cb = cb;
   this.id = ++uid;
   this.getter = parsePath(expOrFn);
   this.value = this.get();
 }
 get() {
   window.target = this;
   const vm = this.vm
   let value = this.getter.call(vm, vm)
   window.target = undefined;
   return value
 }
 update() {
   const oldValue = this.value;
   this.value = this.get();
   this.cb.call(this.vm, this.value, oldValue);
 }
}
const bailRE = /[^\w.$]/;
function parsePath(path) {
 if (bailRE.test(path)) {
   return
 }
 const segments = path.split('.')
 return function (obj) {
   for (let i = 0; i < segments.length; i++) {
     if (!obj) return
     obj = obj[segments[i]]
   }
   return obj
 }
}



### 細節完善
雙刃劍及解決方法
甲、非響應式的操作
當變量是引用類型時,該變量的值是地址--若地址沒有改變,不會引發響應式
⑴引擎限制:執行順序
自定義的指令,組件等要在創建實例前生成
⑵JS自身限制:     在vue完成實例第二步的初始化後,
直接在data對象上進行增加的變量,是非響應式的
無法通過vm.$delete刪除data上的變量
①、響應式是在第二步的初始化階段使用屬性描述性形成的
Ⅰ、限制:修改數組元素的操作是非響應式的
解決: VUE對数组方法進行了重寫--更新检测,也會觸發響應式(所有可修改數組的方法)
增刪:splice()、push()、pop()、shift()、unshift()
排序:sort()、reverse()
Ⅱ、限制: 直接用索引设置数组项、修改数组的长度时,是非響應式的
例如:vm.items [ indexOfItem ] = newValue
vm.items.length = newLength
解決: 方法1、要使用vue.$set()
如:異步獲取的數據,需要使用vm.set動態添加
但使用 async 可以 實現同步的異步請求,而無該限制
方法2、 或者在這兩種操作後,調用被Vue重寫的數組方法
Ⅲ、限制:在把一個變量(對象類型)賦值給data的一個變量,該變量是響應式的; 
但在給該變量新添加屬性時,是非響應式的
解決: 1、先在該變量上添加屬性後再賦值

2、使用 vue.$set() 對該變量新添加屬性
this.$set(this.someObject, 'b' ,  2 )
3、为已有对象赋值多个新屬性: 
this.someObject = Object.assign( { },  this.someObject,  { a: 1, b: 2 } )
而Object.assign(this.someObject, { a: 1, b: 2 }) 的非響應式的
Ⅳ、限制: 在建立觀察後,無法在根數據對象上(data之類)添加響應式數據
解決:預留變量,并且设置初始值
⑶、vue.$set()使用:
vm.$set( target, propertyName/index, value )
参数:
{Object | Array} target
{string | number} propertyName/index
{any} value
返回值:设置的值
⑷、使用 Object.freeze(),会阻止修改现有的 property--响应系统无法追踪变化


## 2. 視圖
### 虛擬節點樹
**爲了實現在操作節點樹上的性能優化,來描述節點樹DOM**  
文件位置:`src/core/vdom`  
**組成:**
1. 虛擬節點
> 以JS對象形式,通過定義少量的重要的節點屬性來描述節點樹
2. 對比算法Diff
> 優化節點更新的邏輯


function createElement(domobj) {
  let tempDom = document.createElement(domobj.tagName);
  if (domobj.childrens && domobj.length != 0) {
    for (let i = 0; i < domobj.childrens.length; i++) {
      let td = createElement(domobj.childrens[i]);
      tempDom.appendChild(td);
    }
  }
  return tempDom;
}
let domTree = createEle(domObj);

虛擬節點VNode

文件:vnode.js
創建類VNode

虛擬節點的屬性要足夠少而且能正確描述真实的節點樹
class VNode {
  constructor(
    tag,
    data,
    children,
    text,
    elm,
    context,
    componentOptions,
    asyncFactory
  ) {
    this.tag = tag
    this.data = data    /*節點對應的對象,含數據信息,VNodeData類型*/
    this.children = children  /*子節點是數組*/
    this.text = text     /*節點文本*/
    this.elm = elm       /*虛擬節點對應的dom節點*/
    this.ns = undefined        /*節點的名字空間*/
    this.context = context          /*組件節點對應的Vue實例*/
    this.fnContext = undefined       /*函數式組件對應的Vue實例*/
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key   /*節點的標誌,用以優化*/
    this.componentOptions = componentOptions   /*組件的option選項*/
    this.componentInstance = undefined       /*節點對應的組件的實例*/
    this.parent = undefined           /*父節點*/
    /*是否為原生HTML或普通文本,innerHTML的時候為真,textContent的時候為假*/
    this.raw = false         
    this.isStatic = false         /*靜態節點標誌*/
    this.isRootInsert = true      /*是否作為跟節點插入*/
    this.isComment = false             /*是否為註釋節點*/
    this.isCloned = false           /*是否為克隆節點*/
    this.isOnce = false                /*是否有v-once指令*/
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }
  get child() {
    return this.componentInstance
  }
}


實例:六種節點類型
> 註釋節點 文本節點 元素節點 組件節點 函數式組件節點 克隆節點
1. 注釋節點

const createEmptyVNode = (text) => {
const node = new VNode()
node.text = text // 注釋的信息
node.isComment = true // 為註釋節點
return node
}

對比算法

1.先同级比较,在比较子节点
2.先判断一方有儿子一方没儿子的情况
3.比较都有儿子的情况
4.递归比较子节点

3. 渲染

  1. 視圖、數據、渲染的關聯
  • 在視圖渲染之前,把模板template編譯成VNode並緩存下來
  • 在數據變化時,對比新舊節點,進行必要的重新渲染

抽象語法樹

解析模板

優化模板

生成代碼

乙. 生命周期

生命周期的四個階段:初始化階段、容器創建挂載階段、數據更新階段、實例銷毀階段

每个Vue实例在被创建时要经过初始化过程:
如设置数据监听、编译模板、将实例挂载到節點樹并在数据变化时更新節點樹等

包括运行生命周期钩子函数

Vue官方生命周期图

 //  src/core/instance/index.js
function Vue(options) {
!(this instanceof Vue)

) {

warn('Vue is a constructor and should be called with the `new` keyword')

}
this._init(options)
}
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
// src/core/instance/init.js
Vue.prototype._init = function (options) {

    .......
const vm = this
vm.$options = mergeOptions(
    resolveConstructorOptions(vm.constructor),
    options || {},
    vm
)
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
if (vm.$options.el) {
  vm.$mount(vm.$options.el)
}

}

實驗數據:在鈎子函數,用debugger或打印輸出:  

beforeCreate:在實例初始化之後,數據觀察和事件配置之前被調用
created:在實例創建完成后調用
beforeMount:在挂載開始之前被調用:相關的render函數首次被調用
mounted:el被新創建的vm.$el替換,並挂載到實例之後調用
beforeUpdata:數據更新之後被調用,重新渲染虛擬節點樹之前
updated:虛擬節點樹已經渲染到頁面,數據更新完成
beforeDestroy:實例銷毀之前調用。此時實例尚未銷毀
destroyed:實例銷毀後被調用,實例的東西被解除,包括事件監聽等;但數據保留

第一階段:初始化

⑴、 new Vue:搭建運行環境,整合構建實例的相關參數
①、生成了一個VUE的樣板對象
⑵、 初始化第一步
①、初始化VUE環境事件--實現VUE的環境功能的方法定義
如構成響應式原理、數據監視、watch/event 事件回调
②、初始化鈎子函數
③、此時完成了VUE的環境搭建
Ⅰ、在$options中有相關配置
1、該屬性是以實參所構成的相關值的對象
如:鈎子函數
Ⅱ、但實例是沒有裝載相關配置
beforeCreate: 完成了VUE的環境搭建,還沒有創建實例--裝載配置
⑶、初始化第二步:完成實例的創建
①、參數注入:數據、監視、計算屬性、方法庫等
②、響應式數據劫持生成:執行$options.data的函數:mergedInstanceDataFn()
該函數生成$data數據倉庫:
根據$options.data創建數據劫持對象:this.\$data
將變量定義到當前實例根屬性上:this.msg
③、
created:此時實例已經創建完畢,還沒有挂載容器,但已可以進行數據操作

    new Vue({ 
      el: "#app",
      data: {
        msg: "默認msg",
      },   
      created() {
        console.log("實例: ", this);
        console.log("$el: ", this.$el);//undefined
        console.log("$options: ", this.$options);//根據配置參數創建構成實例的相關對象值
        console.log("$data: ", this.$data);//$options.data創建數據劫持對象
        console.log("msg: ", this.msg);//將變量定義到該實例根屬性上
      },
    });

第一階段已經完成VUE環境事件和實例的初始化
第二階段:創建並挂載容器
第二階段進行實例與容器的連接:
根據提供的頁面結構生成與當前實例存在關係的相關容器結構

⑴、獲取“el”值:
①、當沒有指定“el”值時,要手動調用挂載函數;否則程序在此處停止

 Ⅰ、vm.$mount( el );   

Ⅱ、vm.$mount( ); 空值,則此時必須有模板--template;否則程序在此處停止
②、el作用:該實例與頁面容器的關係--該實例的作用的頁面位置及範圍
Ⅰ、可以取標簽的id、class;多餘符合的多個class,則取第一個元素
Ⅱ、el原理是使用document.querySelector
Ⅲ、$el是指該實例所創建的節點樹的根節點
⑵:創建虛擬節點樹
①、啓動編譯函數Complie:生成虛擬節點樹,完成數據關聯與操作
Ⅰ、根據模板/el生成虛擬節點樹,即將模板轉爲JS對象
Ⅱ、
Ⅲ、已經完成指令等内容的執行、
②、執行render函數: 生成節點樹,將虛擬節點樹提供給該函數
同時完成數據關聯
一個節點樹結構的生成
beforeMount:此時生成的節點樹要在下一步才添加到頁面中
⑶:挂載容器:
①、 vm.$el = 虛擬節點樹 :此時el為模板或者原來的el
②、將虛擬節點樹 ,轉化爲節點樹
若是組件,則是替換組件標簽(組件在之前都沒有el)
mounted:此時容器和實例已經插件完畢
適合用於初始化數據請求
此時節點樹還沒有寫入頁面中

第三階段:更新數據
把節點樹寫入頁面,進入更新階段

這兩個周期都發生在數據改變之後:所以是監控數據變化後的虛擬節點樹的重新渲染
在數據變化時,插入鈎子函數:alert(),以阻斷生命周期的運行,以查看頁面改變情況
VUE中是:异步更新队列
beforeUpdata:此時接收到頁面數據變化,但還沒有把頁面重新渲染
重新渲染虛擬節點樹並寫入頁面
updated:此時已經把頁面重新渲染了

也是$nextTick的執行階段

watch: 在數據變化之前 是
$watch:若監控多個變量,則傳入回調函數的值是新舊返回值
可以在回調函數内部直接調用unwatch()取消觀察函數
第四階段:銷毀實例

VUE的功能消失,但數據還保持著
銷毀VUE實例--VUE的功能;由其構建的數據,不會被銷毀

模板及渲染函數
⑴、關係:el定位、template模板、render渲染

定位    模板    渲染

作用 定位頁面位置 創建節點樹模板 把虛擬節點樹渲染成爲節點樹
適用場景 只new Vue()中使用 組件化 模塊化
優先級 最低 第二 最高

1、模板會替代定位指向的標簽;

2、傳入渲染函數的模板會替換模板、以及定位頁面位置的標簽

丙. 組件

自定義標簽及其標簽功能
VUE實例相當於一個組件--根組件
每一個組件都是獨立的
構造器繼承函數Vue.extend

const a = Vue.extend({
  template: `

標題

`

});
①、參數--選擇項options:
Ⅰ、不能使用el,el只能在Vue構造器中使用
Ⅱ、應該有template
Ⅲ、data數據封裝在由函數return語句生成的對象裏
Ⅳ、其他如Vue構造器
②、繼承構造器
Ⅰ、Vue繼承函數生成并且返回一個繼承構造器
Ⅱ、繼承構造器不接收參數
③、使用
Ⅰ、作爲獨立頁面功能插件
1、使用new調用繼承構造器生成vue實例
⑴、因爲沒有el:該實例停在第二階段的第一步
2、 挂載$mount
⑴、調用 .$mount( )挂載,無定位el
①、調用挂載可以外部挂載,也可以在created中挂載
②、實例完成第二階段,此後該實例的$el指向渲染出來的節點樹
③、使用appendChild( “ vm.$el” )
⑵、調用 .$mount(“頁面定位” )
①、挂載并添加到頁面,進入生命周期的第三階段
3、插件案例:彈出框

Ⅱ、作爲組件配置項,傳入組件定義器
注冊組件
⑵、組件定義器component
利用html5的template創建一個自定義標簽
Vue.component--創建全局組件
①、定義形式
Vue.component("組件名字", 組件配置 ) ;
Ⅰ、特點
在頁面使用組件時,自動new調用
Ⅱ、參數1: 定義組件名字,也是該組件在頁面的標簽名
Ⅲ、參數2:配置組件内容,兩種形式
1.、合并寫法:
i、可以是Vue.extend的返回值、
ii、如Vue.extend參數形式
2、分離式寫法:創建位置與脚本分離,然後通過標簽的id屬性來獲取
i、 脚本形式

ii、 HTML5 模板標簽形式

iii、使用id連接,并且注冊
Vue.component("a-com", {

  template: "#ad" 
});

Ⅵ、 let x = Vue.component("組件名字" ) ; 獲取一個已經注冊的組件
⑶、創建局部該組件
Ⅰ、組件模板可以采取合并和分離式寫法
Ⅱ、書寫地方
①、在組件的 components:屬性中添加
②、VUE實例中的局部組件

const app = new Vue({
  el: "#appp",
  data: {
    msg: "amtf"
  },

components: {
aa : aa // 使用vue.extend的返回值、組件定義器的返回值

   bb: {  組件配置  }

}

});

0.局部組件:只在該實例中使用,不可在其他VUE實例中使用
1.通過Vue構造函數内部注冊:通過 components: { aa }

  1. 鍵是組件的標簽名;值是組件配置

3.全局組件與局部組件出現重名時,局部組件有更高優先級
組件會在生命周期mounted前一步把節點樹渲染到頁面,同時把該組件標簽賦值給$el
⑷、使用組件
①、生成的組件,只能添加在VUE實例的容器内
可以直接在 new Vue()定位的頁面中
可以直接在其他組件的template中
②、注意循環調用組件
③、設置忽略自定義標簽名字:Vue.config.ignoredElements
限制
⑴、組件的data
①、必須是由函數返回的對象,否則報錯
②、若要引用同一個對象,則可以返回一個對象標識符,或者可以不適應函數封裝
⑵、每個組件必須要有一個根元素
⑶、組件名:
①、多單詞、
②、單詞大寫開頭
③、駝峰、或者橫綫連接
若使用駝峰,則在標簽上使用橫綫連接
⑷、編譯作用域:
父级模板里的所有内容都是在父级作用域中编译的;
子模板里的所有内容都是在子作用域中编译的
①、在VUE實例中,組件變量取該實例的變量
②、在組件模板中,取該組件的變量
⑸、組件template必須要有一個跟標簽,其他標簽放在該標簽中
組件的生命周期
组件的调用顺序都是先父后子, 渲染完成的顺序是先子后父
组件的销毁操作是先父后子, 销毁完成的顺序是先子后父
⑴、在初始化階段到挂載階段

Ⅰ、原理:每一個組件都有其生命周期
①、在將節點樹渲染到頁面時--mount的前一步,
得保證已經獲取該組件的全部節點樹,所以此時要進入内層組件
②、在獲取了全部節點樹后開始渲染到頁面
③、在有内層嵌套時,得先完成内層組件的節點樹的渲染
組件構建完成beforeMount后會檢查是否有子/兄弟組件,有則進入子/兄弟組件的構建
Ⅱ、具體順序
父beforeCreate => 。。父beforeMount => 子① beforeCreate => 。。 子①beforeMount =>
子② beforeCreate => 。。 子②beforeMount => 子①mounted => 子②mounted => 父mounted
①、所有組件的beforeMount及之前的生命周期順序:
1.由頁面定義順序自上而下
2.由外層組件到内層組件

  1. 一旦進入組件的生命周期,要先完成該組件的這三個周期再進入下一個組件

②、所有組件的beforeMount執行完畢后:
1.由元素定義順序自上而下,非頁面定義順序
2.由内層組件到外層組件
3.執行完mounted才進入下一個組件

  1. 子組件在第二階段的第三步:

把子組件的創建好的虛擬節點樹賦值給$el,
并且替換在頁面中定義在父組件内的標簽位置
⑵、更新階段
①、子组件更新过程:父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
②、父组件更新过程:父beforeUpdate -> 父updated
⑶、銷毀階段
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed
⑷、組件獨有生命周期
keep-alive

keep-alive是Vue的内置组件,其中的生命周期只会走一遍,并且会增加activated和deactivaed生命周期,用以其他的业务逻辑。

通常用来包裹动态切换的路由或组件,防止组件频繁地创建和销毁,从而达到性能优化的效果。

keep-alive常用的属性
include:字符串或正则表达式。匹配的组件会被缓存。

exclude:字符串或正则表达式。匹配的组件都不会被缓存。

max:数字。最多可以缓存多少组件实例。

activated活跃状态

当前组件显示时执行的生命周期

deactivated缓存状态

当前组件缓存时执行的生命周期

errorCaptured

当捕获一个来自子孙组件的错误时被调用。

此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

• 当keep-alive 缓存组件才会有的生命周期的钩子函数
• activated deactivated
• errorCaptured 当子孙组件出错时,会调用这个钩子函数

組件閒數據交流
甲、組件閒的數據交流

單向數據交流 父到子 屬性綁定 props 原理 vm.$attrs 響應式

子到父    事件綁定  vm.$emit    原理 vm._event    無響應式
組件閒    事件總綫  vm.$on    原理 vm.$emit、vm.$on    無響應式

雙向數據交流 組件閒

⑴、父子組件:組件甲添加到組件乙中
1、組件甲添加到在組件乙的components中、或者直接把一個組件的標簽寫在實例中

     2、VUE實例是頂層父組件:root

3、組件標簽上定義的屬性,都會向子組件傳遞
4、組件的事件綁定與屬性綁定:
Ⅰ、兩者在不同生命周期中生成
事件綁定:Vue事件環境在第一階段的第一步完成初始化
屬性綁定:第二階段的第二步--組件編譯過程,創建虛擬節點樹
Ⅱ、①、事件綁定:Vue事件環境在第一階段的第一步完成初始化
i、子組件標簽上的事件,作爲該子組件模板的事件環境
ii、定義在子組件的事件庫--_events
iii、事件庫形式: { 事件1:[invoker函數],事件2:[invoker函數]。。。 }
值是以數組形式保存著一個元素--invoker函數
通過執行invoker()調用所有該事件所綁定的函數
iiii、在子組件模板上可觸發事件庫裏存儲的父組件方法
通過傳參可以把子組件的數據傳遞到父組件的方法中
iiiii、_events是實例是事件庫、$event是被觸發的事件、event是原生JS事件
②、屬性綁定:添加在子組件其標簽上的屬性
i、相當於直接添加在子組件模板的根標簽上的屬性
ii、同時也把數據傳遞到了子組件的模板上--跨組件的數據傳遞
5、直接進行組件閒數據訪問: 把組件當作對象進行訪問,直接訪問組件數據
JS沒有私有變量、vue配置
①、獲取子組件數據:
this.$children:獲取子組件數組集合
this.$refs:獲取有res屬性的子組件對象集合
②、獲取父組件數據:
this.$parent:獲取父組件
this.$root:獲取根組件
③、中央事件總綫
乙、父到子傳遞
原理:模板與子組件的$attrs屬性
1.添加在模板根標簽上的屬性,相當於添加在子組件跟標簽上



子組件

2、來自父組件的數據會被傳到子組件模板上
⑴.相當於添加到模板跟標簽上



⑵.子組件的非DOM屬性的自定義屬性, 會放入子組件的$attrs屬性中



①、在子組件模板上獲取$attrs屬性:this.$attrs
該屬性獲取的屬性保留著數據類型
1、在組件生命周期的編譯函數Complie中直接獲取父組件的相關數據,并賦值給.$attrs
2、然後在構成頁面時,將$attrs寫入頁面的標簽上
3、而這是在JS環境執行的
②、在子組件模板上調用$attrs屬性:如計算屬性

      computed: {
        getMsg(){
          return this.$attrs.attr1
        }

③、$attrs屬性與屬性綁定具有響應式
單向父到子:$attrs屬性的規範使用:數據攔截
攔截原先放在在$attrs的自定義屬性,改放入prop中
⑴、props數據攔截形式:
①、在子組件標簽上:以props的變量作爲綁定參數,獲取父組件的數據
②、在子組件模板上不需要進行相關操作,就可以直接使用數據
Ⅰ、傳遞靜態值,直接賦值
Ⅱ、傳遞動態值,使用v-bind
Ⅲ、可傳入一個對象的全部變量
可以使用v-bind而不帶參數,但prop中要有相應的變量名字
⑵、props書寫形式:props:{ a: String... } 、props: [ “a” ] (數組:以字符串形式)
prop使用對象字面量形式定義時,可以對值有更多配置选项
①、類型限制type: String、Number、Boolean、
Array、Object、Date、Function、Symbol、
自定义构造器--此使用instanceof來判斷
②、指定默認值default:若默認值是对象或数组,使用函數返回該值
1、vue使用undefined判斷是否使用默認值
2、傳入:空、未定義、未傳值時調用默認值
3、引用類型,對應的prop需要使用函數封裝返回
③、 是否必選required:布爾,定义该 prop 是否是必填
與默認值同時存在時,只在傳入值爲未定義時,會取到默認值
④、自定义验证規則validator:函數,会将该 prop 的值作为唯一的参数
Ⅰ、返回值:布爾 , 若沒有返回值,則為假
⑤、验证prop值是在一个组件实例创建之前
实例 property (如 data、computed 等) 在 default 或 validator 函数中是不可用的
⑶、本组件实例访问props的變量,如同data
①、props是只讀的,不能寫入,響應式
②、
⑷、其他:
①、特殊屬性如,class屬性,直接添加,$attrs只會去自定義屬性
②、使用驼峰命名法的 prop 名需要轉爲等价的短横线命名
丙、子到父傳遞
子組件傳參到父組件:監聽子組件事件--$emit.event
⑴、原理:事件綁定
在子組件上綁定事件,不會被添加到子組件模板的跟標簽上
而是定義在該組件的事件庫--_events
⑵、子組件可以調用父組件的方法,并且把子組件的數據傳給父組件的方法
如,在子組件模板上調用來自父組件的方法,并傳入來自子組件模板的數據
methods: {

    adclick() {
       this._events.click[0](this.msg)
    }  }

①、綁定在子組件上的方法,若有括號,將不能傳參,只能觸發父組件方法
⑶、Vue規範使用:vm.$emit--只會觸發對應VUE實例事件庫中定義的事件
vm.$emit( 事件名字 [, 剩餘參數] )
①、子組件標簽上添加自定義事件名
0.事件名字
Ⅰ、不要使用標簽定義的規範的已知事件名,如
Ⅱ、多單詞
Ⅲ、一般使用:間隔

1.若要傳參到父組件方法,則不能帶括號
2.自定義事件名字不要使用駝峰
②、在子組件的模板上創建事件,綁定調用函數

③、通過調用函數時,調用this.$emit方法觸發子組件事件庫的事件

  1. 可以通過執行函數觸發this.$emit
    methods: {
    afn(e) {

    this.$emit("aclick",e);

    }

  2. this.$emit的參數1事件被觸發,對於觸發了綁定子組件該事件的父組件的方法
  3. 在子組件模板上直接調用,不需要this: 如@input="$emit('abc',msg)"

④、非響應式:子組件模板上數據變化,不會自動觸發$emit
⑤、将原生事件绑定到子组件
在子組件上綁定時,使用事件修飾符:.native,取消這種情況
⑷、子組件上的v-model
丁、中央事件總綫:
event BUS
⑴、原理:VUE實例可以使用$on方法在事件倉庫中定義事件
在組件裏使用$emit來調用事件倉庫中的事件,并且傳參
⑵、使用步驟
①、創建事件vm1.$on(“事件名字”,回調函數)
一個在vm1實例中創建的事件
回調函數使用箭頭函數,以正確獲取指向該組件的this值
該事件觸發時,會在創建的環境中執行
②、觸發事件:vm1.$emit( “事件名字”[,剩餘參數] )方法
⑶、中央總綫:上面兩種方法的主體
①、使用一個空VUE實例
②、該組件的跟組件:this.$root、該組件的原型上
⑷、定義的位置及時機
①、接收數據的組件創建事件
②、傳送數據的組件觸發事件
③、$on必須優先$emit定義
④、若要自動調用
$on可定義在beforeMount
$emit可定義在mounted
戊、響應式的雙向數據傳遞:組件閒
⑴. 使用prop與$emit可以實現父子組件閒雙向傳遞
⑵. 結合計算屬性與v-model,props
Ⅰ、定義流程:
①、在子組件模板上,使用props定義來自父組件的數據
②、在子組件模板上,定義計算屬性:
取值操作返回props的變量
賦值操作中調用$emit
③、在子組件模板上,使用v-model綁定計算屬性:
實現取值賦值時觸發get、set,
進而有條件觸發父組件方法,實現更新數據
④、在子組件上使用prop屬性來獲取父組件數據
prop獲取的數據再傳入計算屬性
⑤、在子組件上創建事件并且綁定父組件方法
⑥、定義父組件方法來修改父組件數據
Ⅱ、執行流程:
首先子組件模板上的v-model綁定的計算屬性發生賦值操作時,觸發set
接著在set函數中觸發$emit
進而觸發綁定在子組件上的父組件方法,并且傳參以實現修改父組件的數據
再而在子組件上使用prop屬性所綁定的父組件的數據發生了變化
最後該數據變化觸發計算屬性,實現數據更新
⑶. v-bing.sync修飾符
是方法二的語法糖
Ⅰ、定義流程:
①、在子組件模板上,使用props定義來自父組件的數據
②、在子組件模板上,定義計算屬性:
取值操作返回props的變量
賦值操作中調用$emit(“update:屬性名”,nv)
此屬性名來自prop的變量
③、在子組件模板上,使用v-model綁定計算屬性:
實現取值賦值時觸發get、set,
進而有條件觸發父組件方法,實現更新數據
④、在子組件上使用prop屬性來獲取父組件數據,并且使用.sync修飾符
prop獲取的數據同樣再傳入計算屬性
Ⅱ、執行流程: 如方法二
Ⅲ.sync語法糖:
1.在父組件中為對應的屬性進行賦值操作

  1. 在子組件中的事件庫中,新增一個自定義事件

update:屬性名(來自prop的)
⑷. 使用引用類型
父組件數據封裝在對象裏
Ⅰ、定義流程:
①、在子組件模板上,使用props定義來自父組件的對象
②、在子組件模板上,使用v-model綁定對象的值:
③、在子組件上使用prop屬性來獲取父組件對象
④、父組件的數據包裝在對象裏
Ⅱ、執行流程: 直接修改
Ⅲ、非單向數據流,無法監視
⑸、獨立數據監控對象
Vue.observable方法
1、原理:該方法通過Object.defineProperty完成數據劫持和響應式,
生成并返回一個被劫持的響應式的對象
2、作用:為項目的數據統一管理維護提供接口--建立外層作用域的響應式數據倉庫
3、定義流程: 傳入對象,并且用一個變量接受返回值
let a = Vue.observable(對象)
4、使用流程: 在該作用域中,所有組件都可以直接使用: a.x、a.y
組件閒的切換:動態組件
通過Vue的元素,在該元素上加上用v-bind:is,并取值於組件名
⑴、使用keep-alive保持組件的狀態,取消切換組件時重新渲染
分發内容:插槽
向組件實例添加標簽:在組件中添加標簽:
⑴、默認值:在標簽内部放入其他標簽作爲默認值
⑵、具名插槽:具有先後順序
在插槽上給插槽添加name屬性,在元素上使用v-slot:賦值name屬性來指定插槽
簡寫:#
⑶、作用域插槽:子組件在父組件中使用子組件的數據
三步驟
①、在子組件模板的插槽標簽上,使用綁定屬性來獲取子組件的指定數據
②、在父組件的子組件的插槽内:把一個新變量賦值給帶值的v-solt
③、該新變量對象:值是子組件的數據,鍵是子組件的綁定屬性名

指令

  

21

22

21

21

    const Mvvm2 = function (options) {
      this.$el = document.querySelector(options.el);
      this._data = options.data;
      let subsDom = {};
      for (let key in this._data) {
        let selStr = `[mv-text="${key}"]`;
        subsDom[key] = this.$el.querySelectorAll(selStr);
      }
      for (let key in this._data) {
        for (let i = 0; i < subsDom[key].length; i++) {
          subsDom[key][i].textContent = this._data[key];
        }
        Object.defineProperty(this, key, {
          get() {
            return this._data[key];
          },
          set(nv) {
            this._data[key] = nv;
            for (let i = 0; i < subsDom[key].length; i++) {
              subsDom[key][i].textContent = this._data[key];
            }
          }
        });
      }
    };
    let vm2 = new Mvvm2({
      el: "#app2",
      data: {
        msg: "默認msg2",
        info: "默認info2"
      }
    });

改良

    // 1.、該實例對象存儲存儲所有綁定了指令的節點、 2.、原型方法提供節點樹和更新頁面
    var Watcher = function () { }  // 
    // 存儲所有綁定了該指令的節點, 按綁定的具體數據進行劃分  
    Watcher.prototype.setDom = function (rootDom, key) {
      let selStr = `[mv-text="${key}"]`;
      this[key] = rootDom.querySelectorAll(selStr);
    };
    // 把數據渲染到頁面
    Watcher.prototype.notify = function (key, nv) {
      let domList = this[key]; 
      for (let i = 0; i < domList.length; i++) {
        domList[i].textContent = nv;
      }
    };
    // 數據變化時,調用該方法
    Watcher.prototype.updata = function (data) {
      for (let key in data) {
        this.notify(key, data[key]);
      }
    };
    let Mvvm1 = function (options) {
      // 初始化:獲取節點和數據
      this.init(options.el, options.data);
    };

    Mvvm1.prototype.init = function (el, data) {
      this.$el = document.querySelector(el);
      this._data = data;
      this.deps(this._data); // 生成響應式
    };

乙、 异步更新队列
特點: 更新節點樹是异步执行的
侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更
如果同一个 watcher 被多次触发,只会被推入到队列中一次
在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作
限制: 一些需要在賦值操作后,立即利用新值進行操作的内容
如:在一個按鈕中調用該方法,其兩次打印結果都是舊值--異步的数据变更

    fn(e) {
      console.log(this.$refs.a.innerHTML);
      this.msg = "111";
      console.log(this.$refs.a.innerHTML);

该组件不会立即重新渲染。当刷新队列时,组件会在下一个事件循环“tick”中更新
解決:vm.$nextTick
为了在数据变化之后等待 Vue 完成更新節點樹,可以在数据变化之后立即使用vm.$nextTick
監控頁面的節點更新,在頁面節點更新后調用該函數,
此方法執行時間如同生命周期的 updated
如更新數據後,在此方法内創建輪播圖實例、echarts

你可能感兴趣的:(vue.js)