vue中的組件以及父子組件間通信傳值

文章結構.png

前言

您將在本文當中了解到,往網頁中添加數據,從傳統的dom操作過渡到數據層操作,實現同一個目標,兩種不同的方式.以及什么是組件,如何定義和使用組件,父子組件之間如何進行簡單的通信傳值...

對于vuejs,我也只是個初學者,很多人都覺得簡單,但我覺得是它并不容易的,就像JQuery的,常用的API也就那些,但是遇到一些炫酷的效果,就是寫不來。

在切換到寫Vuejs代碼中,你不需要去關注dom層操作,更多的精力是放在處理數據上,數據是什么,就讓頁面顯示什么,操作數據,就是在操做view(視圖),這與JQuery是不一樣的,編程思路是需要進行轉化的

單純的vuejs其實是不足以撼動jQuery的地位的,它的強大之處在于它的生態系統非常豐富,路由,模型,UI組件等各個部分的鉤子等令vuejs風靡國內外,借鑒了Angular中指令,React中組件化等,上手相對而言比較容易

如今jQuery時代真是江河日下了,這里我并不是說它不重要,它仍然是非常優秀而重要的,只是任何技術都有輝煌和落幕的時候,時代在進步,技術也在不斷更新迭代..

從github上的star數看得出,vue勢頭略蓋過react,甩angular幾條街,已形成三足鼎立趨勢,凡是react,angular能做的,無論是pc,移動端,甚至webapp,pwa應用(lavas=vue+pwa),小程序(wepy),vue幾乎無孔不入了

在vue的使用過程中,從開始學習單純的引入script標簽加載vuejs腳本形式到最終使用vue-cli腳手架搭建出來的項目,它又換成另外一種編程思路

前一種方式更多是對vuejs中的一些API學習的驗證,還是可以的,它是把html,js和各種邏輯耦合在一起進行編碼,類似于jQuery的風格.它也能搞事,但是這與腳手架搭建起的應用是不同的

后一種使用腳手架方式卻是我們常用的方式,它是以一種單文件組件方式,也就是以.vue后綴名文件就是一個組件,這個單文件組件定義包含了模板,邏輯和它的樣式,它做了一個非常好的組件封裝.

在vuejs中組件與組件之間的通信,關聯操作,數據共享,路由狀態的切換變更,UI組件的嵌套,插件與主程序的額外拓展等,如果你覺得處理他們起來覺得很簡單,那才是真簡單..

本文并不是什么高大上的內容,首次分享vue學習筆記心得,關于vue的知識特別多,也特別雜,旨在記錄自己學習過程中的一些困惑和理解,如果你是大神,就直接忽略吧,也歡迎路過的老師多提意見和指正

vuejs是什么?

  • 它只關注視圖層的view,是構建用戶界面的漸進式框架

  • 數據驅動,聲明式渲染(模板,插值表達式),模塊化,組件化,客戶端路由(vue-router),數據狀態管理(vuex),構建工具(vue-cli)

vue中核心點

  • 響應式數據綁定,當數據(model層)發生改變,它會自動更新視圖(view),內部實現原理是利用Es5中的Object.definedProperty中的setter/getter代理數據,監控對數據的操作

  • 視圖組件,UI界面對應的每個功能模塊,可視為組件,劃分組件是為了更好管理,維護,實現代碼的復用,減少代碼之間的依賴,也就是逼格高一詞,高內聚,低耦合

  • 虛擬DOM:運行js的速度是很快的,大量的操作DOM就會很慢,時常在更新數據后會重新渲染頁面,這樣造成在沒有改變數據的地方也重新渲染了DOM節點,這樣就造成了很大程度上的資源浪費,用內存中生成與真實DOM與之對應的數據結構,這個在內存中生成的結構稱為虛擬DOM,當model中數據發生變化時,Vue會將模板編譯成虛擬 DOM 渲染函數,并結合響應系統,在應用狀態改變時,vuejs能夠智能地計算出重新渲染組件,并以最小代價并應用到DOM操作上

  • MVVM模式:其中M:model數據模型,V:view視圖模板,而VM(觀察者,vue幫我們實現了的):view model負責業務處理,對數據進行加工,處理,之后交給視圖,它是通過在摸板中指令與數據進行關聯控制的,使用mvvm模式,編碼的重點是在于view層和model層,以前是面對DOM開發(MVP),現在更多的是面向數據編程

上面羅列出來的呢,只是一些抽象的概念,具體的理解,必須得通過代碼才能體會的,在以后的文章的中都會一一的去探索和感受

vuejs的使用

  • 方式1:獨立引用(本地方式),通過script標簽形式(本篇示例先已這種方式)

  • 方式2:線上引用,bootcdn(直接引入或者下載到本地都可以)

  • 方式3:npm方式(npm install vue),獲取最新穩定版本

  • 方式4:命令行方式(vue-cli腳手架工具)

原生js實現一dom需求操作
往頁面中插入一個button按鈕,和一內容,并且點擊按鈕時,改變它自身的顏色,實現內容的顯示和隱藏的效果:實例效果如下,例子很簡單,旨在于與vue做比較

image

實現方式1:利用原生js

