Vue.js 系列教程 2:組件,Props,Slots


原文:intro-to-vue-2-components-props-slots

譯者:nzbin

這是關於 JavaScript 框架 Vue.js 五個教程的第二部分。在這一部分,我們將學習組件,Props 以及 Slots。這個系列教程並不是一個完整的用戶手冊,而是通過基礎知識讓你快速了解 Vuejs 以及它的用途。

系列文章:

  1. 渲染, 指令, 事件
  2. 組件, Props, Slots (你在這!)
  3. Vue-cli
  4. Vuex
  5. 動畫

組件和傳遞數據

如果你熟悉 React 或者 Angular2,組件思想和傳遞狀態對你並不陌生。如果不是, 讓我們先了解一些主要概念。

大小網站通常由不同的部分組成,並且抽象成更小的部分更容易布局、重用、並使得我們的代碼更清晰。為了避免在冗長的多層次的頁面中搜尋標簽,我們可以這樣構建組件:

<header></header>
<aside>
  <sidebar-item v-for="item in items"></sidebar-item>
</aside>
<main>
  <blogpost v-for="post in posts"></blogpost>
</main>
<footer></footer>

這是一個簡單的例子,但是你可以看到這種組合方式在開始構建網站結構時的用途。如果你要維護這些代碼,你可以很容易的了解程序的結構並且找到每一部分。

Vue 有多種創建組件的方式。讓我們從易到難,而復雜的例子就是一個普通的 Vue 程序。

app.$mount('#app');

var app = new Vue({
  el: 'hello',
  template: '<h1>Hello World!</h1>'
});

代碼正常運行,但用處不大,因為它只能使用一次,我們還沒有向不同的組件傳遞信息。從父組件向子組件傳遞數據的方式稱為 props

下面是我能做的最簡單的例子,所以非常容易理解。記住 HTML 中的 :text 是 Vue 綁定的縮寫。我們在指令部分的最后提到過。綁定可以用於所有方面,但是在這個實例中,這樣做的好處是不需要把狀態放在 mustache 模板中, 比如 {{ message }}

在下面的代碼中,Vue.component組件new Vue 稱為實例。一個程序中可以有多個實例。通常情況下,我們會有一個實例和多個組件,因為實例是主要應用程序。

Vue.component('child', {
  props: ['text'],
  template: `<div>{{ text }}<div>`
});

new Vue({
  el: "#app",
  data() {
    return {
      message: 'hello mr. magoo'
    }
  }
});
<div id="app">
  <child :text="message"></child>
</div>

See the Pen simple props by Sarah Drasner (@sdras) on CodePen.

現在我們可以在程序中隨意使用這個組件:

<div id="app">
  <child :text="message"></child>
  <child :text="message"></child>
</div>

See the Pen simple props by Sarah Drasner (@sdras) on CodePen.

我們也可以向 props 中添加驗證,這和 React 中的 PropTypes 類似。這個功能很好,因為它是自描述的,並且如果與我們的期望值不同會返回錯誤,但只有在開發模式中才顯示 :

Vue.component('child', {
  props: {
    text: {
      type: String,
      required: true
    }
  },
  template: `<div>{{ text }}<div>`
});

在下面的例子中,我在開發模式中加載 Vue ,並且故意在 prop 驗證中輸入一個非法類型。你可以看到控制台報錯。(這非常有幫助,你可以使用 Vue 的開發工具發現錯誤).

Vue.component('child', {
  props: {
    text: {
      type: Boolean,
      required: true
    }
  },
  template: `<div>{{ text }}<div>`
});

See the Pen simple props with validation by Sarah Drasner (@sdras) on CodePen.

對象應該作為一個工廠函數返回,你也可以傳遞一個自定義驗證函數,這非常有用,因為可以檢查不符合業務、輸入或者其它邏輯的數值。對於如何使用每一種類型,有一篇寫的很好的 指南.

沒有必要在在 props 中給子組件傳遞數據,也可以使用狀態或靜態值:

Vue.component('child', {
  props: { 
    count: {
      type: Number,
      required: true
    }
  },
  template: `<div class="num">{{ count }}</div>`
})

