vue.js了解篇4(自定義組件)

目錄
    1. 注冊組件(全局注冊/局部注冊)
    2. (向組件模版)傳遞數據 
    3. (響應組件模版中的)事件
    4. 組件添加v-model
    5. is (解決特殊限制)
    6. 插槽
    7. 動態組件

前言

除了可使用普通html元素外,Vue還可以自定義組件。

使用自定義組件的好處:
  1. 便于維護:可以使代碼結構清晰,分散到各文件中。
  2. 可復用:減少代碼冗余。

注意:
  1. 組件只能有一個根元素
  2. 組件可以使用指令
  3. 組件有和Vue相同的選項(因為組件就是可復用的Vue實例),例如 data、computed、watch、methods 以及生命周期鉤子等。僅有的例外是像 el 這樣根實例特有的選項。
  4. 每添加一個組件,就會有一個它的新實例被創建。data 選項必須是一個函數,因此每個實例可以維護一份被返回對象的獨立的拷貝。
  6. 一個組件默認可以擁有任意數量的 prop,任何值都可以傳遞給任何 prop。
  7. 多行template更易讀,但它們在 IE 下并沒有被支持,所以如果需要在不 (經過 Babel 或 TypeScript 之類的工具) 編譯的情況下支持 IE,請使用折行轉義字符。
例

<div id="app">
  <app-nav></app-nav>  <!--導航欄-->
  <app-view>  <!--內容-->
    <app-sidebar></app-sidebar>  <!--左側欄-->
    <app-content></app-content>  <!--右側內容-->
  </app-view>
</div>
...
1、全局/局部 注冊組件
Vue.component('my-component-name', { /* ... */ })
/* 
第一個參數:組件名。2種命名方式
   第一種命名方式:全小寫 加 連字符-(W3C規范,建議使用),使用<hello-world>
   第二種命名方式:HelloWorld,使用<hello-world>或<HelloWorld>
*/

格式一:全局(可在本頁中使用)

全局注冊的組件可以用在其被注冊之后的任何 (通過 new Vue) 新創建的 Vue 根實例。
例1

<div id="app">
    <!--第2步:使用全局組件-->
    <hello-world></hello-world>
</div>

<script>
// 第1步:注冊全局組件
Vue.component('hello-world', {
  template: '<h1>自定義組件!</h1>'
})

// 創建根實例
new Vue({
  el: '#app'
})
</script>
例2

<div id="components-demo">
  <button-counter></button-counter>
</div>
...
// 定義一個名為 button-counter 的新組件
Vue.component('button-counter', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
new Vue({ el: '#components-demo' })

格式二:局部(僅在該Vue實例下可用)

注意:局部注冊的組件作為其他組件的子組件時不可用,解決如下
  var ComponentA = { /* ... */ }
  var ComponentB = {
    components: {
      'component-a': ComponentA
    },
    // ...
  }
例

<div id="app">
    <!--第2步、使用局部組件-->
    <hello></hello >
</div>

<script>
var Child = {
  template: '<h1>自定義組件!</h1>'
}
// 創建根實例
new Vue({
  el: '#app',
  // 第1步、注冊局部組件(僅在id為app中使用)
  components: {
    'hello': Child,
    'hello2': {
      template: '<h1>自定義組件!</h1>'
    },
  }
})
</script>
2. 向組件模版傳遞數據

父組件的數據通過props傳給子組件

<div id="app">
    <child message="hello!"></child>
</div>
 

<script>
// 注冊
Vue.component('child', {
  // 聲明 props
  props: ['message'],
  // 同樣也可以在 vm 實例中像 "this.message" 這樣使用
  template: '<span>{{ message }}</span>'
})
// 創建根實例
new Vue({
  el: '#app'
})
</script>

單向動態綁定(當父組件的數據變化時,該變化會傳導給子組件)

<div id="app">
    <div>
      <input v-model="parentMsg">
      <br>
      <child v-bind:message="parentMsg"></child>
    </div>
</div>
 
<script>
// 注冊
Vue.component('child', {
  // 聲明 props
  props: ['message'],
  // 同樣也可以在 vm 實例中像 "this.message" 這樣使用
  template: '<span>{{ message }}</span>'
})
// 創建根實例
new Vue({
  el: '#app',
  data: {
    parentMsg: '父組件內容'
  }
})
</script>
循環

<div id="app">
    <ol>
      <todo-item v-for="item in sites" v-bind:todo="item"></todo-item>
    </ol>
</div>
 
<script>
Vue.component('todo-item', {
  props: ['todo'],
  template: '<li>{{ todo.text }}</li>'
})
new Vue({
  el: '#app',
  data: {
    sites: [
      { text: 'Runoob' },
      { text: 'Google' },
      { text: 'Taobao' }
    ]
  }
})
</script>

屬性詳解

單向動態綁定
  props 使得其父子 prop 之間形成了一個單向下行綁定:父級 prop 的更新會向下流動到子組件中,但是反過來則不行。這樣會防止從子組件意外改變父級組件的狀態,從而導致應用的數據流向難以理解(即不應該在一個子組件內部改變 prop)。
例-prop 用來傳遞一個初始值:
props: ['initialCounter'],
data: function () {
  return {
    counter: this.initialCounter
  }
}

例-prop 以一種原始的值傳入且需要進行轉換:
props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}
1、Vue中的駝峰屬性名在html中要使用連字符-(因為html大小寫不敏感)。但{{}}中不受限制。如下:

  <blog-post post-title="hello!"></blog-post>

  Vue.component('blog-post', {
    props: ['postTitle'],
    template: '<h3>{{ postTitle }}</h3>'
  })