css代碼

body {
       margin:0;
       text-align:center;
   }
button {
       width:100px;
       height:50px;
       margin-top:50px;
       background:green;
       color:#fff;
       outline:none;
       border:none;
       border-radius:5px;
       cursor:pointer;
       margin-bottom:10px;
    }

JavaScript代碼

*
MVP:模式,原生js操作dom         
1. 獲取dom元素節點,創建元素
2. 添加元素dom操作
3. 添加事件操作,邏輯處理

*/
window.onload = function(){
  var  oBody = document.getElementsByTagName("body")[0], // 獲取dom節點
      oDiv  = document.createElement("div"), // 創建div元素
      oBtn  = document.createElement("button"),// 創建button元素
      onOff =  true; // 切換狀態
      oDiv.setAttribute("class","content"); // 給div元素添加一個content類
      oBtn.setAttribute("id","btn"); // 給button按鈕添加一個id為btn

                
      oBtn.innerHTML = "點我按鈕"; // dom操作,添加內容
      oDiv.innerHTML = "我是川川"; // 同上
      oBody.appendChild(oBtn);// 把節點元素添加到dom中
      oBody.appendChild(oDiv);
                  
      // 添加事件
      oBtn.onclick = function(){// 添加事件,改變樣式,執行邏輯
              if(onOff){
                 this.style.backgroundColor="red";
                 oDiv.style.display = "none";
                 // hide()
               }else{
                  this.style.backgroundColor="green";
                  oDiv.style.display = "block";
                  // show()
               }
                 //onOff==true?hide():show();
                  onOff = !onOff; 
               }

              // 可以把里面的代碼封裝一下
              // 隱藏
             /*function hide(){
                     oBtn.style.backgroundColor="red";
                     oDiv.style.display = "none";
            }
           // 顯示
           function show(){
                 oBtn.style.backgroundColor="green";
                 oDiv.style.display = "block";
          }*/
                 
     }

實現方式2:jQuery

css代碼:并沒有什么多大的改變,只是添加來類名

body{
       margin:0;
       text-align:center;
     }
     .btn{
       width:100px;
       height:50px;
       margin-top:50px;
       background:green;
       color:#fff;
       outline:none;
       border:none;
       border-radius:5px;
       cursor:pointer;
       margin-bottom:10px;
     }
     .activeBtn{
        background:red;
     }

jQuery代碼

$(function(){
   var $body = $("body"),
       $btn = $("<button id='btn' class='btn'>點我按鈕</button>"),
       $div = $("<div class='div'>我是川川</div>"),
       onOff = true;
                
      $body.append($btn);
      $body.append($div);
                 

      $btn.click(function(){
          if(onOff){
               $(this).addClass('activeBtn');
               $div.css("display","none");
          }else{
                          
               $(this).removeClass('activeBtn');;
               $div.css("display","block");
          }
          onOff = !onOff;
      })
})

實現方式3:vue

css代碼同上:

html代碼,模板

<div id="app">
     <button id="btn" class="btn" v-bind:class="status?defaultClass:activeClass"  v-on:click="btnFun">{{btnMsg}}</button>
    <div v-if="status" class="content">{{descMsg}}</div>    
</div>

使用vuejs代碼

var vm =  new Vue({
     el: "#app",
     data: {
     btnMsg: "點我按鈕",
     descMsg: "我是川川",
     status:true,
     defaultClass:"btn",
     activeClass:"activeBtn"
  },
  methods:{
     btnFun:function(){
       this.status = !this.status;
     }
  }
})

上面的代碼也可以整合成這樣

var data = {
    btnMsg: "點我按鈕",
    descMsg: "我是川川",
    status:true,
    defaultClass:"btn",
    activeClass:"activeBtn"
}
var methodsObj = {
    btnFun:function() {
      this.status = !this.status;
    }
}
var vm =  new Vue({
   el: "#app",
   data:data,
   methods:methodsObj
})

三種方法對比:

共同點:實現同一個效果,但是底層邏輯都是一致的

不同點::用原生js,jQuery這兩種方式在于操作DOM,怎么創建,獲取,遍歷元素等,添加事件,需借助原生方法或者jQuery提供的方法操作dom,而vuejs,它關注點是數據,數據是什么,就讓頁面顯示什么,并通過在模板中綁定指令,屬性的方式與數據進行關聯,數據與方法進行分離,數據驅動實現頁面的渲染

在上面的vuejs代碼中,涉及到的知識有:vue實例化的屬性和方法,模板,插值表達式({{表達式}}),指令,屬性的綁定,事件方法監聽綁定,條件渲染v-if,動態綁定class:

其中el:實例選項,值的類型是字符串或者實際html元素對象,邊界管理,掛載點,確定vue的作用域范圍

data:實例選項,數據,值的類型可以是Object或者函數,注意在定義子組件時,只接受function methods:實例選項,方法,值是一個對象,注意,不要使用箭頭函數定methods函數,例如:btn:()=>tthis.status = !this.status,箭頭函數綁定來父級作用域的上下文,所以這個this將不會指向這個vue實例,this.status將是undefined