new Vue({
  el: '#app',
  data() {
    return {
      count: 0    
    }
  },
  methods: {
    increment() {
      this.count++;
    },
    decrement() {
      this.count--;
    }
  }
})
<div id="app">
  <h3>
    <button @click="increment">+</button>
    Adjust the state
    <button @click="decrement">-</button>
  </h3>
  <h2>This is the app state: <span class="num">{{ count }}</span></h2>
  <hr>
  <h4><child count="1"></child></h4> 
  <p>This is a child counter that is using a static integer as props</p>
  <hr>
  <h4><child :count="count"></child></h4>
  <p>This is the same child counter and it is using the state as props</p>
</div>

See the Pen child component using and not using props by Sarah Drasner (@sdras) on CodePen.

區別在於你是否傳遞了一個屬性並綁定它:

沒有使用狀態
<child count="1"></child>

vs

使用狀態
<child :count="count"></child>

到現在為止,我們已經用字符串在子組件中創建了內容,如果使用 babel 的話,你可以在所有瀏覽器中使用 ES6 (我強烈建議),可以使用 模板字面量 來避免難以閱讀的拼接字符串:

Vue.component('individual-comment', {
  template: 
  `<li> {{ commentpost }} </li>`,
  props: ['commentpost']
});

new Vue({
  el: '#app',
  data: {
    newComment: '',
    comments: [
      'Looks great Julianne!',
      'I love the sea',
      'Where are you at?'
    ]
  },
  methods: {
    addComment: function () {
      this.comments.push(this.newComment)
      this.newComment = ''
    }
  }
});
<ul>
    <li
      is="individual-comment"
      v-for="comment in comments"
      v-bind:commentpost="comment"
    ></li>
  </ul>
  <input
    v-model="newComment"
    v-on:keyup.enter="addComment"
    placeholder="Add a comment"
  >

See the Pen rjwJdY by Sarah Drasner (@sdras) on CodePen.

雖然有些作用,但是字符串中的內容仍然有限制。最后,在這個評論列表中,我們希望有照片和作者的名字,你可能已經猜到過多的信息會非常擁擠。而字符串中沒有語法高亮效果。

考慮到所有這些事情,我們將創建一個模板。我們會用特殊的 script 標簽包裹常規的HTML,然后使用 id 引用它來創建一個組件。當文本和元素很多的時候,這種方式更清晰:

<!-- This is the Individual Comment Component -->
<script type="text/x-template" id="comment-template">
<li> 
  <img class="post-img" :src="commentpost.authorImg" /> 
  <small>{{ commentpost.author }}</small>
  <p class="post-comment">"{{ commentpost.text }}"</p>
</li>
</script>
Vue.component('individual-comment', {
  template: '#comment-template',
  props: ['commentpost']
});

See the Pen Photo App post with Vue.js by Sarah Drasner (@sdras) on CodePen.

Slots

這樣好多了。但是如果兩個組件的內容或者樣式略有不同時會怎樣?我們可能會通過 props 將所有不同的內容及樣式傳遞到組件,每次切換所有的東西,或者我們可以復制組件並創建不同的版本。但是如果可以重用組件,並用相同的數據或功能填充它們,那就太好了。這就是 slots 的有用之處。

假如我們有一個程序實例,使用相同的組件 <app-child> 兩次。在每個子組件內部,我們需要一些相同的內容以及不同的內容。對於要保持一致的內容,我們使用一個標准的 p 標簽,而對於要切換的內容,我們放在空的 <slot></slot> 標簽中。

<script type="text/x-template" id="childarea">
  <div class="child">
    <slot></slot>
    <p>It's a veritable slot machine!<br> 
    Ha ha aw</p>
  </div>
</script>

然后在程序實例中,我們可以在在 <app-child> 組件標簽中傳遞內容,它會自動填充到 slots 中:

<div id="app">
  <h2>We can use slots to populate content</h2>
  <app-child>
    <h3>This is slot number one</h3>
  </app-child>
  <app-child>
    <h3>This is slot number two</h3>
    <small>I can put more info in, too!</small>
  </app-child>
</div>

See the Pen Slots Example by Sarah Drasner (@sdras) on CodePen.

slots 中也可以有默認內容。如果要在 slot 中寫內容,而不是寫 <slot></slot>,你可以這樣填充:

<slot>I am some default text</slot>

如果你沒有在 slot 中填充其它內容,就會顯示默認文本,這是非常有用的!鼓掌吧。