2、屬性類型驗證。屬性類型type可以是:String、Number、Boolean、Function、Object、Array、Date、Symbol、一個自定義的構造函數。
  1、prop 會在一個組件實例創建之前進行驗證,所以實例的屬性 (如 data、computed 等) 在 default 或 validator驗證 函數中是不可用的;
  2、如果有一個屬性類型驗證失敗,(開發環境構建版本的) Vue 將會產生一個控制臺的警告。

  Vue.component('example', {
    props: {
      // 基礎類型檢測 (`null` 和 `undefined` 會通過任何類型驗證)
      propA: Number,
      // 多種類型
      propB: [String, Number],
      // 必傳且是字符串
      propC: {
        type: String,
        required: true
      },
      // 數字,有默認值
      propD: {
        type: Number,
        default: 100
      },
      // 數組/對象,有默認值。默認值應當由一個工廠函數返回
      propE: {
        type: Object,
        default: function () {
          return { message: 'hello' }
        }
      },
      // 自定義驗證函數
      propF: {
        validator: function (value) {
          return value > 10
        }
      }
    }
  })
3、傳值(靜態、動態)

<!-- 靜態賦值 -->
<blog-post title="My journey with Vue"></blog-post>

<!-- 動態賦值 -->
<blog-post v-bind:title="post"></blog-post>
<!-- 動態賦予一個復雜表達式的值 -->
<blog-post
  v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>

<!-- 傳入post對象{  id: 1,  title: 'My Journey with Vue'}的所有屬性 -->
<blog-post v-bind="post"></blog-post>
等價于
<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>
數字

<!-- 即便 `42` 是靜態的,我們仍然需要 `v-bind` 來告訴 Vue -->
<!-- 這是一個 JavaScript 表達式而不是一個字符串。-->
<blog-post v-bind:likes="42"></blog-post>

<!-- 用一個變量進行動態賦值。-->
<blog-post v-bind:likes="post.likes"></blog-post>
bool

<!-- 包含該 prop 沒有值的情況在內,都意味著 `true`。-->
<blog-post is-published></blog-post>

<!-- 即便 `false` 是靜態的,我們仍然需要 `v-bind` 來告訴 Vue -->
<!-- 這是一個 JavaScript 表達式而不是一個字符串。-->
<blog-post v-bind:is-published="false"></blog-post>

<!-- 用一個變量進行動態賦值。-->
<blog-post v-bind:is-published="post.isPublished"></blog-post>
數組

<!-- 即便數組是靜態的,我們仍然需要 `v-bind` 來告訴 Vue -->
<!-- 這是一個 JavaScript 表達式而不是一個字符串。-->
<blog-post v-bind:comment-ids="[234, 266, 273]"></blog-post>

<!-- 用一個變量進行動態賦值。-->
<blog-post v-bind:comment-ids="post.commentIds"></blog-post>
對象