插值表達式:雙大括號語法{{表達式}}這是數據綁定最常見的形式,其中雙大括號里面的表達式,不僅僅可以是變量,還可以進行簡單邏輯(加減..判斷)運算,注意表達式若在雙大括號之外,它是不起作用的,大括號相當于是聲明了具備了vue的執行壞境

v-html:值的類型是string,它是在html標簽內聯中寫的,v-html="",雙大括號會將數據解釋為純文本,并不是HTML ,為了渲染輸出真正的 HTML ,你需要使用 v-html 指令,被插入的內容都會被當做 HTML,數據綁定會被忽略

注意,你不能使用 v-html 來復合局部模板,因為 Vue 不是基于字符串的模板引擎。組件更適合擔任 UI 重用與復合的基本單元

網站上動態渲染的任意 HTML 可能會非常危險,因為它很容易導致 XSS 攻擊。應該對可信內容使用 HTML 插值,絕不要對用戶提供的內容插值,例如表單之類的,正常情況下,都是用插值表達式雙大括號方式

v-text:值的類型是string,例如:v-html="",更新元素文本內容,與插值表達式是等價的,與v-html區別是,它不會渲染解析html標簽,會原樣當做字符串輸出

v-on:指令:綁定事件監聽器,事件的類型由參數指定,可縮寫@符號,值的類型:函數(方法),它是寫在內聯元素html標簽上的,可以攜帶參數,v-on:click=“方法名"

注意:用在普通元素上,只能監聽原生DOM事件,用在自定義元素組件上時,也可以監聽子組件觸發的自定義事件(這在子組件向父組件傳值的時候,子組件通過$emit內置的實例方法觸發自定義事件,這個自定義事件是寫綁定在父組件上的,這個特別重要)

v-bind:綁定內聯html標簽元素的屬性(style,class,id,src,title,alt,width,height等),值的類型任何,可簡寫為冒號:,例如v-bind:href="";v-bind:class="",:title="",動態地綁定一個或多個特性,或一個組件 prop到表達式,綁定自定義屬性

v-if:值的類型任何,根據表達式的值的真假條件渲染元素,表達式中的值為false是,該元素會從dom中移除

官方解釋:在切換時元素及它的數據綁定 / 組件被銷毀并重建。如果元素是 <template> ,將提出它的內容作為條件塊。當條件變化時該指令觸發過渡效果

v-show:當表達式的值為false,只是表現形式的隱藏(display:none),根據表達式之真假值,切換元素的CSS中的display屬性,如果頻繁切換時就用v-show,如果是一次的話,那么就用v-if,在性能上,v-show要優于v-if,因為不是頻繁的改變dom結構,而從代碼冗余結構上:v-if要比v-show要少

共同點:都是控制元素的顯示和隱藏,若是需要頻繁切換時,就用v-show,若是使用頻率較低,那么就用v-if

數據:基本數據類型(Number,string,null,undefined,boolean,symbol)和非基本數據類型(函數,對象),上面的data里面定義的屬性就是數據了

其實對于數據理解,無論是網頁或者app上我們能看到的東西都可視為數據,宏觀上:內容html結構作為數據的載體容器,層疊樣式(css)修飾元素標簽外觀展示,行為動作(javascript)相應的業務邏輯,請求,事件操作等,對應的就是資源(包括圖片,文字),其實能夠承載信息的載體都可視為數據(上面代碼中的狀態,數值等)

什么是組件?

[圖片上傳失敗...(image-c452a-1533190200954)]

  • 通俗一點:頁面上的某一個部分,是對應用模塊的一種抽象,它往往可以具有獨立的邏輯和功能頁面,同時又能根據規定的接口規則進行相互融合,編寫成一個完整的應用,它好比是電腦中的每一個元器件(如鍵盤,硬盤,鼠標,顯示器等),而在網頁中,對應的是導航欄,側邊欄,底部,列表,彈窗,下拉菜菜單,時間選擇器等

  • 形式上:通過自定義標簽元素,它是對原生一些html的拓展,封裝可重用性代碼,也可以是原生 HTML 元素的形式,以is特性進行擴展(在文檔中DOM模板解析有說明,主要解決的是在標準html標簽內嵌套自定義標簽出現莫名的bug問題)

  • 頁面只不過是這些組件的容器,也可以理解為一個大的應用(網站,app等)是由很多部分組成,每個部分就可以看成一個小組件,通過組件的自由組合可形成的功能完整的界面,當不需要某個組件或者要替換某個組件時,可以隨時進行替換和刪除,而不影響整個應用的運行,這就是組件式開發

  • 前端組件化的核心思路就是將一個巨大復雜的東西拆分成若干個粒度合理的小東西

[圖片上傳失敗...(image-c4564e-1533190200954)]

使用組件化的好處

  • 提高的開發效率(A,B,C,D前端小伙伴可同時進行,根據功能模塊獨立開發)

  • 方便重復使用,簡化調試步驟,方便單元測試

  • 提升整個項目的可維護性,便于團隊成員的協同開發,團隊中的前端leader給成員劃分功能模塊,每個模塊,由誰負責,最后拼接起來成一個完整的應用(不懂技術的產品都是紙上談兵)

