這文章在一年前已經寫出來了。今天仍是決定放出來供全部人學習。爲何我會寫vue組件通訊全揭祕,由於不管任何組件模式的框架。組件是核心,只有把組件寫組件之間能理順了。項目也就天然順了。內容很是多,並且一年後我以爲組件的通訊部分的沒有任何變化。沒有任何一點過期。但願對你們有幫助前端
Vue 是尤雨溪一我的的項目,是一套構建用戶界面的漸進式框架。與其餘重量級框架不一樣的是,Vue 採用自底向上增量開發的設計。Vue 的核心庫只關注視圖層,它不只易於上手,還便於與第三方庫或既有項目整合。另外一方面,當與單文件組件和 Vue 生態系統支持的庫結合使用時,Vue 也徹底可以爲複雜的單頁應用程序提供驅動。vue
Vue 經過 Api 來進行統一性的管理,可讓整個團隊的代碼都用統一的風格和方法標準去運做,並且對組件系統也有強大的支持,在封裝組件時經過 Props 和 Event 兩個標準性的原則去調用,可讓開發更加駕輕就熟。node
若是非要讓我說一個不學 Vue 的理由,多是它的寫法太方便了……你也可能以爲它借鑑的太多,沒有亮眼的地方,那我只能說一樣實現的東西就是那麼方便,簡潔的教程和 Api 文檔接入整個開發體系,至關符合中國市場的開發——業務變更大、版本要求上線快、需求改動頻繁、學習成本低……相比之下,一樣有着高效的功能,集成了組件系統和 Virtual DOM。webpack
掌握 Vue 主要就是正確理解教程和深刻掌握 Api 的用法,不但要會用,更重要的是學會對症下藥,在任何一種場景下使用最簡潔、最正確、最合理的代碼纔是關鍵。只有對 Api 和教程有了必定程度的項目實戰和組件庫實戰經驗才能把它用的遊刃有餘。程序員
在 Vue2.0 起步的時候我在掘金上進行了 Vue 課程的一系列套課的講解,從基礎到 Vuex,最後到組件庫的實戰都進行了簡單的講解。期間也通過大量的項目實戰和組件庫的實踐,經過一步步總結,對 Api 文檔的深刻理解和測試性模擬,總結了一些真實場景的正確用法和經常使用案例需求,讓你在開發中少走彎路,少刨坑。es6
在 Vue 開發中,咱們不但要準確的運用 Api,還要結合 es6 的新語法,用更深更強大的新特性來組織代碼,這一樣也是下一代 Javascript 的標準:web
在這次教程中將會展現 es6 大量的新語法進行,只有不斷的進行嘗試,纔能有不一樣的成效。vue-router
若是你想快速上手進行一個特別面向 C 端的 Mobile 產品開發,甚至是一箇中大型的項目開發,若是你能徹底閱讀完全部課程,而且跟着一步步實踐,那麼你一樣也能給本身的C端產品設計一套屬於本身的組件庫,畢竟通用型的組件庫仍然具有面對市場競爭需求的獨特性。vuex
本課程分享的內容是 Vue 的最新版本,能夠說這是一套獨一無二的教程,不但會結合官方教程和 Api,最主要的是告訴你們在什麼場景用什麼方式組織代碼,避開沒必要要的坑。vue-cli
數據驅動架構體系永遠離不開組件模式。在這裏我會給你們分享級別組件的劃份內部原則性,在本身打造組件庫的同時,也大量借鑑了各大廠商團隊的優秀組件寫法,進行比對優缺點,總結相應的理論。
$on
, $emit
, v-on 三者關係$attrs
,$listeners
深組件通訊當我在掘金寫下第一篇文章的時候,雖然只是很基礎的部分,但文章在兩天內得到了大量關注,這充分顯示出了中國市場的開發者們對 Vue 的渴望程度。與此同時,我也收集到了一些批評意見,對於讀者的反饋能及時作出響應才更能體現出一個課程的價值。
不是能寫出源碼的教程就是對你有幫助,也並非寫的很基礎就對你沒有幫助。不是每一個人都能當大上牛、進入大公司的研發團隊,大多數程序員都是面對業務層面的開發。所以如何在市場上有立錐之地,能快速接手項目,這纔是大部分人應該最須要發力的地方。
學習本課程的同窗須要對 Html 和 JavaScript 的基礎知識有必定了解,理解 es6 基礎新特性,瞭解 npm 和 node 的基本用法。
推薦:
別浪費時間看別的了,若是你能靜下來看完整本書,比任何 es6 其它書籍都好,爲何呢?平民化,就像 Vue 同樣,很容易讓人理解。
同時在學習本教程的時候,儘可能跑一遍 Vue 中文官網結合 Api 你能看懂的示例。
可能有些 Api 或者教程只有一個簡單的解釋,還特別官方話,不要緊,跟着我一步一步敲遍全部的 Demo。
在整理好心情開始旅程之時,咱們每每都會帶上許多必備工具,一樣 Vue 在面向開源之時,周邊的身態也向其靠攏。
vue-devtool
以往 Dom 操做的時候,咱們都是經過 dubger 斷點來進行錯點查找和基礎數據驅動,dubger 已經派不上什麼用場了,只有經過觀察數據的變化,才能準確的定位到錯識變化的數據和是否執行了須要的事件。
就用商店輸入vue自行安裝
vue-cli
Vue.js
提供一個官方命令行工具,可用於快速搭建大型單頁應用。該工具提供開箱即用的構建工具配置,帶來現代化的前端開發流程。只需幾分鐘便可建立並啓動一個帶熱重載、保存時靜態檢查以及可用於生產環境的構建配置的項目:
進入 Node.js 官網,下載 Node.js 安裝包;
爲了下載安裝包快速一點,走淘寶源進入 cli 終端;
運行npm install -g cnpm --registry=https://registry.npm.taobao.org 全局安裝 vue-cli $ npm install --global vue-cli 建立一個基於 webpack 模板的新項目 $ vue init webpack my-project 安裝依賴,走你 $ cd my-project $ npm install $ npm run dev 複製代碼
打開文件夾,本次教程的示例所有經過 Components 文件夾來定義單個組件,進行 SPA 的應用開發,用單 .vue 文件也更加直觀,一個文夾多是一個 Page,也多是一個 Component;
在開啓 Vue 的旅程之時,拿 todo-list 嘗試一下它的神奇魔法,經過 Vue 實例和模板進行數據與行爲的交互綁定;
實例的每一個選項如何與定義的模板值進行一一對應,經過數據驅動、事件綁定,來輕鬆高效的實現一個 todoList 應用。相比 Juqery 這種操做 Dom 的冷兵器時代,給開發者的感受是徹底變了一種模式,延續着 Html 寫法的友好性和適應度,一樣還提供了 JSX 語法,Vue 官網說是一個漸進式框加,寫法也一樣是漸近式,讓開發者以不畏懼的心態使用,並且 Vue 的數據驅動模式提供了大量的 Api,每一個 Api 不管是實例選項仍是實例屬性都負責着本身的職責,它們就像五金店的零件同樣,只有正確的使用每一個 Api 特性而且做用到恰當的地方,Vue 工程代碼組織結構和後續的維護纔會顯得易如反掌。在組件化工程化沒到來的時候,業務的實現複雜度並非最難的,反而使人頭疼的是對代碼後續的版本迭代、重構、複用等一系列問題,但願經過簡單的 todo-list 應用,能夠對前端開發革命有新的認識!
<template> <div> <input type="text" v-model.trim="msg" @keyup.enter="push"> <ul> <li v-for="(item,index) in list" :key="index" @click="deleteItem(index)"> {{index}} {{item.name}} </li> </ul> </div> </template> <script> export default { name: 'todo-list', data () { return { msg: "", list: [] } }, methods: { push () { this.list.push({name:this.msg}) this.msg = "" }, deleteItem (index) { this.list.splice(index,1) } } } </script> 複製代碼
本章經過這個示例 Demo 表現 Vue 數據驅動式框架運做是如何簡單到使人窒息。
一個 todo-list 應用集成了兩個事件,兩條 data 數據就完成了!
經過 Template 裏的 Html 模版能清楚的觀察到綁定信息,數據聯動和時時改動:
對於往時操做dom寫法和當前的數據驅動有什麼區別?
以上只是一個簡單 todo-list Demo 總結出來的例子,文中所提到的也只是部分功能優點,還有不少功能可讓開發路徑更加快速。重點在於數據驅動的模式,只要把組件與組件之間的通訊掌握了,也就至關於你就手握大半江山,由於一切的一切都是基於組件通訊模式和結構用法來的。
下篇課程導讀:
數據驅動一切都是一數據,只有靈活把控對數據的理解,才能自如的運用,在 Vue 裏靈活的 data,死板的 props,是存放數據的和傳遞數據的基點。
在前端來講數據驅動式框架,必然離不開事件驅動,事件驅動必定程度上彌補了數據驅動的不足,在dom操做
的時代一般都是這樣操做:
經過特定的選擇器查找到須要操做的節點 -> 給節點添加相應的事件監聽
響應用戶操做,效果是這樣:
用戶執行某事件(點擊,輸入,後退等等) -> 調用 JavaScript 來修改節點
這種模式對業務來講是沒有什麼問題,可是從開發成本和效率來講會比較力不從心,在業務系統愈來愈龐大的時候,就顯得複雜了。另外一方面,找節點和修改節點這件事,效率自己就很低,所以出現了數據驅動模式。
讀取模板,同時得到數據,並創建 VM( view-model ) 的抽象層 -> 在頁面進行填充
要注意的是,MVVM
對應了三個層,M - Model
,能夠簡單的理解爲數據層;V - View
,能夠理解爲視圖,或者網頁界面;VM - ViewModel
,一個抽象層,簡單來講能夠認爲是 V 層中抽象出的數據對象,而且能夠與V 和 M 雙向互動
(通常實現是基於雙向綁定,雙向綁定的處理方式在不一樣框架中不盡相同)。
用戶執行某個操做 -> 反饋到 VM 處理(能夠致使 Model 變更) -> VM 層改變,經過綁定關係直接更新頁面對應位置的數據
能夠簡單地理解:數據驅動不是操做節點的,而是經過虛擬的抽象數據層來直接更新頁面。主要就是由於這一點,數據驅動框架才得以有較快的運行速度(由於不須要去折騰節點),而且能夠應用到大型項目。
Vue 經過{{}}
綁定文本節點,data
裏動態數據與Props
靜態數據進行一個映射關係,當data
中的屬性或者props
中的屬性有變更,以上二者裏的每一個數據都是行爲操做須要的數據或者模板 view 須要渲染的數據,一旦其中一個屬性發生變化,則全部關聯的行爲操做和數據渲染的模板上的數據同一時間進行同步變化,這種基於數據驅動的模式更簡便於大型應用開發。只要合理的組織數據和代碼,就不會顯得後續皮軟。
二者選項裏均可以存放各類類型的數據,當行爲操做改變時,全部行爲操做所用到和模板所渲染的數據同時都會發生同步變化。
Data 被稱之爲動態數據的緣由,在各自實例中,在任何狀況下,咱們均可以隨意改變它的數據類型和數據結構,不會被任何環境所影響。
Props 被稱之爲靜態數據的緣由,在各自實例中,一旦在初始化被定義好類型時,基於 Vue 是單向數據流,在數據傳遞時始終不能改變它的數據類型。
更爲關鍵地是,對數據單向流的理解,props
的數據都是經過父組件或者更高層級的組件數據或者字面量的方式進行傳遞的,不容許直接操做改變各自實例中的props
數據,而是須要經過別的手段,改變傳遞源中的數據。
當一個實例建立的時候,Vue
會將其響應系統的數據放在data選項中
,當這些屬性的值發生改變時,視圖將會產生「響應」,即匹配更新爲新的值。初始定行的行爲代碼也都會隨着響應系統進行一個映射。
而 data 選項中的數據在實例中能夠任意改變,不受任何影響,前提必須數據要跟邏輯相輔相成。
<template> <div> <p v-if='boolean'>true</p> <p v-for='value in obj'>{{value}}</p> <p v-for='item in list'>{{item}}</p> <p>{{StringMsg}}</p> <p>{{NumberMsg}}</p> </div> </template> <script> export default { data () { return { obj : {a:'1',b:'2',c:'3'}, list:['a','b','c'], boolean : true, StringMsg : 'hello vue', NumberMsg : 2.4, } } } </script> 複製代碼
運行代碼時,在data選項
裏定義了五種數據類型,經過指令和{{}}
進行渲染,證明了data選項
裏能夠定義任何數據類型
。
<template> <div> <p>{{StringMsg}}</p> <p>{{NumberMsg}}</p> <button @click='changeData'>改變數據</button> </div> </template> <script> export default { data () { return { StringMsg : 'hello vue', NumberMsg : 2.4 } }, methods: { changeData () { this.StringMsg = 2.4; this. NumberMsg = 'hello vue' } } } </script> 複製代碼
每一個.vue 的文件則就是一個實例,在 data 中定義了兩種數據:
同時還定義了一個 changeData 事件。
在運行代碼時候,data選項
已經進入了Vue的響應系統
裏,model層
(數據層)與view層
(視圖層)進行了對應的映射,任何數據類型均可以定義。
當用戶發生點擊操做的時候,同時能夠把 StringMsg, NumberMsg 的數據對調,充分說明了,不管值和類形均可以進行隨意轉換
。
<template> <div> <p>{{StringMsg}}</p> <p>{{NumberMsg}}</p> <button @click='changeData'>改變數據</button> <button @click='findData'>查看數據</button> </div> </template> <script> export default { data () { return { StringMsg : 'hello vue', NumberMsg : 2.4 } }, methods: { changeData () { this.StringMsg = 2.4; this.NumberMsg = 'hello vue' }, findData () { console.log(`StringMsg: ${this.StringMsg}`) console.log(`NumberMsg: ${this.NumberMsg}`) } } } </script> 複製代碼
改變數據之後,經過點擊 findData 事件來進行驗證,雖然在初始化定義好了行爲數據的檢測代碼,可是當數據在執行 findData 以前先執行 changeData,一旦改變 data 選項裏的數據時,findData 裏對應的數據同時也會進行相應的映射。
this.StringMsg //=> 2.4
this.NumberMsg //=>'hello vue'
總結:
使用props
傳遞數據做用域是孤立的,它是父組件經過模板傳遞而來,想接收到父組件傳來的數據,須要經過props選項
來進行接收。
子組件須要顯示的聲明接收父組件傳遞來的數據的數量
,類型
,初始值
。
簡單的接收能夠經過數組的形式來進行接收。
<template> <div> <demo :msg='msgData' :math = 'mathData' ></demo> </div> </template> <script> import Demo from './Demo.vue' export default { data () { return { msgData:'從父組件接收來的數據', mathData : 2 } }, components : { Demo } } </script> 複製代碼
<template> <div> <p>{{msg}}</p> <p>{{math}}</p> </div> </template> <script> export default { name: 'demo', props: [ 'msg' , 'math'], } </script> 複製代碼
在子組件中須要經過顯示定義好須要從父組件中接收那些數據。
一樣的在父組件中在子組件模板中過v-bind
來傳遞子組件中須要顯示接收的數據。
語法: :== v-bind(是封裝的語法糖) :msg = msgData
props 能夠顯示定義一個或一個以上的數據,對於接收的數據,能夠是各類數據類型,一樣也能夠傳遞一個函數。
<template> <div> <demo :fn = 'myFunction' ></demo> </div> </template> <script> import Demo from './Demo.vue' export default { components : { Demo }, methods: { myFunction () { console.log('vue') } } } </script> 複製代碼
<template> <div> <button @click='fn'>按鈕</button> </div> </template> <script> export default { name: 'demo', props: [ 'fn' ], } </script> 複製代碼
一樣,在父組件中也能夠向子組件中傳遞一個function
,在子組件一樣也能夠執行父組件傳遞過來的 myFunction 這個函數。
對於字面量語法和動態語法,初學者在父組件模板中向子組件中傳遞數據時加和不加 v-bind 有什麼區別,同時會引發什麼錯語等問題會感受迷惑。
v-bind:msg = 'msg'
經過 v-bind 進行傳遞數據而且傳遞的數據並非一個字面量,雙引號裏的解析的是一個表達式
,一樣也能夠是實例上定義的數據和方法(其實就是引用一個變量)"。
msg='11111'
沒有 v-bind 的模式下只能傳遞一個字面量,這個字面量只限於 String 類量,字符串類型。
注意:
雖然經過字面量模式下,傳任何類型都會被轉成字符串類型,可是在子件接收的時候能夠經過 typeof 去進行類型檢測。
想經過字面量進行數據傳遞時,若是想傳遞非String類型
,必須props
名前要加上v-bind
,內部經過實例尋找,若是實例方沒有此屬性和方法,則默認爲對應的數據類型
。
:msg='11111'
//number :msg='true'
//bootlean :msg='()=>{console.log(1)}
//function :msg='{a:1}
//object
HTML 特性是不區分大小寫的,因此當使用的不是字符串模板,camelCased (駝峯式) 命名的 prop 須要轉換爲相對應的 kebab-case (短橫線隔開式) 命名。
因爲文檔上仍然有這句話,通過測試後,不管是否是字符串模板,camelCased (駝峯式) 和 kebab-case (短橫線隔開式) 二者均可以。
爲了直觀性,規範性仍是推薦 kebab-case (短橫線隔開式)。
props 原子化可讓總體代碼邏輯和向外暴露須要傳遞數據的接口很是清晰,可是一樣能夠把子組件須要接收的 props 在父組件中以一個對象進行傳遞。
當傳遞的數量一旦多到已經讓原子化再也不結構清晰的時候,經過一個對象傳遞顯得更爲簡潔明瞭。
<template> <div> <demo v-bind= 'msg' ></demo> </div> </template> <script> import Demo from './Demo.vue' export default { components : { Demo }, data () { return { msg : {a:1,b:2} } } } </script> 複製代碼
<template> <div> <button>按鈕</button> </div> </template> <script> export default { name: 'demo', props: ['a','b'], created () { console.log(this.a) console.log(this.b) }, } </script> 複製代碼
<demo v-bind= 'msg' ></demo>
內部發生了什麼?在子組件模板內部對其進行了一個封裝
,把其展開則跟 props 原子化原理是一個原理
<demo :a='a' :b='b' ></demo>
一般狀況下建議使用第二種,props 原子化。
在 data 選項中,當前實例(當前組件中改動)能夠任意改變data選項裏的數據
,Vue
傳遞數據時是基於數據單向流動
,子組件不能改變當前實例中的 props 任何屬性,須要通知父組件改變相應的值,從新改變。
<template> <div> <button @click='changeProps'>按鈕</button> </div> </template> <script> export default { name: 'demo', props: ['msg'], methods: { changeProps () { this.msg = 'new msg' } } } </script> 複製代碼
直接改變 props 時會發生一個警告報錯
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "msg"
防止數據的不可控性
,不能顯示的直接改變,父組件的傳遞來的數據和子組接 props 接收的數據也是同步響應的,一旦父組件向下傳遞的數據改變時,prop 接收的數據值也會一樣發生變化。
單向數據流的緣由也是如此,就像河流同樣,水只會從高向低流,想讓水的質量改變,只有從源頭改變。
<template> <div> <demo :msg = 'msg' ></demo> <button @click='msg = "new vue"'>按鈕</button> </div> </template> <script> import Demo from './Demo.vue' export default { components : { Demo }, data () { return { msg : 'vue' } } } </script> 複製代碼
在父組件中初始化傳遞事後,想要改變子組件的數據,能夠經過再次改變向子組件傳遞的 msg 數據,子組件渲染的視圖一樣會跟着同步改動。
雖然 props 是不可改動的,上面的 case 是父組件進行改變自身實例的的數據,這個實現很簡單,有時通過一次數據傳遞,不需用父組件再次傳遞,由於一些需求須要改動 props 數據,能夠用過渡的方法,讓其轉換爲一個可變的數據。
props: ['msg'], data: function () { return { myMsg: this.msg } } 複製代碼
在 data 選項裏經過 myMsg 接收 props msg 數據,至關於對 myMsg = msg 進行一個賦值操做,不只拿到了 myMsg 的數據,並且也能夠改變 myMsg 數據。
this.myMsg = 'new Vue' myMsg 會發生相應的改變。
依然是經過 props 一次性接收,想對接收的 prop 進行一些過濾操做再次進行視圖渲染,能夠在一些計算屬性中進行操做,能夠 computed 監聽 props 裏的數據變化,通過過濾操做返回一個須要的值。
props:['msg'] computed : { computedMsg () { return this.msg + 1 } } 複製代碼
注意: 在 JavaScript 中對象和數組是引用類型,指向同一個內存空間,若是 prop 是一個對象或數組,在子組件內部改變它會影響父組件的狀態。不要對父組件傳遞來的引用類型數據進行過濾。
下篇導讀
本章對 props 和 data 的用法理解已經進行了全面的講解,經過再次改變傳遞數據時是在父組件的實例裏進行實施的。每每特定的需求和一些組件封裝觸發傳遞的命令並不能直接在父組件執行,須要子組件通知上層組件。
再近一步說,子組件改變不了父組件傳遞的數據,可是子組件能夠用通訊的方式,通知子組件改動,所以 $on
,$emit
,v-on 深刻理解這三者關係尤其重要!
$emit
,$on
的關係$on(eventName)
監聽事件$emit(eventName)
觸發事件若是把Vue
當作一個家庭(至關於一個單獨的components
),女主人一直在家裏指派($emit)
男人作事,而男人則一直監聽($on)
着女士的指派($emit)裏eventName
所觸發的事件消息,一旦 $emit
事件一觸發,$on
則監聽到 $emit
所派發的事件,派發出的命令和執行派執命令所要作的事都是一一對應的。
Api 中的解釋:
vm.$emit( event, […args] )
參數:
{string} event
[...args]
觸發當前實例上的事件。附加參數都會傳給監聽器回調。
vm.$on( event, callback )
參數:
{string | Array<string>}
event (數組只在 2.2.0+
中支持) {Function} callback
用法:
監聽當前實例上的自定義事件。事件能夠由 vm.$emit
觸發。回調函數會接收全部傳入事件觸發函數的額外參數。
<template> <div> <p @click='emit'>{{msg}}</p> </div> </template> <script> export default { name: 'demo', data () { return { msg : '點擊後女人派發事件' } }, created () { this.$on('wash_Goods',(arg)=> { console.log(arg) }) }, methods : { emit () { this.$emit('wash_Goods',['fish',true,{name:'vue',verison:'2.4'}]) } } } </script> 複製代碼
以上案例說了什麼呢?在文章開始的時候說了 $emit
的(eventName)是與 $on(eventName)
是一一對應的,再結合以上兩人在組成家庭的以前,女人會給男人列一個手冊,告訴男人我會派發 $(emit)
那些事情,男人則會在家庭組成以前 $on(eventName)
後應該如何作那些事情。
經過以上說明我來進一步解釋一下官方 Api 的意思。
vm.$emit( event, […args] )
參數:
{string} event
[...args]
this.$emit('wash_Goods','fish') 複製代碼
vm.$on( event, callback )
參數:
{string | Array<string>}
event (數組只在 2.2.0+
中支持)
第一個參數是相對於 $emit (eventName)
一一對應的 $on (eventName)
,二者是並存的、必須是 String 類型的。
(數組只在
2.2.0+中支持)
或者是Array<String>
數組中必須包含的是 String 項,後面再具體說。
故事中就是男人在組件一個家庭 (components) 的時候所監聽的事件名。
{Function} callback
第二個參數則是一個 function,一樣也被叫做以前回調函數,裏面能夠接收到由 $emit 觸發時所傳入的參數(若是是單個參數)。
故事中是男人在接收到女人派發的事情該去作那些事情。
{string | Array} event (數組只在
2.2.0+
中支持)
在2.2中新增這個 Api 牽扯了另外一種方式,也存在這其它的獨特用法。
繼續延續故事,當女人派發的事情多了,我相信做爲男人也會以爲很煩,一旦聽到事件的時候確定會很煩躁,總會抱怨兩句。
若是女人在組成家庭以前,告訴男人將要監聽那些事情,若是作一件事就抱怨一次,啓不是畫蛇添足,因此咱們能夠經過Array<string> event
把事件名寫成一個數組,在數組裏寫入你所想監聽的那些事件,使用共享原則去執行某些派發事件。
<template> <div> <p @click='emit'>{{msg}}</p> <p @click='emitOther'>{{msg2}}</p> </div> </template> <script> export default { name: 'demo', data () { return { msg : '點擊後女人派發事件', msg2 : '點擊後女人派發事件2', } }, created () { this.$on(['wash_Goods','drive_Car'],(arg)=> { console.log('事真多') }) this.$on('wash_Goods',(arg)=> { console.log(arg) }) this.$on('drive_Car',(...arg)=> { console.log(BMW,Ferrari) }) }, methods : { emit () { this.$emit('wash_Goods','fish') }, emitOther () { this.$emit('drive_Car',['BMW','Ferrari']) } } } </script> 複製代碼
以上案例說明了當女人不管是派發drive_Car
或者是wash_Goods
事件,都會打印出事真多
,再執行一一對應監聽的事件。
一般狀況下,以上用法是毫無心思的。在日常業務中,這種用法也用不到,一般在寫組件的時候,讓$emit在父級做用域中
進行一個觸發,通知子組件的進行執行事情。接下來,能夠看一個經過在父級組件中,拿到子組件的實例進行派發事件,然而在子組件中事先進行好派好事件監聽的準備,接收到一一對應的事件進行一個回調,一樣也能夠稱之爲封裝組件向父組件暴露的接口。
<template> <div> <slot name="list"></slot> <div class="list-donetip" v-show="!isLoading && isDone"> <slot>沒有更多數據了</slot> </div> <div class="list-loading" v-show="isLoading"> <slot>加載中</slot> </div> </div> </template> <script type="text/babel"> export default { data() { return { isLoading: false, isDone: false, } }, props: { onInfinite: { type: Function, required: true }, distance : { type : Number, default:100 } }, methods: { init() { this.$on('loadedDone', () => { this.isLoading = false; this.isDone = true; }); this.$on('finishLoad', () => { this.isLoading = false; }); }, scrollHandler() { if (this.isLoading || this.isDone) return; let baseHeight = this.scrollview == window ? document.body.offsetHeight : this.scrollview.offsetHeight let moreHeight = this.scrollview == window ? document.body.scrollHeight : this.scrollview.scrollHeight; let scrollTop = this.scrollview == window ? document.body.scrollTop : this.scrollview.scrollTop if (baseHeight + scrollTop + this.distance > moreHeight) { this.isLoading = true; this.onInfinite() } } }, mounted() { this.scrollview = window this.scrollview.addEventListener('scroll', this.scrollHandler, false); this.$nextTick(this.init); }, } </script> 複製代碼
對下拉組件加載加更的組件進行了一個簡單的封裝:
data 參數解釋:
false 表明正在執行下拉加載獲取更多數據的標識
,true表明數據加載完畢
false 表明數據沒有全完加載完畢
,true 表明數據已經所有加載完畢
props 參數解釋:
父組件向子組件傳入當滾動到底部時執行加載數據的函數
距離滾動到底部的設定值
mounted
的時候,對window
對像進行了一個滾動監聽,監聽的函數爲scrollHandler
isLoading,isDone
任何一個爲true時則退出
isloading
爲true
時防止屢次一樣加載,必須等待加載完畢isDone
爲true
時說明全部數據已經加載完成,沒有必要再執行scrollHandler
loadedDone
一旦組件實例$emit('loadedDone')事件時,執行回調,放開加載權限finishLoad
一旦組件實例$emit('finishLoad')事件時,執行回調,放開加載權限if (this.isLoading || this.isDone) return;
一旦一者爲true,則退出,緣由在mounted已經敘述過了if (baseHeight + scrollTop + this.distance > moreHeight)
當在window對象上監聽scroll事件時,當滾動到底部的時候執行
this.isLoading = true;
防止重複監聽this.onInfinite()
執行加載數據函數父組件中調用 infinite-scroll 組件
<template> <div> <infinite-scroll :on-infinite='loadData' ref='infinite'> <ul slot='list'> <li v-for='n in Number'></li> </ul> </infinite-scroll> </div> </template> <script type="text/babel"> import 'InfiniteScroll' from '.......' //引入infinitescroll.vue文件 export default { data () { return { Number : 10 } }, methods : { loadData () { setTimeout(()=>{ this.Number = 20 this.$refs.infinite.$emit('loadDone') },1000) } } } </script> 複製代碼
在父組件中引入 infinite-scroll 組件
當滑到底部的時候,infinite-scroll 組件組件內部會執行傳入的:on-infinite='loadData'
函數 同時在內部也會把 Loading 設置爲 true,防止重複執行。
在這裏用this.$refs.infinite
拿到infinite-scroll
組件的實例,同時觸發事件以前在組件中 $on
已經監聽着的事件,在一秒後進行改變數據,同時發出loadDone
事情,告訴組件內部去執行loadDone
的監聽回調,數據已經所有加載完畢,設置this.isDone = true;
一旦isDone
或者isLoading
一者爲true
,則一直保持return退出狀態
。
$emit
和 $on
必須都在實例上進行觸發和監聽。
第一階段 $emit
和 $on
的二者之間的關係講完了,接下來該說說 v-on 與 $emit
的關係。
另外,父組件能夠在使用子組件的引入模板直接用 v-on 來監聽子組件觸發的事件。
v-on 用接着故事直觀的說法就是,在家裏裝了一個電話,父母隨一直聽着電話,一樣也有一本小冊子,在組成家庭以前,也知識要去監聽那些事。
不能用 $on 偵聽子組件釋放的事件,而必須在模板裏直接用 v-on 綁定
。
上面 Warn 的意思是$emit和$on只能做用在一一對應的同一個組件實例
,而v-on只能做用在父組件引入子組件後的模板上
。
就像下面這樣: <children v-on:eventName="callback"></children>
就拿官方的這個例子說吧,其實仍是很直觀的:
<div id="counter-event-example"> <p>{{ total }}</p> <button-counter v-on:increment="incrementTotal"></button-counter> <button-counter v-on:increment="incrementTotal"></button-counter> </div> Vue.component('button-counter', { template: '<button v-on:click="incrementCounter">{{ counter }}</button>', data: function () { return { counter: 0 } }, methods: { incrementCounter: function () { this.counter += 1 this.$emit('increment') } }, }) new Vue({ el: '#counter-event-example', data: { total: 0 }, methods: { incrementTotal: function () { this.total += 1 } } }) 複製代碼
這樣的好處在哪裏?雖然 Vue
是進行數據單向流的,可是子組件不能直接改變父組件的數據,(也不是徹底不能,但不推薦用),標準通用明瞭的用法,則是經過父組件在子組件模板上進行一個 v-on
的綁定監聽事件,同時再寫入監聽後所要執行的回調。
在counter-event-example
父組件裏,聲明瞭兩個button-count
的實列,經過 data
用閉包的形式,讓二者的數據都是單獨享用的,並且v-on
所監聽的 eventName
都是當前本身實列中的 $emit
觸發的事件,可是回調都是公用的一個 incrementTotal
函數,由於個實例所觸發後都是執行一種操做!
若是你只是想進行簡單的進行父子組件基礎單個數據進行雙向通訊的話,在模板上經過 v-on
和所在監聽的模板實例上進行 $emit
觸發事件的話,未免有點多餘。一般來講經過 v-on
來進行監聽子組件的觸發事件的話,咱們會進行一些多步操做。
<template> <div> <p @click='emit'>{{msg}}</p> </div> </template> <script> export default { name: 'demo', data () { return { msg : '點擊後改變數據', } }, methods : { emit () { this.$emit('fromDemo') }, } } </script> 複製代碼
<template> <div class="hello"> <p>hello {{msg}}</p> <demo v-on:fromDemo='Fdemo'></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', data () { return { msg: '數據將在一秒後改變' } }, methods: { waitTime() { return new Promise(resolve=>{ setTimeout(()=> { this.msg = '數據一秒後改變了' resolve(1) },1000) }) }, async Fdemo () { let a = await this.waitTime(); console.log(a) } }, components : { Demo } } </script> 複製代碼
從上面 demo 能夠看出當子組件觸發了 fromDemo
事件,同時父組件也進行着監聽。
當父組件接收到子組件的事件觸發的時候,執行了async
的異步事件,經過一秒鐘的等秒改變 msg
,再打印出回調後經過 promise
返回的值。
接下來想通的此例子告訴你們,這種方法一般是經過監聽子組件的事件,讓父組件去執行一些多步操做,若是咱們只是簡單的示意父組件改變傳遞過來的值用此方法就顯的多餘了。
咱們進行一些的改動:
children
<template> <div> <p @click='emit'>{{msg}}</p> </div> </template> <script> export default { name: 'demo', props: [ 'msg' ], methods : { emit () { this.$emit('fromDemo','數據改變了') }, } } </script> 複製代碼
parent
<template> <div class="hello"> <demo v-on:fromDemo='Fdemo' :msg='msg'></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', data () { return { msg: '數據沒有改變' } }, methods: { Fdemo (arg) { this.msg = arg } }, components : { Demo } } </script> 複製代碼
上面 demo
中子組件從父組件接收一個 msg
數據,可是想點擊按鈕的時候,改變父組件的 msg
,進行父組件的數據改動,同時再次改變子組件的 msg
,可是最簡便的方法則是直接改變 prop
裏 msg
的數據。可是數據驅動都是單向數據流,爲了避免形成數據傳遞的混亂,咱們只能依靠一些其它手段去完成,一個小小的傳遞數據就顯得很複雜的了,因此後續咱們會講講如何去用更簡便的 Api
作對應的事。
下篇課程導讀:
在2.0初期 .sync
被砍了,v-model
承擔起了雙向綁定的職責,畢竟 v-model
不是爲組件與組件之間數據雙向綁定而設計的,用起來總有蹩腳的時候。2.3
版本的迴歸,啓用了顯示通知的形式讓雙向綁定又活了,.sync
或者 v-model
比$emit
與 v-on
只是進行簡單的父子組件數據交互更加便捷。
上一章咱們已經對$emit和v-on
如何進行數據和行爲的交互作了講解,但若是隻是簡單用來數據傳遞改變的話.sync和v-model
再適合不過了。若是用過1.0的 Vue 的開發者,我相信 .sync 會讓你用起來很是便捷,經過雙向綁定很簡單就能實雙,父子組件的雙向綁定,2.0爲了保持單向數據流的良好性,去除了 .sync 的功能。
官方解釋:
1.0 Props 如今只能單向傳遞。爲了對父組件產生反向影響,子組件須要顯式地傳遞一個事件而不是依賴於隱式地雙向綁定。
推薦使用
經過大量觀察,在初期2.0版本中,由於 .sync 並無迴歸,只是在2.3進行迴歸,在組件庫中進行數據雙向綁定,幾乎都是經過 v-model 來進行的。可是不管從語意上仍是感觀上,給代碼維護的感就是不直觀,v-model 在開發一般都是結合 Input 輸入框來結合進行一個數據綁定,進行父子組件雙向綁定,可是相比自定義 v-on 組件事件,不管從代碼量,仍是用法上更加簡潔。
在 Vue 中,有許多方法和 Angular 類似,這主要是由於 Angular 是 Vue 早期開發的靈感來源。然而 Angular 中存在許多問題,在 Vue 中已經獲得解決。
官方解釋
自定義事件能夠用來建立自定義的表單輸入組件,使用 v-model 來進行數據雙向綁定。
<input v-model="something"> 複製代碼
這不過是如下示例的語法糖:
<input v-bind:value="something" v-on:input="something = $event.target.value"> 複製代碼
v-model 其實也是一個語法糖,想要理解這些代碼,你要先知道Input元素
上自己有個oninput事件
,這是HTML5新增長
的,相似 onchange,每當輸入框內容發生變化的時候,就會觸發Input事件
,而後把 Input 輸入框中 value 值再次傳遞給 something。
此時 value 運用在一個 Input 元素上,用:v-bind:value='something',意義上面只是把 Input 輸入框中的 value 值與 something 做爲一一對應的雙向綁定,這就像一個循環操做,當再次觸發 Input 事件時,input($event.target)對象中的value值會再次改變something
。
這裏咱們對 v-model 綁定在 Input 元素上進行語法糖上的解析。
既然在元素上能進行雙向綁定,那在組件中進行雙向綁定又如何實現,原理其實都是同樣的,只是應用在自定義的組件上時,拿的並非$event.target.value
,由於我此時不做用在 Input 輸入框上。
<custom-input v-bind:value="something" v-on:input="something = arguments[0]"> </custom-input> 複製代碼
經過以上簡寫,經過自定事件讓 v-model 進行一個父子組件雙向綁定的話。
接收的只能是 value 嗎?必須是,由於 v-model 是基於 Input 輸入框定製的,其中value 值是爲 Input 內部定製的
v-on:input="something = arguments[0]" 複製代碼
此時做用在組件上時,v-on 監聽的語法糖也會有所改動,監聽的並非$event.target.value
,而是回調函數中的第一個參數
。
<template> <div class="hello"> <button @click="show=true">打開model</button> <demo v-model="show"></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, data () { return { show: false } } } </script> 複製代碼
<template> <div v-show="value"> <div> <p>這是一個Model框</p> <button @click="close">關閉model</button> </div> </div> </template> <script> export default { props: ['value'], methods: { close () { this.$emit('input',false) } } } </script> 複製代碼
這是一個模態框的基本雛形,能夠在父組件經過 v-model 來進行 model 框和父組件之間的顯示交互。
經過子組件看出經過props接收了value值
,當點擊關閉的時候仍是經過$emit事件觸發input事件
,而後經過傳入 false 參數。
父組件隱式 v-on:input="something = arguments[0]" 進行了監聽,一但 Input 事件觸發,父組件就會執行監聽回調,從而作到了雙向綁定。
checkbox 和 radio 原理
<input type="checkbox" :checked="status" @change="status = $event.target.checked" /> <input type="radio" :checked="status" @change="status = $event.target.checked" /> 複製代碼
經過綁定 checked 屬性,一樣的監聽的是 change 事件,不管是 checkbox 仍是 radio 在操做的時候都會隱式自動觸發一個 change 事件,跟 Input 經過 value 值,Input 觸發事件原理綁定是同樣的。
定製組件,咱們就能夠重寫v-model裏的Props 和 event
,默認狀況下,一個組件的 v-model 會使用 value 屬性
和 input 事件
,每每有些時候,value 值被佔用了,或者表單的和自定議v-model的$emit('input')事件發生衝突
,爲了不這種衝突,能夠定製組件 v-model,衝突示例。
<template> <div v-show="value"> <div> <p>這是一個Model框</p> <input type="text" v-model="value"> {{value}} <button @click="close">關閉model</button> </div> </div> </template> <script> export default { props: ['value'], methods: { close () { this.$emit('input',false) } } } </script> 複製代碼
<template> <div class="hello"> <button @click="show=true">打開model</button> <demo v-model="show"></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, data () { return { show: false } } } </script> 複製代碼
上面例子能夠發現,在子組件中input中v-model
和model顯示的操做數據共同佔用的 props 中的(value)
,一樣二者也共同佔用了 emit('input') 觸發事件,Input 輸入框的事件是自動出發,而 model 顯示消失是手動觸發。
初始化的時候,Input 輸入框的值的會被 value 傳入的 false 值給自動加上,當改變 Input 輸入框的時候,由於衝突而致使報錯。
定製 v-model, 經過 model 選項改變 props 和 event 的值,從而解除二者的衝突。
<template> <div v-show="show"> <div> <p>這是一個Model框</p> <input type="text" v-model="value"> {{value}} <button @click="closeModel">關閉model</button> </div> </div> </template> <script> export default { model: { prop: 'show', event: 'close' }, props: ['show'], data () { return { value: 10 } }, methods: { closeModel () { this.$emit('close',false) } } } </script> 複製代碼
<template> <div class="hello"> <button @click="show=true">打開model</button> <demo v-model="show" ></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, data () { return { show: false } } } </script> 複製代碼
經過 model 選項的改變,把 props 從本來的value換成了show
,input觸發的事件換成了close
,從而二者都不相互依賴,解決了衝突的問題。
有些時候經過父組件中的子組件模板中想傳遞 value 值,也會致使一樣的衝突。
在不用定製組件的狀況下,如下的寫法,也會一樣致使衝突,致使同用一個 value。
<demo v-model="show" value="some value"></demo> 複製代碼
props:['value'] 複製代碼
在一些狀況下,咱們可能會須要對一個 prop 進行『雙向綁定』。事實上,這正是 Vue 1.x 中的 .sync 修飾符所提供的功能。當一個子組件改變了一個 prop 的值時,這個變化也會同步到父組件中所綁定的值。這很方便,但也會致使問題,由於它破壞了『單向數據流』的假設。因爲子組件改變 prop 的代碼和普通的狀態改動代碼毫無區別,當光看子組件的代碼時,你徹底不知道它什麼時候悄悄地改變了父組件的狀態。這在 debug 複雜結構的應用時會帶來很高的維護成本。
在2.0發佈一段以後,不管在業務組件仍是在功能組件庫上面的,大量的子組件改變父子組件的數據和組件庫中可能達到大功率的複用,可是在2.3中迴歸,從新引入了 .sync 修飾符,此次它只是做爲一個編譯時的語法糖存在。它會被擴展爲一個自動更新父組件屬性的 v-on 偵聽器。
以前的例子中,v-model 畢竟不是給組件與組件之間通訊而設計的雙向綁定,不管從語意上和代碼寫法上都沒有 .sync 直觀和方便。
不管從 v-model 仍是 .sync 修飾符來看,都離不開 $emit v-on 語法糖的封裝,主要目的仍是爲了保證數據的正確單向流動與顯示流動。
<demo :foo.sync="something"></demo> 複製代碼
語法糖的擴展:
<demo :foo="something" @update:foo="val => something = val"></demo> 複製代碼
當子組件須要更新 foo 的值時,它須要顯式地觸發一個更新事件:
this.$emit('update:foo', newValue) 複製代碼
同時父組件@update:foo
也是依賴於子組件的顯示觸發,這樣就能夠很輕鬆的捕捉到了數據的正確的流動
。
第一個參數則是 update 是顯示更新的事件,跟在後面的:foo
則是須要改變對應的props值
。
第二個參數傳入的是你但願父組件foo數據裏將要變化的值
,以用於父組件接收update時更新數據
。
<template> <div v-show="show"> <p>這是一個Model框</p> <button @click="closeModel">關閉model</button> </div> </template> <script> export default { props: ['show'], methods: { closeModel () { this.$emit('update:show',false) } } } </script> 複製代碼
<template> <div class="hello"> <button @click="show=true">打開model</button> <demo :show.sync="show" ></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, data () { return { show: false } } } </script> 複製代碼
上面的 case 一樣也解決了 model 顯示交互操做,從代碼的語意上看上去讓開發者一目瞭然,一樣也作了 v-model 作不了的事,基於 props 的原子化,對傳入的 props 進行多個數據雙向綁定
.sync 也能輕鬆作到。
<template> <div class="hello"> <button @click="show=true">打開model</button> <demo :show.sync="show" :msg.sync="msg"></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, data () { return { show: false, msg: '這是一個model' } } } </script> 複製代碼
<template> <div v-show="show"> <p>{{msg}}</p> <button @click="closeModel">關閉model</button> <button @click="$emit('update:msg','改變了model文案')">改變文案</button> </div> </template> <script> export default { props: ['show', 'msg'], methods: { closeModel () { this.$emit('update:show',false) } } } </script> 複製代碼
父組件向子組件 props 裏傳遞了 msg 和 show 兩個值,都用了.sync 修飾符,進行雙向綁定。
子組件改變父組件的數據時,update 冒號後面的參數和父組件傳遞進來的值是同步的,想改變那個,則冒號後面的值對應的那個,二者是一一對應的,同時也是必填的。
一樣還能夠在組件 template 裏點擊執行 click 後不但能夠支持回調函數,還能夠寫入表達式,只是一種直觀的表現仍是推薦這種寫法的。
.sync 修飾符給咱們開發中帶來了很大的方便,同時在2.0的初期的組件庫中大量的 v-model 給開發者用起來仍是很彆扭,在.sync 迴歸後同時也會慢慢向.sync 進行一個版本的遷移。
下篇課程導讀:
不基於大量行爲操做,只是進行一個或多個數據雙向組件的時候,能夠輕鬆用 .sync 與 v-model 去化解,每每組件通訊並非你想像的那麼輕鬆簡單,在項目複雜的時候,組件如何合理的拆分,會讓業務代碼的清晰度
,複用率
,後續維護都會下降成本
,有利必有困難,一樣會形成組件與組件的深層次傳遞,那咱們如何進行通訊呢?第一個想到的辦法必然是 Vuex。Vuex 理解其實本質上並非處理跨度深層次組件而使用的,每每這樣會致使你們會濫用 vuex,而 $attrs
$listeners
這對兄弟能夠很好的幫助你進行深組件的通訊。
在2.4版本中,有關$attrs
和$listeners
這兩個實例屬性用法仍是比模糊,深層次挖掘將會很是有用,由於在項目中深層次組件交互的話可能就須要 Vuex 助力了,可是若是隻是一個簡單的深層次數據傳遞,或者進行某種交互時須要向上通知頂層或父層組件數據改變時,殺雞用牛 VUX 可能未免有點多餘!
什麼狀況纔會顯得多餘,若是咱們純經過 props 一層一層向下傳遞,再經過 watch 或者 data 進行過渡,若是隻是單向數據深層能傳遞,進行監聽改變深傳遞的數據,不進行跨路由之間頁面的共享的話,用這兩個屬性很是便捷。
有些開發者,特別對 Vuex 沒有深刻理解和實戰經驗的時候,同時對組件與組件多層傳遞時,不敢大膽的解耦組件,只能進行到父子組件這個層面,並且組件複用率層面上也有所降低。
$attr
與 interitAttrs 之間的關係默認狀況下父做用域的不被認做 props 的特性綁定 (attribute bindings)
將會「回退」且做爲普通的 HTML 特性應用在子組件的根元素上。當撰寫包裹一個目標元素或另外一個組件的組件時,這可能不會老是符合預期行爲。經過設置 inheritAttrs 到 false,這些默認行爲將會被去掉。而經過 (一樣是 2.4 新增的) 實例屬性 $attrs
可讓這些特性生效,且能夠經過 v-bind 顯性的綁定到非根元素上。
注意:這個選項不影響 class 和 style 綁定。
官網上並無給出一點 demo,語意上看起來仍是比較官方的,理解起來老是有點不太友好,經過一些 demo 來看看發生了什麼。
<template> <div> {{first}} </div> </template> <script> export default { name: 'demo', props: ['first'] } </script> 複製代碼
<template> <div class="hello"> <demo :first="firstMsg" :second="secondMessage"></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, data () { return { firstMsg: 'first props', secondMessage: 'second props' } }, } </script> 複製代碼
父組件在子組件中進行傳遞 firstMsg 和secondMsg 兩個數據,在子組件中,應該有相對應的 props 定義的接收點,若是在 props 中定義了,你會發現不管是 firstMsg 和 secondMsg 都成了子組件的接收來的數據了,能夠用來進行數據展現和行爲操做。
雖然在父組件中在子組件模版上經過 props 定義了兩個數據,可是子組件中的 props 只接收了一個,只接收了 firstMsg,並無接收 secondMsg,沒有進行接收的此時就會成爲子組件根無素的屬性節點。
<div class="hello" second="secondMessage"></div>
當咱們用 v-for 渲染大量的一樣的 DOM 結構時,可是每一個上面都加一個點擊事件,這個會致使性能問題,那咱們能夠經過 HTML5 的 data 的自定義屬性作事件代理。
<template> <div class="hello" @click="ff"> <demo :first="firstMsg" :data-second="secondMsg"></demo> <demo :first="firstMsg" :data-second="secondMsg"></demo> <demo :first="firstMsg" :data-second="secondMsg"></demo> <demo :first="firstMsg" :data-second="secondMsg"></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, data () { return { firstMsg: 'first props', secondMsg: 'secondMsg' } }, methods: { ff (e) { if(e.target.dataset.second == 'secondMsg') { console.log('經過事件委託拿到了自定義屬性') } } } } </script> 複製代碼
通過改動以後,在父組件中,把向子組件傳遞的參數名改爲了 HTML 自定義的 data-second 屬性,一樣在子組件中不進行 props 接收,就順其天然的成爲了子組件每個根節點的自定義屬性。
經過事件冒泡的原理,然而能夠從e.target.dataset.second 就能找對應的 Dom 節點進行邏輯操做。
一樣,在子組件模版上能夠綁定多個自定義屬性,在子組件包裹的外層進行一次監聽,經過 data 自定義屬性拿到循環出來組件的對應的數據,進行邏輯操做。
interitAttrs = false 發生了什麼 ?
<template> <div> {{first}} </div> </template> <script> export default { name: 'demo', props: ['first'], inheritAttrs: false, } </script> 複製代碼
對子組件進行一個改動,咱們加上 inheritAttrs: false,從字面上的翻譯的意思,取消繼承的屬性,然而 props 裏仍然沒有接收 seconed,發現就算 props 裏沒有接收 seconed,在子組件的根元素上並無綁定任何屬性。
$attrs
包含了父做用域中不被認爲 (且不預期爲) props 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 props 時,這裏會包含全部父做用域的綁定 (class 和 style 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件——在建立更高層次的組件時很是有用。
在前面的例子中,子組件props中並無接受seconed
,設置選項 inheritAttrs: false
,一樣也不會做爲根元素的屬性節點,整個沒有接收的數據都被 $attr
實例屬性給接收,裏面包含着全部父組件傳入而子組件並無在 Props裏顯示接收的數據。
爲了驗證事實,能夠在子組件中加上
created () { console.log(this.$attrs) } 複製代碼
打印出來則是一個對象 {second: "secondMsg", third: "thirdMsg"}
想要通 $attr
接收,但必需要保證設置選項 inheritAttrs: false,否則會默認變成根元素的屬性節點。
開頭說了,最有用的狀況則是在深層次組件運用的時候,建立第三層孫子組件,做爲第二層父組件的子組件,在子組件引入的孫子組件,在模版上把整個 $attr
當數做數據傳遞下去,中間則並不用經過任何方法去手動轉換數據。
<template> <div> <next-demo v-bind="$attrs"></next-demo> </div> </template> 複製代碼
<template> <div> {{second}}{{third}} </div> </template> <script> export default { props : [ 'second' , 'third'] } </script> 複製代碼
孫子組件在 props 接收子組件中經過 $attr
包裹傳來的數據,一樣是經過父組件傳來的數據,只是在子組件用了$attrs
進行了統一接收,再往下傳遞,最後經過孫子組件進行接收。
以此類推孫子組件仍然不想接收,再傳入下級組件,咱們仍然須要對孫子組件實力選項進行設置選項 inheritAttrs: false,不然仍然會成爲孫子組件根元素的屬性節點。
從而利用 $attrs
來接收 props 爲接收的數據再次向下傳遞是一件很方便的事件,深層次接收數據咱們理解了,那從深層次向層請求改變數據如何實現。意思就是讓頂層數據和最底層數據進行一個雙向綁定。
listeners 能夠認爲是監聽者。
向下如何傳遞數據已經瞭解了,面臨的問題是如何向頂層的組件改變數據,父子組件能夠經過 v-model,.sync,v-on 等一系列方法,深層及的組件能夠經過 $listeners
去管理。
$listeners
和 $attrs
二者表面層都是一個意思,$attrs
是向下傳遞數據,$listeners
是向下傳遞方法,經過手動去調用 $listeners
對象裏的方法,原理就是 $emit
監聽事件,$listeners
也能夠當作一個包裹監聽事件的一個對象。
<template> <div class="hello"> {{firstMsg}} <demo v-on:changeData="changeData" v-on:another = 'another'></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, data () { return { firstMsg: '父組件', } }, methods: { changeData (params) { this.firstMsg = params }, another () { alert(2) } } } </script> 複製代碼
在父組件中引入子組件,在子組件模板上面進行 changeData 和 another 兩個事件監聽,其它這兩個監聽事件並不打算被觸發,而是直接被調用,再簡單的理解則是向下傳遞兩個函數。
<template> <div> <p @click="$emit('another')">子組件</p> <next-demo v-on='$listeners'></next-demo> </div> </template> <script> import NextDemo from './nextDemo.vue' export default { name: 'demo', components: { NextDemo }, created () { console.log(this.$listeners) }, } </script> 複製代碼
在子組件中,引入孫子組件 nextDemo。在子組件中像 $attrs
同樣,能夠用 $listeners
去總體接收監聽的事件,{changeData: ƒ, another: ƒ}
以一個對象去接收,此時在父組件中子組件模板上監聽的兩個事件不但能夠被子組件實例屬性 $listeners
去總體接收,而且同時能夠在子組件進行觸發。
一樣在孫子 nextDemo 組件中,繼續向下傳遞,經過 v-on 把整個 $listeners
所接收的事件傳遞到孫子組件中,只是經過 $listeners
把其全部在父組件拿到監聽事件一併經過 $listeners
一塊兒傳遞到孫子組件裏。
<template> <div class="hello"> <p @click='$listeners.changeData("change")'>孫子組件</p> </div> </template> <script> export default { name: 'demo', created () { console.log(this.$listeners) }, } </script> 複製代碼
依然能拿到從子組中傳遞過來的$listeners
全部的監聽事件,此時並非經過$emit
去觸發,而是像調用函數同樣,$emit
只是針對於父子組件的雙向通訊,$listeners
包了一個對象,分別是 changeData 和 another,經過$listeners.changeData('change')等於直接觸發了事件,執行監聽後的回調函數,就是經過函數的傳遞,調用了父組件的函數。
經過 $attrs
和 $listeners
能夠很愉快地解決深層次組件的通訊問題,更加合理的組織你的代碼。
下篇導讀
以上介紹瞭如何在高層組件向下傳遞數據,在底層組件向上通知改變數據或者進行一些行爲操做,而$listeners
就像是調用了父組件的函數同樣,看上去根本沒有什麼區別,你可能會想用$parents,$children
同樣能作到。不是不可用,而是在什麼狀況下適合用,經過下篇介紹木偶組建和智能組件好好理一下正確場景下如何準確利用 Api 進行行爲交互、數據交互。
Vue 中在組件層面的數據和行爲通訊,前五章經過一些 demo 和進行了深刻總結,包括如下幾點:
$emit
與 $on
的通訊,父子組件 v-on 與 $emit
的通訊$attrs
與 $listeners
深層次數據傳遞與行爲交互的運用模式以上涵蓋了大量組件與組件之間的通訊模式,只有能熟練掌握以上知識點,接下來才能對智能組件與木偶組件寫法和封裝有準確用法。
智能組件能夠稱爲第三方通用組件,也能夠稱之爲業務型公用組件,與父組件之間的關係是徹底解耦的,只能經過 props 進行數據傳遞,event 進行事件傳遞,不依賴於任何環境,只須要傳遞相應的數據和事件,就能獲得你想要的操做。
木偶組件是爲了業務頁面進行拆分而造成的組件模式。好比一個頁面,能夠分多個模塊,而每個模塊與其他頁面並無公用性,只是純粹拆分。
還有一個方面則是複合組件的聯動用法。當一個智能組件是由兩個組件組成的一個複合智能組件,而它的子組件與父組件之間就有一個木偶的原理,由於二者是相互的,在開發者調用並需保持它們的關係性、規範性,一旦改變其自己的模式則會無效。
木偶組件的拆分簡便用法
對於每個木偶組件在定義以前,你必然會知道它將做用於哪一個頁面,在哪一層,都是有一個準確的不變性,取決於你對頁面的拆分深度和數量。
$parent
指向當前組件的父組件,能夠拿到父組件的整個實例。前面已經說了,木偶組件能夠明確的知道運用在每一個 spa 頁面對應路由的第幾層組件,多是當前頁面的子組件,孫子組件,或者更深的層次。而想和父組件進行通訊的話,在不考慮複用的前題下,能夠明確如何與父組件進行數據通訊或者行爲通訊。
<template> <div class="hello"> {{msg}} <demo></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, data () { return { msg: '父組件', } } } </script> 複製代碼
<template> <div> <p>{{demoMsg}}</p> <p @click="handleClick">子組件</p> </div> </template> <script> export default { name: 'demo', data () { return { demoMsg : '' } }, methods: { handleClick () { let msg = this.$parent.msg this.demoMsg = msg this.$parent.msg = '父組件數據被改了' } } } </script> 複製代碼
demo 組件已經明確的知道是 Hello 組件的子組件,也能夠是 demo 組件是 Hello 組件的木偶組件,經過 $parent
就能夠隨意取到和改動父組件實例的屬性(數據)。一樣這也並不違反數據的單向流的原則,能夠對比一下經過 v-on 和 $emit
或者 v-model,.sync 這幾種方法,不但方便不少,還更加快捷,而且明確了組件的位置,就像木偶同樣,永遠不會變,它的父組件永遠只會是同一個。
父組件
... methods : { parentMethods () { console.log('調用父組件的方法') } } 複製代碼
this.$parent.parentMethods() 複製代碼
一樣能夠調用父件的方法,經過子組的調用去執行父組件的方法。此方法是在父組件內部執行的,在某些場景下就會顯得很便捷,後面會給出例子。
$children
也是針對於木偶組件的應用,它和$parent
相反,此 Api是對於一個組件來講,已經明確知道它的子組件,也多是一個子組件集,準確地拿到想要的子組件實例,或者子組件集實列$children
能夠經過父組件拿到子組件的實例,它是以一個數組的形式包裹。
<template> <div class="hello"> <p @click='handlerClick'>父組件</p> <demo></demo> <demo></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo }, methods: { handlerClick () { console.log(this.$children) this.$children.forEach(item => { item.demoMsg = '經過$children改變' }) } } } </script> 複製代碼
<template> <div> <p>{{demoMsg}}</p> </div> </template> <script> export default { name: 'demo', data () { return { demoMsg : '' } } } </script> 複製代碼
此時已經不是經過子組件去與父組件通訊,而是用父組件與子組件通訊,$parent
與$children
就造成了一個父子組件互相通訊的機制,仍是那句重點一句只適合木偶組件的模式
。
在父組件中明確 demo 組件是子組件,經過$children
拿到全部 demo 組件的實例,經過 forEach 循環改變每一個子組件的實例屬。由於 data 裏全部屬性(數據)都是經過 object.defineproperty 來進行數據劫持,把 data 裏的屬性都綁到 Vue 實例上。從中咱們能夠垂手可得的獲得它。
智能組件的運用
智能組件多是業務組件也多是第三方通用組件,總歸是多個組件公用的子組件,由於它可能服務多個組件或者頁面,當嵌入不一樣組件裏,所須要展求的業務能力也是有所區別的,所以稱之爲智能組件。
比方說一個智能組件 A,將嵌入 B,C 組件作爲子組件:
當A嵌入到B中須要顯示文案嵌入B組件中
當A嵌入到C中須要顯示文案嵌入C組件中
經過向智能傳遞一個數據和標識,告訴它我須要你展現什麼?
<template> <div class="hello"> <p>父組件</p> <demo type='A'></demo> </div> </template> <script> import Demo from './Demo.vue' export default { name: 'hello', components: { Demo } } </script> 複製代碼
<template> <div> <p>{{type==='B'?'嵌入B的組件':'嵌入C的組件'}}</p> </div> </template> <script> export default { name: 'demo', props: ['type'] } </script> 複製代碼
對於智能組件你永遠不知道你將做用於哪一個組件之下,這自己就是一個不定因素,特別對於通用組件,這將會暴露各類方法和 props 數據,只有傳遞數據傳遞事件去作本身想作的事件,智能組件(也是一個封裝模塊),會根據傳入的數據和事件去作內部封裝後所作的事情,而你並不能夠輕意的隨便改動它。
智能組件與木偶組件同時能夠相互嵌套,能夠做用在複合組件上
。通常複合組件是都是通三方通用組件稱之爲智能組件,可是複合組件的父組件和子組件一樣能夠互相成爲對方的木偶組件
,二者能夠成爲相互依賴的關係
。不管從代碼量和理解,調用都會很方便,木偶組件相比智能組件更方便理解和簡潔
,可是功能上就比較單一
。
accordion屬於第三方通用組件,一樣也是一個複合組件。
<template> <div> <slot></slot> </div> </template> <script> export default { props : ['repeat'], methods : { open (uid) { this.$children.forEach(item => { if(item._uid != uid){ item.close = false } }) } } } </script> 複製代碼
<template> <div> <p @click='handleClick'>{{title}}</p> <div v-show='close' > <slot></slot> </div> </div> </template> <script> export default { props : ['title'], data () { return { close : false } }, created () { if(this.$parent.repeat === true) { this.close = true } }, methods : { handleClick () { this.$parent.open(this._uid) this.close = !this.close } } } </script> 複製代碼
<template> <div class="hello"> <accordion :repeat='true'> <accordion-item title='vueTitle'>vue</accordion-item> <accordion-item title='vue-routerTitle'>vue-router</accordion-item> <accordion-item title='vuex-Title'>vuex</accordion-item> </accordion> </div> </template> <script> import Accordion from './accordion.vue' import AccordionItem from './accordion-item.vue' export default { name: 'hello', components: { Accordion, AccordionItem } } </script> 複製代碼
先從智能組件這個方面提及,不管是 accordion 仍是 accordion-item 同向外暴露一個 props進行你但願的操做。
accordion 暴露了一個 repeat,當 repeat 爲 true 的時候則把全部 item 項初始化都進行展開。
accordion-item 暴露了一個 title,能夠隨意傳入你想設計的標題。
以上這些每每都是一些不定因素,也不知道它可能會嵌套在哪一個頁面組件的哪一層,這就是複合組件的智能方面。
再從木偶組件這個方面論一下。accordion 與 accordion-item 二者是父子組件關係,這種關係是不可變的,想要用到這個複合組件,accordion 與 accordion-item 必須保證肯定的父子組件關係,而且缺一不可,從中就能體現出二者的木偶性。
accordion-item 經過$parent
調用 accordion 父組件的 open 方法, 而 accordion 經過$children
拿到每個 accordion-item 子組件的實例,進行顯示隱藏的轉換。二者很充分造成了一個對木偶關係,這種父子關係是永遠斷不了的。
總結: 木偶組件:子組件只能有一個爹,必須是惟一的,並且父子倆長得一模一模,誰離開誰都活不了。
智能組件:子組件能夠有N個爹,非惟一性,並且父子長得不必定要同樣,子組件可能會有N個爹的特性,子組件離開哪一個爹都能繼續生存。
中央事件通訊,就像一根線同樣,把兩個組件的通訊用一根線鏈接起來。前面幾節課講了父子組件通訊與深層次嵌套組件通訊,而且已經經過各類 Api 和良好的解決方案,可是同級組件怎麼辦,不管用$emit
v-on
v-model
.sync
$attr與$listeners
都不適用,以上這些都是基於嵌套的父子組件進行通訊
。
同級組件通訊,也是一種常見的通訊模式,在一個大的容器下(父組件)底下有兩個平級的組件,兩個組件進行數據交或者行爲交互,在 Api 的方法裏也沒有專門的設計。
$emit
,v-on,$on
三者結合使用這種操做是很是複雜的,若是你能良好掌握以上三個 Api 進行同級組件的通訊,那你對這三個 Api 已經徹底掌握了。這種方法是一種過渡方法,b->a
a->c
,意思是a
去通知b
,b
對a
進行一個監聽,當a
監聽到件事,在進行向c
觸發,c
的內部再進行監聽,這樣就造成了一個過渡鏈條。可是代碼上就不顯的那麼直觀了,多個觸發事件,多個監聽事件,一旦這種平級組件須要通訊多了,那麼代碼就有一種很難維護的感受。
實例demo
<template> <div> <p @click="$emit('fromFirst','來自A組件')">first組件</p> </div> </template> <script> export default { name: 'first' } </script> 複製代碼
按着上面的講解的順序,先定義一個同級子組件,當點擊的時候向外觸發一個eventName
爲fromFirst
的事件,傳遞一個來自A組件
的參數這就造成了b->a
讓a
去監聽事件,讓b
去觸發事件。
<template> <div> <p>父組件</p> <first v-on:fromFirst="hanlderFromA"></first> <second ref="second"></second> </div> <template> <script> import First from './first.vue' import Second from './second.vue' export default { name: 'login', components: { First, Second }, methods: { hanlderFromA (Bmsg) { let second = this.$refs.second second.$emit('fromLogin', Bmsg) } } } <script> 複製代碼
First
/Second
,仍是延續b->a
。此時a
就是這個父組件,再梳理一下知識點,v-on與$emit
是進行父子組件事件通訊
,做用在父子組件兩個層面上,在First
組件模版上進行一個v-on監聽
,一旦監聽到觸發fromFirst
事件,則進行hanlderFromA
函數。a->c
這個階段,$emit與$on
都是做用在同一個組件的實列上,經過this.$refs
拿到Second
組件的實列,在執行hanlderFromA函數
時再告訴c
組件進行通訊,同時把從b
接收到的參數再次傳入。以上很明顯能看出 A(父組件)只是一個過渡體,也能夠說是一個真實的中央體,進行中央事件的派發。
<template> <div> <p>{{Bmsg}}</p> <p>second組件</p> </div> </template> <script> export default { name: 'second', created () { this.$on('fromLogin', (Bmsg) => { this.Bmsg = Bmsg console.log('通訊成功') }) }, data () { return { Bmsg: '' } } } </script> 複製代碼
Second 組件是被通訊的一方,在a(父組件)
進行觸發,然而在c(second)
組件中進行監聽,一旦監聽到了fromLogin
事件,能夠作你想作得改變數據,行爲操做都不是問題了。
這就是b->a a->c
的模式,我只能用一句話說,複雜!實在是複雜,那必然有簡單的方法。在瞭解更簡單的方法以前,先了解一下ES6 模塊的運行機制
。
ES6 模塊的運行機制
JS 引擎對腳本靜態分析的時候,遇到模塊加載命令import
,就會生成一個只讀引用
。ES6 export 的原始值變了,import加載
的值也會跟着變。所以,ES6
模塊是動態引用,而且不會緩存值,模塊裏面的變量綁定其所在的模塊。
舉個例子
// lib.js export let counter = 3; export function incCounter() { counter++; } // main.js import { counter, incCounter } from './lib'; console.log(counter); // 3 incCounter(); console.log(counter); // 4 複製代碼
雖然在main.js
執行程序的時候加載了count
,可是count
在lib.js
和在main.js
裏造成了一個引用關係,一旦libs內部的export
導出的counter
發生變化時,main.js
中一樣會發生變化。
經過額外的實例進行簡單的中央事件處理
定義一個額外的實例進行一個事件的中轉,對於ES6
模塊的運行機制已經有了一個講解,當模塊內部發生變化的時候,引入模塊的部分一樣會發生變化,當又一個額外的實例對加載機制進行引入進行$emit與$on
進行綁定通訊,能垂手可得解決問題,經過b->a->c
的模式直接過渡。
定義一箇中央事件實例
import Vue from 'vue' export default new Vue() 複製代碼
new 一個 Vue 的實例,而後把這個實例能過 es6 模塊機制導出。
<template> <div> <p>父組件</p> <first></first> <second></second> </div> </template> <script> import First from './first.vue' import Second from './second.vue' export default { name: 'login', components: { First, Second } } </script> 複製代碼
在父這裏只須要進行兩個同組件的引入,能夠刪除任何過渡的方式。
<template> <div> <p @click="handleClick">first組件</p> </div> </template> <script> import Bus from './bus.js' export default { name: 'first', methods: { handleClick () { Bus.$emit('fromFirst', '來自A的組件') } } } </script> 複製代碼
在first同級
組件中把bus
實例引入,點擊時讓bus
實例觸發一個fromFirst
事件,這裏你可能已經理解 module 加載機制配合在單個實例上用$emit和$on
進行通訊綁定,往下看。
<template> <div> <p>{{Bmsg}}</p> <p>second組件</p> </div> </template> <script> import Bus from './bus.js' export default { name: 'second', created () { Bus.$on('fromFirst', ( Amsg )=> { this.Bmsg = Amsg console.log('同級組件交互成功') }) }, data () { return { Bmsg: '' } } } </script> 複製代碼
一樣也引入bus
實列,經過bus
用$on
監聽fromFirst
事件,由於bus實例
與bus.js
裏的export defalt new Vue
關係是一個引用關係,當代碼執行後,不管first
或者second
組件經過bus實例
造成了一個中央事件鏈條
,這種方法不但直觀,也更加便捷。
中央事件的延生 跨組件深層次交互
既然同級組件能夠用中央事件去過渡,那深層次嵌套不一樣級組件能夠嗎?那你確定第一時間用到了 Vuex,但我一直認爲 Vuex 操做大量的數據聯動性很是有用,可是若是隻是一個改變數據,或者執行事件,用起來反而更加直觀。
將要模擬的方案:
當firstInner組件
可能會與second組件
或者secondInner組件
發生跨組件深層次交互也一樣能夠用中央事件去進行過渡,若是說 vuex 是頂層共享數據源,那麼中央事件就是頂層共享通訊網
。
demo 示例
前面的全部父組件都不寫代碼了,只展現一下firstInner 組件、secondInner 組件。
<template> <div> <p @click="handleClick">firstInner組件</p> </div> </template> <script> import Bus from './bus.js' export default { name: 'first', methods: { handleClick () { Bus.$emit('fromFirstInner', '來自firstInner組件') } } } </script> 複製代碼
<template> <div> <p>secondInner組件</p> </div> </template> <script> import Bus from './bus.js' export default { name: 'secondInner', created () { Bus.$on('fromFirstInner',(msg) => { console.log(msg) }) } } </script> 複製代碼
不管你想通訊的兩個組件嵌到在任何地方,它們的關係是如何的,只須要經過中央事件的處理,都能完成,同時還能夠進行一對多的中央事件處理方式。在程序代碼可控的狀況下,沒有什麼是不可行的,只要數據量的變更是在可控範圍以內,作一箇中央事件網去行成一個通訊網絡,也是一個不錯的選擇。