<!-- 即便對象是靜態的,我們仍然需要 `v-bind` 來告訴 Vue -->
<!-- 這是一個 JavaScript 表達式而不是一個字符串。-->
<blog-post
  v-bind:author="{
    name: 'Veronica',
    company: 'Veridian Dynamics'
  }"
></blog-post>

<!-- 用一個變量進行動態賦值。-->
<blog-post v-bind:author="post.author"></blog-post>
4、組件可以接受任意特性(動態添加屬性)
  1、因為組件并不總能預見組件會被用于怎樣的場景。
  2、這些特性會被添加到這個組件的根元素上。

<blog-post title="My journey with Vue"></blog-post>
title會被加在根元素上
5、對于絕大多數特性來說,從外部提供給組件的值會替換掉(覆蓋)組件內部設置好的值。但class 和 style 則會合并。
6、禁止 根元素繼承特性
  注意 inheritAttrs: false 選項并不會影響 style 和 class 的綁定。

Vue.component('my-component', {
  inheritAttrs: false,
  props: ['label', 'value'],
  // ...
})
label、value不再從<my-component value=''>中獲取


$attrs 屬性決定將一個組件的特性名和特性值傳遞給指定元素
Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on:input="$emit('input', $event.target.value)"
      >
    </label>
  `
})
3. 響應組件模版中的事件

父組件可以使用props傳遞數據給子組件,但如果子組件要把數據傳遞回去,就需要使用事件

每個 Vue 實例都實現了事件接口:
  使用 $on(eventName) 監聽事件
  使用 $emit(eventName) 觸發事件

組件內部使用 $emit方法并傳入事件名稱來觸發一個外部事件。使用 $emit 的第二個參數來傳遞參數,外部可以通過 $event 訪問到。

事件名不存在任何自動化的大小寫轉換,必須完全一致。
注意:v-on 事件監聽器在 DOM 模板中會被自動轉換為全小寫 (因為 HTML 是大小寫不敏感的),所以 v-on:myEvent 將會變成 v-on:myevent——導致 myEvent 不可能被監聽到。所以極力推薦使用全小寫加連字符-。

舉例1

<div id="blog-posts-events-demo">
  <div :style="{ fontSize: postFontSize + 'em' }">
    <blog-post
      v-for="post in posts"
      v-bind:key="post.id"
      v-bind:post="post"
      v-on:hello-text="postFontSize += 0.1"
    ></blog-post>
  </div>
</div>

Vue.component('blog-post', {
  props: ['post'],
  template: `
    <div class="blog-post">
      <h3>{{ post.title }}</h3>
      <button v-on:click="$emit('hello-text')">
        Enlarge text
      </button>
      <button v-on:click="$emit('enlarge-text', 0.1)"> 
        Enlarge text
      </button>
      <div v-html="post.content"></div>
    </div>
  `
})

==========================
==========================
傳值1

<button v-on:click="$emit('enlarge-text', 0.1)">
  Enlarge text
</button>
...
<blog-post
  ...
  v-on:enlarge-text="postFontSize += $event"
></blog-post>


==========================
==========================
傳值2 

<blog-post
  ...
  v-on:enlarge-text="onEnlargeText"
></blog-post>
...
methods: {
  onEnlargeText: function (enlargeAmount) {
    this.postFontSize += enlargeAmount
  }
}

舉例2

<div id="app">
    <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>
</div>
 
<script>
Vue.component('button-counter', {
  template: '<button v-on:click="incrementHandler">{{ counter }}</button>',
  data: function () {    // data 必須是一個函數。這樣的好處就是每個實例可以維護一份被返回對象的獨立的拷貝,如果 data 是一個對象則會影響到其他實例
    return {
      counter: 0
    }
  },
  methods: {
    incrementHandler: function () {
      this.counter += 1
      this.$emit('increment')    
      // this.$emit('increment',1) 帶參數    ,在function (count)可獲取 或 html 元素中 $event可獲取
    }
  },
})
new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
})
</script>

.sync(2.3.0+ 新增)

在有些情況下可能需要對一個 prop 進行“雙向綁定”。不幸的是,真正的雙向綁定會帶來維護上的問題。推薦以 update:myPropName 的模式觸發事件來達到效果:
this.$emit('update:title', newTitle)

<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>
簡寫為
<text-document v-bind:title.sync="doc.title"></text-document>

整個對象的屬性
<text-document v-bind.sync="person"></text-document>

注意:
  注意帶有 .sync 修飾符的 v-bind 使用表達式、對象。只能提供想要綁定的屬性名。

在一個組件的根元素上直接監聽一個原生事件,而不是監聽子組件時

<base-input v-on:focus.native="onFocus"></base-input>

注意:在嘗試監聽一個類似 <input> 的非常特定的元素時,且根元素并不是input時會失效,listeners 屬性(一個對象,里面包含了作用在這個組件上的所有監聽器)可以解決這個問題:

例:
Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` 將所有的對象合并為一個新對象
      return Object.assign({},
        // 從父級添加所有的監聽器
        this.$listeners,
        // 然后添加自定義監聽器,
        // 或覆寫一些監聽器的行為
        {
          // 這里確保組件配合 `v-model` 的工作
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})
4. 組件添加v-model
<custom-input v-model="searchText"></custom-input>
<!--
等價于
<custom-input
  v-bind:value="searchText"
  v-on:input="searchText = $event"