組件化的特性

  • 高內聚(也就是說組件的功能必須是完整的,例如要實現下拉菜單功能,那在下拉菜單這個組件中,就把下拉菜單所需要的所有功能全局實現,那些第三方UI(例如iview,elemUI)就是如此)

  • 低耦合(通俗點說,功能邏輯代碼要獨立,不能和項目中的其他代碼發生沖突,在實際項目中,避免不了要涉及團隊協作,傳統方式是按照業務去編寫代碼,業務邏輯和數據耦合在一起,這就很容易發生相互沖突,所以運用組件化方式就可大大避免這種沖突的存在)

  • 每一個組件都應該有自己清晰的邏輯,職責,完整的功能,較低的耦合便于單元測試和重復利用

  • 頁面上每個獨立的可視/可交互區域都可以視為一個組件

  • 每個組件對應的是一個工程目錄,組件所需要的各種資源在這個目錄下就近維護(也就是模板(內容),數據,樣式)

  • 當內容結構樣式相似時,頁面中重復出現多次,那么就可以把它拆分出一個組件(什么時候該拆分成一個組件,將一個大組件分成若干個小組件)

組件的組成

  • 一個完整的組件,應該包括模板(內容結構html),層疊樣式(css),行為動作(邏輯,javascript代碼),數據,在你用腳手架搭建一個項目的時候,一個單文件組件.vue文件就是一個組件,它就包含了模板,數據,樣式

  • 組件可以層層嵌套,理論上是可以無限制的拆分,但是也不可盲目拆分,應當遵循上面提到的組件化原則,什么時候該拆分一個組件,得根據實際情況由前端代碼的你來決定的,組件層次嵌套多了,意味著結構復雜,那么對應的數據傳遞也會變得復雜

[圖片上傳失敗...(image-1e4a0a-1533190200954)]

怎么理解父組件與子組件,兄弟組件,非父子組件

剛開始看vue官方文檔時候,對于理解父子組件真的很懵逼,迷迷糊糊的,父組件與子組件其實是一個相對的概念,你可以把它理解包含與被包含的關系,被包含的自定義標簽元素稱為子組件,根實例下的模板的內容是父組件,可以對比以前寫html的時候,元素嵌套那種層級關系。

通過全局注冊(Vue.component(tagName, options))或者局部注冊,自定義標簽元素的是子組件,在根實例的作用域范圍內,父實例的模塊中以自定義元素 調用子組件進行使用,要注意的是確保在初始化根實例之前,注冊了子組件

兄弟組件:同級關系的自定義標簽元素在父模板中進行使用稱為為兄弟組件

非父子組件:非同級關系自定義標簽元素(可以通過總線的方式,本篇不涉及此內容,以后在總結)

[圖片上傳失敗...(image-3c7717-1533190200954)]

沒有代碼的實際演示,是理解不了上圖他們之間怎么通信傳值的,組件之間的通信傳值是一塊硬骨頭,邏輯比較繞,遠比函數傳參復雜得多,為來更好的理解父子組件間傳值,下面以一個todolist的經典例子

同樣,我會一步一步從原生js,jQuery在到vuejs,并且實現父子元素的通信,實現效果如下圖所示:

輸入框內輸入值,點擊添加按鈕,將表單中的值添加到頁面中,同時,又可以刪除列表項內容,注意是刪除列表項而不是隱藏

image

原生js實現todolist
css代碼:

body{
 margin:0;
 text-align:center;
 padding-top:30px;
 width:-moz-fit-content;
 width:-webkit-fit-content;// 子元素有浮動時,仍然想要子元素水平居中,配合margin:auto;
 width:fit-content;
 margin:0 auto;

}
input{
  width:400px;
  height:30px;
  outline:none;
  float:left;
}
#btn{
  width:50px;
  height:35px;
  display:inline-block;
  float:left;
  background:red;
  color:#fff;
  outline:none;
  border:none;
  cursor:pointer;
}
.clearfix:after{
  content:"";
  height:0;
  display:block;
  clear:both;
  overflow:hidden;
}
ul li{
   text-align:left;
   cursor:pointer;
}

html代碼

<div class="clearfix">
       <input type="text" id="input"  value="" placeholder="請輸入內容進行添加" />
       <button id="btn">添加</button>
</div>
<ul id="list"></ul>

avascript代碼

// 獲取元素
var oInput = document.querySelector("#input"),
   oBtn =  document.querySelector("#btn"),
   oList = document.querySelector("#list");

   // 元素上添加事件
   oBtn.onclick = function(){
                  
        var oInputVal = oInput.value;
        var oLis =document.getElementsByTagName("li");
        if(oInputVal == ""){
          alert("輸入內容為空,請輸入內容...");
        } else {
          //console.log(oInputVal);
         oList.innerHTML += "<li>"+oInputVal+"</li>";
         oInput.value = "";

         for(let i = 0;i<oLis.length;i++){
                // 通過addEventListener事件綁定監聽               
               oLis[i].addEventListener('click',function(){  
                   this.parentNode.removeChild(this);    
                })
          }
        }            
    }

使用jQuery方式實現

css,html代碼省略,與上面一致