你也可以使用具名 slot 。如果一個組件中有兩個 slot, 可以通過添加 name 屬性區分它們 <slot name="headerinfo"></slot>,並且通過特定的名稱訪問 slot <h1 slot="headerinfo">I will populate the headerinfo slot!</h1> 。這非常有用。如果有多個命名的 slot 而有一個沒有命名,Vue 命名的內容填充到命名的 slot 中,而剩余的內容將填充到未命名的 slots 中。

請看以下示例:

子組件模板

<div id="post">
  <main>
    <slot name="header"></slot>
    <slot></slot>
  </main>
</div>

父組件模板

<app-post>
  <h1 slot="header">This is the main title</h1>
  <p>I will go in the unnamed slot!</p>
</app-post>

渲染結果

<main>
  <h1>This is the main title</h1>
  <p>I will go in the unnamed slot!</p>
</main>

就我個人而言,如果我一次使用多個 slot,我會將所有的都命名,這對於其他的維護人員來說非常清晰,但 Vue 提供的這個靈活的 API 也很好。

Slot 示例

另外,我們給不同的組件設置特殊的樣式,並保持所有的內容相同,因此可以迅速和容易地改變了外觀。在下面的葡萄酒標簽制造商中,其中一個按鈕將根據用戶的選擇切換組件和顏色,酒瓶背景、標簽和文本將全部切換,同時保持內容不變。

const app = new Vue({
  ...
  components: {
    'appBlack': {
      template: '#black'
    }
  }
});

主要的 Vue App HTML:

  <component :is="selected">
    ...
    <path class="label" d="M12,295.9s56.5,5,137.6,0V409S78.1,423.6,12,409Z" transform="translate(-12 -13.8)" :style="{ fill: labelColor }"/>
    ...
  </component>

<h4>Color</h4>
  <button @click="selected ='appBlack', labelColor = '#000000'">Black Label</button>
  <button @click="selected ='appWhite', labelColor = '#ffffff'">White Label</button>
  <input type="color" v-model="labelColor" defaultValue="#ff0000">

白色組件的 HTML:

<script type="text/x-template" id="white">
  <div class="white">
     <slot></slot>
  </div>
</script>

(這個演示案例非常大,所以最好在一個單獨的窗口或者標簽頁瀏覽)

See the Pen Vue Wine Label Maker by Sarah Drasner (@sdras) on CodePen.

現在,我們已經將所有的 SVG 圖片數據放置在程序中,但是實際上它放置在每個組件的 <slot> 中。我們可以根據使用情況切換不同的內容或樣式,這是一個非常好的功能。你可以看到,通過創建一個更改組件的“selected”值的按鈕,允許用戶自己決定使用哪個組件。

現在所有內容都在一個 slot 中,但是我們也可以使用多個 slot,並通過命名區分它們:

<!-- main vue app instance -->
<app-comment>
  <p slot="comment">{{ comment.text }}</p>
</app-comment>

<!-- individual component -->
<script type="text/x-template" id="comment-template">
  <div>
    <slot name="comment"></slot>
  </div>
</script>

我們可以通過引用的相同的 slot 很容易地在不同的組件之間切換,但是如果希望能夠來回切換還要保持每個組件的獨立狀態會怎樣?目前,當我們切換黑白標簽的時候,模板切換了但內容保持不變。但也許有一種情況,我們希望黑色標簽和白色標簽是完全不同的。你可以把它們包在稱為 <keep-alive></keep-alive> 的特殊組件中,這樣切換的時候會保持獨立的狀態。

檢查上面例子的異常——創建一個黑色標簽,然后一個不同的白色標簽,並在它們之間切換。你會看到,每個狀態都被保存下來,並且彼此不同:

<keep-alive>
  <component :is="selected">
    ...
  </component>
</keep-alive>

See the Pen Vue Wine Label Maker- with keep-alive by Sarah Drasner (@sdras) on CodePen.

我喜歡這個 API 的功能。

這很好,但為了簡單起見,我們總是把所有內容放置在一個或兩個文件中。當建立網站時,如果將單獨的組件放在不同的文件中,並在需要的時候導入進來,這樣的組織性更強。實際上在真實的 Vue 開發中通常是這么做的,我們將在下一部分介紹。接下來談論 Vue-cli、構建過程以及 vuex 狀態管理!


免責聲明!

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



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