></custom-input>
-->

Vue.component('custom-input', {
  props: ['value'],
  template: `
    <input
      v-bind:value="value"
      v-on:input="$emit('input', $event.target.value)"
    >
  `
})
組件上的 v-model 默認會利用名為 value 的 prop 和名為 input 的事件。
但是像單選框、復選框(使用的是checked而不是value)等類型的輸入控件可能會將 value特性用于不同的目的。可以使用model選項來解決(仍然需要在組件的 props 選項里聲明 checked 這個 prop):

<base-checkbox v-model="lovingVue"></base-checkbox>
Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})
5. is (解決特殊限制)
有些 HTML 元素,諸如 <ul>、<ol>、<table> 和 <select>,對于哪些元素可以出現在其內部是有嚴格限制的。而有些元素,諸如 <li>、<tr> 和 <option>,只能出現在其它某些特定的元素內部。

使用這些有約束條件的元素時會遇到一些問題。例如:
  <table>
    <customRow></customRow>
  </table>
這個自定義組件 <blog-post-row> 會被作為無效的內容提升到外部,并導致最終渲染結果出錯。

注意:三種情況不存在這個限制:字符串、單文件組件.vue、<script type="text/x-template">
解決

<table>
  <tr is="customRow"></tr>    <!--自定義tr組件-->
</table>
6. 插槽
組件開始標簽和結束標簽之間的內容(包括html元素、純文本、組件)稱之為插槽,插槽 prop 可以將插槽轉換為可復用的模板。
在 2.6.0 中,為具名插槽和作用域插槽引入了一個新的統一的語法 (即 `v-slot` 指令)。它取代了 `slot` 和 `slot-scope` 這兩個目前已被廢棄但未被移除且仍在[文檔中]的特性。

<alert-box>
  Something bad happened.
</alert-box>
Vue.component('alert-box', {
  template: '
    <div class="demo-alert-box">
      <strong>Error!</strong>
      <slot></slot>
    </div>
  '
})

說明:
  1、<slot></slot> 會替換組件起始標簽和結束標簽之間的任何內容,這里會直接將文本‘Something bad happened.’替換過來。
  2、如果沒有<slot></slot> ,則起始標簽和結束標簽之間的任何內容會被忽略。
  1. 作用域插槽
父級模板里的所有內容都是在父級作用域中編譯的;
子模板里的所有內容都是在子作用域中編譯的。
<navigation-link url="/profile">
  {{ url: 訪問不到。不能訪問父級組件的作用域}}  <!--undefined-->
</navigation-link>
<current-user>
  {{ user.firstName }}
</current-user>
模版
<span>
  <slot>{{ user.lastName }}</slot>
</span>

上述代碼不會正常工作,解決:

<current-user>
  <template v-slot:default="hello">  <!--hello:包含所有插槽 prop 的對象-->
    {{ hello.user.firstName }}
  </template>
</current-user>
<span>
  <slot v-bind:user="user">  <!--為了讓 user 在父級的插槽內容中可用,將 user 作為 <slot> 元素的一個特性綁定上去。綁定在 <slot> 元素上的特性被稱為插槽 prop-->
    {{ user.lastName }}
  </slot>