$(function(){            
       var $input = $("#input"),
          $btn = $("#btn"),
          $ul = $("#list");
                 
          // 添加事件          
          $btn.click(function(){
            var oInputValue = $input.val();
            if(oInputValue == ""){
                alert("輸入內容為空,請輸入內容...");
            }else{
                 $ul.append("<li>"+oInputValue+"</li>");
                 $("#input").val('');
            }        
           })
           /*$("#list").delegate('li','click',function(){
                     console.log(this);
                     $(this).remove();
              })
            */
           // 通過on綁定監聽事件
             $("#list").on('click','li',function(){
                console.log(this);
                $(this).remove();//從DOM中刪除所有匹配的元素
              })
})
/*
bind() 方法向被選元素添加一個或多個事件處理程序,以及當事件發生時運行的函數。

live() 方法為被選元素附加一個或多個事件處理程序,并規定當這些事件發生時運行的函數。 
通過 live() 方法附加的事件處理程序適用于匹配選擇器的當前及未來的元素(比如由腳本創建的新元素,這個方法在最新的JQuery版本中移除了的,不推薦使用

delegate() 方法為指定的元素(屬于被選元素的子元素)添加一個或多個事件處理程序,并規定當這些事件發生時運行的函數。 
使用 delegate() 方法的事件處理程序適用于當前或未來的元素(比如由腳本創建的新元素,要注意與on寫法上的區別

on() 方法在被選元素及子元素上添加一個或多個事件處理程序,使用on方法時,注意使用on()方法時,添加的事件處理程序適用于當前及未來的元素(比如由腳本創建的新元素)如果移除事件處理程序,則使用off()方法,要綁定在父級元素上,而且在低jQuery版本中不支持這個方法
                 
remove方法不會把匹配的元素從jQuery對象中刪除,因而可以在將來再使用這些匹配的元素。但除了這個元素本身得以保留之外,其他的比如綁定的事件,附加的數據等都會被移除

empty:刪除匹配的元素集合中所有的子節點,清空的是內容

*/

對于上面的jQuery使用on對元素進行事件監聽綁定的時候,有一個坑就是,要格外注意的是:要綁定在想要操作元素的父級元素身上,否則會不起作用,其實它是利用了事件委托冒泡的機制

而在低jQuery版本中,沒有on這個方法,若使用它,則會報錯,而對于on方法取而代之的是delegate()這個方法,這里要注意與on方法寫法的區別,總體來說,on方法已經替代了其他方法了的

使用vue實現

  • 怎么定義組件,注冊組件?

  • 父組件怎么向子組件傳值?

  • 子組件怎么向父組件通信?

[圖片上傳失敗...(image-3a32d8-1533190200954)]

你將在以下看到,我先不使用組件方式實現todolist,然后轉化為組件的方式進行編寫,添加內容實現父組件傳值給子組件,刪除列表項,子組件怎么觸發父組件進行通信,感受數據驅動影響視圖,這里使用vue的方式暫且先通過script標簽腳本注入的方式進行使用,與引用jQuery庫方式沒有什么區別

未組件化實現todolist

css代碼省略與上面的一致

html代碼(模板):內容結構上最外層包裹了一個根元素app

<div id="app">
     <div class="clearfix">
         <input type="text" name="" placeholder="請輸入內容進行添加" v-model="inputVal">
         <button type="button" id="btn" @click="addBtn">{{addCon}}</button>
     </div>
     <ul id="list">
         <li v-for="(item,index) in list" @click="deleteList(index)">{{item}}</li>
      </ul>
</div>

javascript代碼

var app = new Vue({
        el:"#app",
        data:{
          addCon:"添加",
          inputVal:"",
          list:[]
        },

        methods:{
          // 點擊添加按鈕添加
          addBtn:function(){
          if(this.inputVal == ""){
               alert("請輸入框內輸入內容..");
           }else{         
               this.list.push(this.inputVal);
               this.inputVal = "";
           }
                      
           },
           // 刪除列表子項
           deleteList:function(index){
             this.list.splice(index,1);
            }
          }
})

如下圖效果所示


image

從上面的示例代碼中涉及到幾個知識點:v-model指令,v-for循環列表,通過先前學過的內聯樣式中綁定事件方法@(v-on),在根實例app中的metods方法中操作數據,實現我們想要的功能,其實vue只是幫我們做了vmodel層的事情,具體的業務邏輯,仍然是離不開原生js的,例如操作數組添加,刪除,截取,拼接等一些方法的

v-model:在表單控件或者組件上創建雙向數據綁定

v-for:循環展示數據,使用該指令時,必須使用特定的語法,alias in expression:alias表示的是expression中別名,而expression表示的當前遍歷元素的對象,例如:v-for="item in Array"而在模板中使用{{item}}

注意在使用v-for的時候,要加上一個:key=""它會提升頁面渲染性能,這個key值是唯一的,v-for默認行為試著不改變整體,而是替換元素。迫使其重新排序的元素,你需要提供一個key的特殊屬性

其實使用index做列表的key值也不是一個最好的選擇,如果不需要給列表進行排序進行額外的操作,如果頻繁的需要跟列表進行變更的時候,使用index是存在問題的,一般都是后臺返回的字段中,寫入一個唯一的標識符,例如:id,關于key詳細內容,可以移步官方文檔閱讀

可以循環渲染數組,對象,數字,字符串,上面的示例代碼中in或者of 前面的item代表的是數組每一項值,而index代表的是索引,in 后面的是數據中的數組名

從上面的效果中看出,在我們每次進行表單輸入值,點擊添加按鈕添加事件操作時,頁面中都會新增出現一條列表項,而且每個列表項在結構樣式上都是相似的,那么我們就可以把這個列表項封裝成一個組件的,封裝好的組件在頁面上可以隨處使用

定義組件,使用組件

全局定義組件:通過Vue提供的內置方法,Vue.compontent('組件名稱',配置選項對象), Vue.component('my-component-name', { 選項/* ... */ }),其中第一個參數表示的是組件的名字,第二個參數是配置選項對象(可包括數據data,template,props,methods等實例選項)

這里要注意的是,當直接在DOM中使用一個組件(而不是在字符串模板template或者單文件組件的時候),官方推薦w3c標準自定義組件名(字母全小寫且必須包含一個連字符,烤串式寫法) 例如下面全局注冊component-a,component-b,component-c組件

Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })

而在DOM中引用組件時

<div id="app">
 <component-a></component-a>
 <component-b></component-b>
 <component-c></component-c>
</div>

當然命名組件時也可以是駝峰式,它們在注冊之后可以用在任何新創建的Vue根實例 (new Vue) 的模板中

局部定義: 在根實例外自定義組件名稱,并且在根實例中通過components方式進行注冊,全局注冊組件官方是不推薦使用的,在后續的利用vue-cli搭建的單文件組件里,都是使用局部注冊組件的方式

通過一個普通javascript對象來定義組件

var ComponentA = { 配置選項對象/* ... */ }
var ComponentB = { 配置選項對象/* ... */ }
var ComponentC = { 配置選項對象/* ... */ }

然后在components選項中定義你想要使用的組件

new Vue({
 el: '#app'
 components: {
   'component-a': ComponentA,
   'component-b': ComponentB
 }
})

對于 components 對象中的每個屬性來說,其屬性名就是自定義元素的名字,其屬性值就是這個組件的選項對象

在以后的vue-cli模塊系統中,通過 Babel 和 webpack 使用 ES2015 模塊,需要這么寫

下面的import和export default都是Es6中模塊系統,如果不清楚的話,可以閱讀Es6中的模塊化Module,導入(import)導出(export)

Es6中模塊(Module)的默認導入導出及加載順序這兩篇文章

import ComponentA from './ComponentA.vue'

export default {
 components: {
   ComponentA
 }
}

上面的是Es6的寫法,在對象中放一個類似 ComponentA 的變量名其實是 ComponentA: ComponentA 的縮寫,即這個變量名同時是:

  1. 用在模板中的自定義元素的名稱

  2. 包含了這個組件選項的變量名

注意:定義組件名大小寫問題

定義組件名的方式有兩種:

使用 kebab-case(字母全小寫,連字符)

Vue.component('my-component-name', { /* ... */ })

當使用kebab-case(短橫線分隔命名)定義一個組件時,你也必須在引用這個自定義元素時使用 kebab-case,例如 <my-component-name>,也就是要一一對應關系,否則就會報錯

did you register the component correctly? For recursive components, make sure to provide the "name" option.
  1. 使用 PascalCase(駝峰式),遇到多個單詞時,首字母大寫
Vue.component('MyComponentName', { /* ... */ })

當使用PascalCase(駝峰式命名)定義一個組件時,你在父模板中引用這個自定義元素時兩種命名法(kebab-case與PascalCase)都可以使用,也就是在DOM中使用<my-component-name>和<MyComponentName>都是可以接受的,但是要注意的是,直接在DOM(即非字符串的模板)中時只有kebab-case是有效的

定義組件時,避免混淆,個人推薦使用PascalCase(駝峰式)方式,而在模板中引用該自定義元素組件時,使用kebab-case(連字符方式),但是別人在模板中引用組件時,若寫了PascalCase(駝峰式)格式,至少得明白怎么回事

兩者比較:

共同點:都可以定義注冊組件,在模板中使用

不同點:若在vue-cli,webpack構建的項目里,若使用全局注冊所有的組件,這意味著如果你已經不使用一個組件了,它仍然會被包含在你最終的構建項目中。這會造成用戶下載的JavaScript 的無謂的增加,也就是全局注冊的組件不會被銷毀.

下面通過全局注冊組件和局部注冊組件的方式父組件傳值給子組件的方式實現添加操作

html代碼

<div id="app">
    <div class="clearfix">
        <input type="text" name="" placeholder="請輸入內容進行添加" v-model="inputVal">
        <button type="button" id="btn" @click="addBtn">{{addCon}}</button>
    </div>
        <ul id="list">
          <!--  <li v-for="(item,index) in list" @click="deleteList(index)">{{item}}</li> -->
          <todo-list v-for="(item,index) in list" v-bind:content="item" :key="index"></todo-list>
       </ul>
</div>

JavaScript代碼