</span>
只有默認插槽時的縮寫(注意默認插槽的縮寫語法不能和具名插槽混用,因為它會導致作用域不明確):

<current-user v-slot="hello">
    {{ hello.user.firstName }}
</current-user>
多個插槽

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>

  <template v-slot:other="otherSlotProps">
    ...
  </template>
</current-user>
作用域插槽工作原理:
  將插槽內容包括在一個傳入單個參數的函數里function (slotProps) {    // 插槽內容  }。這意味著 v-slot 的值實際上可以是任何能夠作為函數定義中的參數的 JavaScript 表達式

<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>

重命名
<current-user v-slot="{ user: person }">
  {{ person.firstName }}
</current-user>

默認值
<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>

<!-- <submit-button>Hello</submit-button>  會顯示Hello(覆蓋默認的Submit)-->
<submit-button></submit-button>  會顯示默認的Submit
<button type="submit">
  <slot>Submit</slot>
</button>
  1. 具名插槽(多個插槽時分別指定名字)
有時需要多個插槽。例:

<base-layout>
<!--
2.6.0新增縮寫格式:<template #header>
只在其有參數的時候才可用,這里的參數header
-->
  <template v-slot:header>
    <h1>Here might be a page title</h1>
  </template>

<!--不寫默認為<template v-slot:default> ,
-->
  <p>A paragraph for the main content.</p>
  <p>And another one.</p>
<!--</template>-->

  <template v-slot:footer>
    <p>Here's some contact info</p>
  </template>
</base-layout>

模版:
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>  <!--一個不帶 name 的 <slot> 出口會帶有隱含的名字“default”-->
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>
動態插槽名(2.6.0)

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>
7. 動態組件
<!--
  緩存組件,當再次切換回到該組件時保持狀態,而不是重新創建。
  被切換到的組件必須都有自己的名字,不論是通過組件的 name 選項還是局部/全局注冊。
-->
<keep-alive>
  <!--Tab切換: 每次切換新標簽的時候,Vue 都創建了一個新的 currentTabComponent 實例-->
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>
8. 異步組件
在大型應用中,可能需要將應用分割成小一些的代碼塊,并且只在需要的時候才從服務器加載一個模塊。為了簡化,Vue 允許以一個工廠函數的方式定義你的組件,這個工廠函數會異步解析你的組件定義。Vue 只有在這個組件需要被渲染的時候才會觸發該工廠函數,且會把結果緩存起來供未來重渲染。
示例1

Vue.component('async-webpack-example', function (resolve) {
  // 這個特殊的 `require` 語法將會告訴 webpack,自動將你的構建代碼切割成多個包,這些包會通過 Ajax 請求加載
  require(['./my-async-component'], resolve)
})
示例2

// 全局注冊
Vue.component(
  'async-webpack-example',
  // 這個 `import` 函數會返回一個 `Promise` 對象。
  () => import('./my-async-component')
)
// 局部注冊
new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component') //  webpack 2 和 ES2015 語法加在一起,異步加載“并不會被 Browserify 支持”
  }
})
示例3(僅作演示,模擬)

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回調傳遞組件定義
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})
異步組件工廠函數也可以返回一個如下格式的對象:

const AsyncComponent = () => ({
  // 需要加載的組件 (應該是一個 `Promise` 對象)
  component: import('./MyComponent.vue'),
  // 異步組件加載時使用的組件
  loading: LoadingComponent,
  // 加載失敗時使用的組件
  error: ErrorComponent,
  // 展示加載時組件的延時時間。默認值是 200 (毫秒)
  delay: 200,
  // 如果提供了超時時間且組件加載也超時了,
  // 則使用加載失敗時使用的組件。默認值是:`Infinity`
  timeout: 3000
})
9. render渲染函數
Vue的模板最終會被編譯成render渲染函數。
Vue推薦在絕大多數情況下使用模板來創建HTML,但在某些情況下使用渲染函數更高效。
  1. 舉例(render的好處)
<anchored-heading :level="1">Hello world!</anchored-heading>

使用模版(冗余)

<script type="text/x-template" id="anchored-heading-template">
  <h1 v-if="level === 1">
    <slot></slot>
  </h1>
  <h2 v-else-if="level === 2">
    <slot></slot>
  </h2>
  <h3 v-else-if="level === 3">
    <slot></slot>
  </h3>
  <h4 v-else-if="level === 4">
    <slot></slot>
  </h4>
  <h5 v-else-if="level === 5">
    <slot></slot>
  </h5>
  <h6 v-else-if="level === 6">
    <slot></slot>
  </h6>
</script>
Vue.component('anchored-heading', {
  template: '#anchored-heading-template',
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})

使用render函數

Vue.component('anchored-heading', {
  render: function (createElement) {
    // "虛擬 DOM”是對由 Vue 組件樹建立起來的整個 VNode 樹的稱呼
    return createElement(  // 虛擬節點 (virtual node)VNode
      'h' + this.level,   // 標簽名稱
      this.$slots.default // 子元素數組,子節點被存儲在組件實例中的 $slots.default 中
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})
  1. 組件樹中的所有 VNodes 必須是唯一的,以下的做法是錯誤的
render: function (createElement) {
  var myParagraphVNode = createElement('p', 'hi')
  return createElement('div', [
    // 錯誤-重復的 VNodes
    myParagraphVNode, myParagraphVNode
  ])
}

解決:使用工廠函數
render: function (createElement) {
  return createElement('div',
    Array.apply(null, { length: 20 }).map(function () {
      return createElement('p', 'hi')
    })
  )
}
  1. createElement 方法說明
// Vue通過建立一個虛擬 DOM 對真實 DOM 發生的變化保持追蹤。
// @returns {VNode}
createElement(
  // 1、{String | Object | Function} 必填項。
  // 一個 HTML 標簽字符串,或組件選項對象,或解析上述任何一種的一個 async 異步函數。
  'div',

  // 2、{Object}  可選參數。
  // 一個包含模板相關屬性的數據對象,可以在模版中使用這些特性。
  {
  },

  // 3、{String | Array}  可選參數。
  // 子虛擬節點 (VNodes),由 `createElement()` 構建而成,也可以使用字符串來生成“文本虛擬節點”。
  [
    // createElement('h1', '一則頭條'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)

createElement 第二個參數(數據對象)詳解

正如在模板語法中,v-bind:class 和 v-bind:style,會被特別對待一樣,在 VNode 數據對象中,下列屬性名是級別最高的字段。

{
  // 和`v-bind:class`一樣的 API,接收一個字符串、對象、數組(字符串和對象組成的)
  'class': {
    foo: true,
    bar: false
  },
  // 和`v-bind:style`一樣的 API,接收一個字符串、對象或對象組成的數組
  style: {
    color: 'red',
    fontSize: '14px'
  },
  // 普通的 HTML 特性
  attrs: {
    id: 'foo'
  },
  // 組件 props
  props: {
    myProp: 'bar'
  },
  // DOM 屬性
  domProps: {
    innerHTML: 'baz'
  },

  // 事件監聽器基于 `on`,但不再支持如 `v-on:keyup.enter` 修飾器,需要手動匹配 keyCode。
  on: {
    click: this.clickHandler
  },
  // 僅用于組件,用于監聽原生事件,而不是組件內部使用
  // `vm.$emit` 觸發的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },
  // 自定義指令。注意,無法對 `binding` 中的 `oldValue` 賦值,因為 Vue 已經自動進行了同步。
  directives: [
    {
      name: 'my-custom-directive',
      value: '2',
      expression: '1 + 1',
      arg: 'foo',
      modifiers: {
        bar: true
      }
    }
  ],
  // 作用域插槽格式
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },
  // 如果組件是其他組件的子組件,需為插槽指定名稱
  slot: 'name-of-slot',
  // 其他特殊頂層屬性
  key: 'myKey',
  ref: 'myRef',
  // 如果在渲染函數中向多個元素都應用了相同的 ref 名,
  // 那么 `$refs.myRef` 會變成一個數組。
  refInFor: true
}

完整示例

var getChildrenTextContent = function (children) {
  return children.map(function (node) {
    return node.children
      ? getChildrenTextContent(node.children)
      : node.text
  }).join('')
}

Vue.component('anchored-heading', {
  render: function (createElement) {
    // 創建 kebab-case 風格的 ID
    var headingId = getChildrenTextContent(this.$slots.default)
      .toLowerCase()
      .replace(/\W+/g, '-')
      .replace(/(^-|-$)/g, '')

    return createElement(
      'h' + this.level,
      [
        createElement('a', {
          attrs: {
            name: headingId,
            href: '#' + headingId
          }
        }, this.$slots.default)
      ]
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})
  1. javascript替換指令

v-if 和 v-for 在render函數里則使用 if、map語句 替換

<ul v-if="items.length">
  <li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>
...
props: ['items'],
render: function (createElement) {
  if (this.items.length) {
    return createElement('ul', this.items.map(function (item) {
      return createElement('li', item.name)
    }))
  } else {
    return createElement('p', 'No items found.')
  }
}

render 函數中沒有與 v-model 的直接對應 - 必須自己實現相應的邏輯:

props: ['value'],
render: function (createElement) {
  var self = this
  return createElement('input', {
    domProps: {
      value: self.value
    },
    on: {
      input: function (event) {
        self.$emit('input', event.target.value)
      }
    }
  })
}

事件修飾符

on
  .passive        &
  .capture        !
  .once           ~
  .capture.once   or
  .once.capture   ~!


舉例
  on: {
    '!click': this.doThisInCapturingMode,
    '~keyup': this.doThisOnce,
    '~!mouseover': this.doThisOnceInCapturingMode
  }
.stop   
  event.stopPropagation()

.prevent    
  event.preventDefault()

.self   
  if (event.target !== event.currentTarget) return

.enter, .13
  if (event.keyCode !== 13) return

.ctrl, .alt, .shift, .meta
  if (!event.ctrlKey) return (change ctrlKey to altKey, shiftKey, or metaKey, respectively)

示例
on: {
  keyup: function (event) {
    // 如果觸發事件的元素不是事件綁定的元素
    // 則返回
    if (event.target !== event.currentTarget) return
    // 如果按下去的不是 enter 鍵或者
    // 沒有同時按下 shift 鍵
    // 則返回
    if (!event.shiftKey || event.keyCode !== 13) return
    // 阻止 事件冒泡
    event.stopPropagation()
    // 阻止該元素默認的 keyup 事件
    event.preventDefault()
    // ...
  }
}
  1. 插槽
this.$slots.default 訪問靜態插槽的內容,得到的是一個 VNodes 數組.


render: function (createElement) {
  // `<div><slot></slot></div>`
  return createElement('div', this.$slots.default)
}
this.$scopedSlots.default訪問作用域插槽,得到的是一個返回 VNodes 的函數


props: ['message'],
render: function (createElement) {
  // `<div><slot :text="message"></slot></div>`
  return createElement('div', [
    this.$scopedSlots.default({
      text: this.message
    })
  ])
}

如果要用渲染函數向子組件中傳遞作用域插槽,可以利用 VNode 數據對象中的 scopedSlots 域
render: function (createElement) {
  return createElement('div', [
    createElement('child', {
      // 在數據對象中傳遞 `scopedSlots`
      // 格式:{ name: props => VNode | Array<VNode> }
      scopedSlots: {
        default: function (props) {
          return createElement('span', props.text)
        }
      }
    })
  ])
}
  1. JSX
使用Babel插件:用于在 Vue 中使用 JSX 語法,它可以讓我們回到更接近于模板的語法上。

import AnchoredHeading from './AnchoredHeading.vue'
...
new Vue({
  el: '#demo',
  render: function (h) {// 將 h 作為 createElement 的別名是 Vue 生態系統中的一個通用慣例,實際上也是 JSX 所要求的。從 Vue 的 Babel 插件的 3.4.0 版本開始,會在以 ES2015 語法聲明的含有 JSX 的任何方法和 getter 中 (不是函數或箭頭函數中) 自動注入 const h = this.$createElement,這樣就可以去掉 (h) 參數了。對于更早版本的插件,如果 h 在當前作用域中不可用,應用會拋錯。
    return (
      <AnchoredHeading level={1}>
        <span>Hello</span> world!
      </AnchoredHeading>
    )
  }
})
  1. 函數式組件(functional: true)
當使用函數式組件時,該引用將會是 HTMLElement,因為他們是無狀態的也是無實例的。
注意:在 2.3.0 之前的版本中,如果一個函數式組件想要接收 prop,則 props 選項是必須的。在 2.3.0 或以上的版本中,可以省略 props 選項,所有組件上的特性都會被自動隱式解析為 prop。

Vue.component('my-component', {
  functional: true,
  props: {
    // ...
  },
  // 為了彌補缺少的實例
  // 提供第二個參數作為上下文
  render: function (createElement, context) {
    // ...
  }
})
因為函數式組件只是一個函數,所以渲染開銷也低很多。
組件需要的一切都是通過上下文傳遞,包括
  props:提供所有 prop 的對象
  children: VNode 子節點的數組
  slots: 一個函數,返回了包含所有插槽的對象
  scopedSlots: (2.6.0+) 一個暴露傳入的作用域插槽以及函數形式的普通插槽的對象。
  data:傳遞給組件的數據對象   
  parent:對父組件的引用
  listeners: (2.3.0+) 一個包含了所有在父組件上注冊的事件偵聽器的對象。這只是一個指向 `data.on`的別名。
  injections: (2.3.0+) 如果使用了inject選項,則該對象包含了應當被注入的屬性。

this.$slots.default 更新為 context.children,
this.level 更新為 context.props.level

向子元素或子組件傳遞數據

在普通組件中,沒有被定義為 prop 的特性會自動添加到組件的根元素上,將現有的同名特性替換或與其合并。
然而函數式組件要求顯式定義該行為:

Vue.component('my-functional-button', {
  functional: true,
  render: function (createElement, context) {
    // 完全透明的傳入任何特性、事件監聽器、子結點等。
    return createElement('button', context.data, context.children)
  }
})


如果使用基于模板的函數式組件,那么還需要手動添加特性和監聽器。因為我們可以訪問到其獨立的上下文內容,所以可以使用 data.attrs 傳遞任何 HTML 特性,也可以使用 listeners (即 data.on 的別名) 傳遞任何事件監聽器。
<template functional>
  <button
    class="btn btn-primary"
    v-bind="data.attrs"
    v-on="listeners"
  >
    <slot/>
  </button>
</template>

示例(根據傳入 prop 的值來代為渲染更具體的組件)

var EmptyList = { /* ... */ }
var TableList = { /* ... */ }
var OrderedList = { /* ... */ }
var UnorderedList = { /* ... */ }

Vue.component('smart-list', {
  functional: true,
  props: {
    items: {
      type: Array,
      required: true
    },
    isOrdered: Boolean
  },
  render: function (createElement, context) {
    function appropriateListComponent () {
      var items = context.props.items

      if (items.length === 0)           return EmptyList
      if (typeof items[0] === 'object') return TableList
      if (context.props.isOrdered)      return OrderedList

      return UnorderedList
    }

    return createElement(
      appropriateListComponent(),
      context.data,
      context.children
    )
  }
})

slots().default 和 children 對比

<my-functional-component>
  <p v-slot:foo>
    first
  </p>
  <p>second</p>
</my-functional-component>

對于這個組件,children 會給你兩個段落標簽,而 slots().default 只會傳遞第二個匿名段落標簽,slots().foo 會傳遞第一個具名段落標簽。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 一、了解Vue.js 1.1.1 Vue.js是什么? 簡單小巧、漸進式、功能強大的技術棧 1.1.2 為什么學習...
    蔡華鵬閱讀 3,369評論 0 3
  • 什么是組件? 組件 (Component) 是 Vue.js 最強大的功能之一。組件可以擴展 HTML 元素,封裝...
    youins閱讀 9,522評論 0 13
  • 前幾天想學學Vue中怎么編寫可復用的組件,提到要對Vue的render函數有所了解。可仔細一想,對于Vue的ren...
    kangaroo_v閱讀 116,120評論 13 171
  • 本文章是我最近在公司的一場內部分享的內容。我有個習慣就是每次分享都會先將要分享的內容寫成文章。所以這個文集也是用來...
    Awey閱讀 9,463評論 4 67
  • 本章內容:表單 與 v-model、組件、自定義指令 六、表單 與 v-model 6.1、基本用法 Vue.js...
    了凡和纖風閱讀 929評論 1 2