/* 注冊全局組件通過Vue.component('自定義組件名稱',配置選項對象) */
Vue.component('TodoList',{ // 第一個參數為組件名字
               
     props:["content"],  // 用props接收父組件傳過來的content是在父組件里用v-bind自定義的一個變量用于接收父組件攜帶的實參值
     template:'<li>{{content}}</li>'  // template模板,要渲染的具體內容
})

var app =  new Vue({
       el:"#app",
       data:{
          addCon:"添加",
          inputVal:"",
          list:[]
        },
        methods:{
           // 點擊添加按鈕添加
           addBtn:function(){
              if(this.inputVal == ""){
                  alert("請輸入框內輸入內容..");
              }else{
               console.log(this.list,this.list.length); // 沒有進行push之前
               this.list.push(this.inputVal);
               this.inputVal = "";
               console.log(this.list,this.list.length);  // 進行了push之后
               }
              }   
         }

注意上面全局注冊組件是在根實例之外操作的,與局部注冊代碼進行比較

下面是局部注冊的代碼

/* 注冊局部組件,通過一個普通javascript對象來定義組件 */
var TodoList = {
    props:["content"],
    template:'<li>{{content}}</li>'
}


var app =  new Vue({
       el:"#app",
           components:{    // 在根組件中進行注冊
           TodoList    // 等價于TodoList:TodoList
       },
       data:{
           addCon:"添加",
           inputVal:"",
           list:[]
       },

       methods:{
        // 點擊添加按鈕添加
        addBtn:function(){
           if(this.inputVal == ""){
               alert("請輸入框內輸入內容..");
           }else{
             console.log(this.list,this.list.length); // 沒有進行push之前
             this.list.push(this.inputVal);
             this.inputVal = "";                     
             console.log(this.list,this.list.length);  // 進行了push之后
            }
          }         
        }
})

下面來分析下上面代碼是怎么進行父組件傳值到子組件中的,將數據渲染到頁面中去的

首先要理解父組件和子組件,他們是一個相對的概念

在上述示例代碼中,根組件(app)模板內的代碼都屬于父組件,而通過Vue.compont()或者局部注冊的組件都是子組件

所謂的父組件向子組件傳值,這個值傳遞其實就是數據,特定的是實參數

在上述代碼中,input框被包裹在父組件中,input框中輸入的值是數據,通過v-model進行雙向數據綁定,通過inputVal這個變量保存,經過按鈕的點擊操作后,它是保存在父組件中的list數組中,是直接掛載根實例下的,通過按鈕的添加操作,將每次新添加的值渲染到指定頁面位置當中去

父組件中的數據是無法直接的在子組件中使用的,所以在父組件引用的子組件中,通過v-bind指令綁定自定義屬性值的方式,父組件中的數據,可以通過v-for循環列表拿到數據

在上面的時例代碼中,通過自定義一個content變量屬性用來接收父組件中的數據,v-bind:content="item",這個item是父組件中list數組中的列表項,它是把list中的每一項值賦值給item,然后通過這個item,通過v-bind的方式傳給這個todo-list組件,通過content這個變量來傳的.

光這樣是不夠的,還需要在子組件里去接收父組件自定義的這個content變量,在子組件中是通過props這個屬性來接收父組件的數據,后面的值可以是數組,也可以是對象,對象允許配置高級選項,如類型檢測、自定義校驗和設置默認值

其實這個content變量是一個prop值, prop是你可以在組件上注冊的一些自定義特性。當一個值傳遞給一個 prop 特性的時候,它就變成了那個組件實例的一個屬性,本質上這個prop的類型是由父組件傳過來的值決定的,當然在寫法上這個prop要注意大小寫問題,具體可查看文檔的

在子組件的模板中使用props接收的值,這樣的話,就達到父組件傳給子組件了,為了更好的理解,我畫一張圖把這個過程屢一下的
[圖片上傳失敗...(image-433b12-1533190200954)]
上面示例代碼中,實現父組件向子組件傳值添加操作,那么現在我想點擊每個列表項的時候,能進行逐條刪除操作,該怎么實現呢,這就涉及到子組件向父組件傳值的問題了

子組件向父組件傳值通信

通過以上示例看出,當父組件根實例app里面data的list數據發生變化時,子組件TodoList也會發生變化,也就是說父組件里面的數據會影響子組件的顯示,那么問題來了,現在我想要點擊列表刪除該項

既然子組件的渲染結果是由父組件決定的,想要刪除子組件,就必須要更改父組件的數據,所以在刪除子組件的時候,我們需要點擊該子組件,子組件需要把對應的內容傳給父組件,讓父組件去改變數據,讓父組件的數據改變了的,隨之子組件便會跟著消失或者增加

子組件向父組件傳值是通過vue提供的emit內置方法實現的,vm.emit("eventName自定義事件名稱",攜帶的附加參數),觸發當前實例上的事件。附加參數都會傳給監聽器回調

以下是具體代碼

html代碼:

<div id="app">
   <div class="clearfix">
      <input type="text" name="" placeholder="請輸入內容進行添加" v-model="inputVal">
      <button type="button" id="btn" @click="addBtn">{{addCon}}</button>
   </div>
   <ul id="list">
      <!--  <li v-for="(item,index) in list" @click="deleteList(index)">{{item}}</li> -->
      <todo-list v-for="(item,index) in list" v-bind:content="item" v-bind:index="index"   v-on:deleteitem="deleteItemFun"  :key="index"></todo-list>
   </ul>
</div>

javascript代碼

/* 注冊局部組件,通過一個普通javascript對象來定義組件 */
var TodoList = {
      props:["content","index"],
      template:'<li @click="deleteList">{{content}}</li>',
      methods: {
         deleteList: function(index){
         //alert(1);
             this.$emit('deleteitem',this.index);// 第一個參數是觸發父組件自定義delete事件,第二個是子組件參數,向父組件傳遞
                      
          }
      }
}

var app =  new Vue({
         el:"#app",
         components:{    // 在根組件中進行注冊
             TodoList    // 等價于TodoList:TodoList
          },
          data:{
             addCon:"添加",
             inputVal:"",
             list:[]
          },

         methods:{
             // 點擊添加按鈕添加
             addBtn:function(){
             if(this.inputVal == ""){
                 alert("請輸入框內輸入內容..");
              }else{
                 console.log(this.list,this.list.length); // 沒有進行push之前
                 this.list.push(this.inputVal);
                     this.inputVal = "";
                     console.log(this.list,this.list.length);  // 進行了push之后
                  }
               },
              // 刪除列表項
               deleteItemFun: function(index){
                  //alert(1);
                  this.list.splice(index,1);
                }
                  
          }
})

上面的代碼中,子組件向父組件傳值,通過emit方法向外觸發事件的方式,當點擊子組件的時候,在該子組件綁定點擊click事件方法,在該子組件methods方法內,通過emit向外觸發一個自定義事件

在父組件創建子組件的同時可以去監聽父組件@deletelist="deleteItemFun",在父組件的這個模板當中,創建子組件的同時也監聽了子組件deletelist這個事件,一旦刪除事件deleteitemFun被觸發的時候就會執行父組件實例下掛載的deleteItemFun這個方法

當點擊列表項的時候,父組件監聽到子組件deleteitem時候就會到父組件的methods中執行deleteitemFun這個方法,執行相應的邏輯,這里的自定義事件是deleteitem

注意:如果你直接將this.list = []的話,那么點擊一項時,整個都會刪除,明顯不符合需求,所以同樣需要有個索引值,那么同樣,父組件傳遞給子組件一個索引值就可以了的,通過props進行傳遞,而子組件觸發父組件時,在$emit第二個參數,通過攜帶索引值參數,這個參數也會隨之的被傳入到父組件中去

注意點:

子組件內定義的事件方法或者數據是無法在父組件中使用的,反過來也是如此,也就是說,組件與組件之間,擁有獨立的數據,data,模板template,還有方法methods等其他實例選項(例如:computed,生命周期等)

同樣,畫一張圖屢一下思路的
[圖片上傳失敗...(image-e3fa47-1533190200955)]
小結:

到這里的話,父組件和子組件之間的簡單傳值就已經結束了,在寫vue代碼的時候,不要直接去操作dom,而是通過數據的改變讓頁面自動變化,父組件向子組件傳值,在父組件中通過v-on綁定自定義屬性方式存儲父組件中的數據,然后通過props在子組件中接收,這樣就可以拿到父組件中的數據

而反過來,在子組件想要向父組件通信傳值,通過emit自定義事件向外觸發的方式,并且可以攜帶參數,然后在父組件的引用子組件中進行自定義事件的綁定,改變子組件,通過emitf方法通知父組件,從而改變父組件掛載的數據,間接的操作了子組件

其實,別看官網或者網上的一些文章簡單的提到這兩種方式的,在實際中,可以看出是得做了不少工作的,那張父子組件傳值圖雖然簡單粗暴,但是內部涉及的知識卻是不少的

總結:

文章到這里就結束了,如果您能堅持讀完,相信您對父子組件傳值有所感受,整篇文章信息量比較大,從認識vue是什么,vue中的核心點,以及vue的使用,原生js實現一dom操作,分別從原生js,jQuery,在到vue,他們實現的方式有什么不同,什么是組件,使用組件化的好處,組件化的特性,組件的組成,怎么理解父組件與子組件,兄弟組件,非父子組件,最后實現todolist,分別用原生js,jQuery,Vue逐一實現的,使用原生js,JQuery,主要是感受比較他們與vuejs的差異

例子雖然比較簡單,但是它囊括了很多知識...

vuejs雖然強大,但是底層核心邏輯依然是是不變的,有些事情仍然是需要我們自己做的,只不過是原生js,jQuery是面向DOM編程,而vuejs可以理解為面向數據編程,它關注的是數據層model

其中重點是在于理解父子組件間的簡單通信,關于組件的內容,比較多,也比較雜,理解組件之間的通信非常重要,反正我覺得這是一道坎,有些內容比較繞..遇到具體哪個知識不熟悉的,一定要左手代碼,右手查文檔...文章若有不足和誤導之處,歡迎路過的老師多提意見和指正

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內容