1. 程式人生 > >vue.js進階之元件

vue.js進階之元件

因為之前的專案用了vue,但是是邊學邊用,很多細節都不熟悉,比如vue-router,之前也寫過vue+browserify構建大型應用。這次寫一個vue最強大的功能,就是vue的元件。
首先推薦一個部落格,真的寫得非常好,當然官方API也寫得很好。
http://www.cnblogs.com/keepfool/p/5625583.html

vue元件基本步驟

這個圖也是盜用這個部落格的,所以先宣告一下,我算是轉載吧,哈哈。
步驟:
1 、建立元件構造器
2、 註冊元件
3、在Vue例項中使用元件
這裡寫圖片描述
一個小小的demo來說明一下,這個就是官方的例子:

<div
id="example"> <my-component></my-component> </div>
// 定義
var MyComponent = Vue.extend({
  template: '<div>A custom component!</div>'
})

// 註冊
Vue.component('my-component', MyComponent)

// 建立根例項
new Vue({
  el: '#example'
})
<div id="example">
  <div>A custom component!</div
> </div>

理解

  1. Vue.extend()是Vue構造器的擴充套件,呼叫Vue.extend()建立的是一個元件構造器。
  2. Vue.extend()構造器有一個選項物件,選項物件的template屬性用於定義元件要渲染的HTML。
  3. 使用Vue.component()註冊元件時,需要提供2個引數,第1個引數時元件的標籤,第2個引數是元件構造器。
  4. 元件應該掛載到某個Vue例項下,否則它不會生效。

屬性值的傳遞

元件例項的作用域是孤立的。這意味著不能並且不應該在子元件的模板內直接引用父元件的資料。可以使用 props 把資料傳給子元件。

Vue.component('child'
, { // 宣告 props props: ['message'], // 就像 data 一樣,prop 可以用在模板內 // 同樣也可以在 vm 例項中像 “this.message” 這樣使用 template: '<span>{{ message }}</span>' })
<child message="hello!"></child>

官網的例子。
然而如何繫結到style和id呢?

data和props

1、data

使用元件Components時,大多數選項可以被傳入到 Vue 構造器中,有一個例外: data 必須是函式。因為如果不是函式的,宣告多個元件的時候,他們共享的就是同一個data,這樣就會亂掉。如果通過函式返回,那麼每個元件維持自己的data作用域。該data屬性只在其component中可見。

<body>
    <div id="app">
        <my-component>
        </my-component>
    </div>
    <template id="myComponent">
        <div>
            <h2>{{msg}}</h2>
            <button @click="showMsg">Show Message</button>
        </div>
    </template>
</body>
<script type="text/javascript" src="../dist/vue.js"></script>
<script type="text/javascript" src="../dist/vue-router.js"></script>
<script type="text/javascript">
new Vue({
    el: '#app',
    components: {
        'my-component': {
            template: '#myComponent',
            data: function() {
                return {
                    msg: 'This is a Component!' //Vue中component的data必須通過function() return
                }
            },
            methods: {
                showMsg: function() {
                    alert(this.msg);
                }
            }
        }
    }
})
</script>

Component其他感覺和之前用的Vue沒什麼區別。
也可以有methods方法,有data,多了props

2、關於元件的作用域

template不是標準的HTML元素,瀏覽器是不理解這個元素的。
這裡寫圖片描述
這裡還可以看到template

那麼Vue是如何讓瀏覽器理解template標籤的呢:
感覺必須是掛載在Vue中,通過Vue解析出這個標籤成為瀏覽器可以理解的元素。
就像下面這段程式碼,必須新new 一個Vue,Vue使用myComponent作為components,然後在Vue繫結的app中,這個component就會被解析成瀏覽器可以閱讀的語言。

new Vue({
    el: '#app',
    components: {
        'my-component': {
            template: '#myComponent',
            data: function() {
                return {
                    msg: 'This is a Component!' //Vue中component的data必須通過function() return
                }
            },
            methods: {
                showMsg: function() {
                    alert(this.msg);
                }
            }
        }
    }
})

並且vue例項和component的作用域是獨立的

new Vue({
    el: '#app',
    data:{
        display:true //vue例項的display
    },
    components: {
        'my-component': {
            template: '#myComponent',
            data: function() {
                return {
                    msg: 'This is a Component!',
                    display: false //component中的display
                }
            },
            methods: {
                showMsg: function() {
                    alert(this.msg);
                }
            }
        }
    }
})
 <div id="app">
        <my-component v-show="display">
        </my-component>
    </div>
    <template id="myComponent">
        <div>
            <h2 v-show="display">{{msg}}</h2>
            <button @click="showMsg">Show Message</button>
        </div>
    </template>

執行結果:

這裡寫圖片描述
h2被隱藏了,
但是my-component沒有被隱藏。
也就是說:父元件模板的內容在父元件作用域內編譯;子元件模板的內容在子元件作用域內編譯
那麼父子元件如何進行通訊呢?
答案是props

3、父元件和子元件通訊

官網的一張圖
這裡寫圖片描述
父元件通過* props* 向下傳遞資料給子元件,子元件通過 events 給父元件傳送訊息。看看它們是怎麼工作的。

a. 父元素向子元素通訊:props

還是剛剛的例子,我們在component中添加了props
例子1:靜態props

new Vue({
    el: '#app',
    data:{
        display:true
    },
    components: {
        'my-component': {
            template: '#myComponent',
            props:['parentmsg'], //宣告props
            data: function() {
                return {
                    msg: 'This is a Component!',
                    display: false //Vue中component的data必須通過function() return
                }
            },
            methods: {
                showMsg: function() {
                    alert(this.msg);
                }
            }
        }
    }
})

我們在template中加了一個p,這個p引用父元素的msg,另外說一聲Vue中屬性繫結這個不識別大小寫

<my-component v-show="display" parentMsg="ParentMsg">

在瀏覽器看到就是

<my-component v-show="display" parentmsg="ParentMsg">

所以即使你聲明瞭props:[‘parentMsg’]也顯示不出來。我的程式碼是這樣

後來看到官方的解釋是這樣的:

HTML 特性不區分大小寫。當使用非字串模版時,prop的名字形式會從 camelCase 轉為
kebab-case(短橫線隔開)

 <div id="app">
        <my-component v-show="display" parentmsg="ParentMsg">
        </my-component>
    </div>
    <template id="myComponent">
        <div>
            <h2 v-show="display">{{msg}}</h2>
            <p>{{parentmsg}}</p> 
            <button @click="showMsg">Show Message</button>
        </div>
    </template>

執行結果
這裡寫圖片描述
這裡就是父元素的ParentMsg傳遞給了component

例子2:動態props
感覺神奇之處就在這裡,用v-bind把剛剛的props繫結起來

<input type="" name="" v-model="ParentMsg">
        <my-component v-show="display" v-bind:parentmsg="ParentMsg">
        </my-component>
    </div>

這樣ParentMsg就根據輸入框中的ParentMsg動態變化了。

new Vue({
    el: '#app',
    data:{
        display:true,
        ParentMsg:"Hello This is Parent"
    },
    components: {
        'my-component': {
            template: '#myComponent',
            props:['parentmsg'],
            data: function() {
                return {
                    msg: 'This is a Component!',
                    display: false //Vue中component的data必須通過function() return
                }
            },
            methods: {
                showMsg: function() {
                    alert(this.msg);
                }
            }
        }
    }
})

prop 是單向繫結的:當父元件的屬性變化時,將傳導給子元件,但是不會反過來。這是為了防止子元件無意修改了父元件的狀態——這會讓應用的資料流難以理解。

另外,每次父元件更新時,子元件的所有 prop 都會更新為最新值。這意味著你不應該在子元件內部改變 prop 。如果你這麼做了,Vue 會在控制檯給出警告。

通常有兩種改變 prop 的情況:

  1. prop 作為初始值傳入,子元件之後只是將它的初始值作為本地資料的初始值使用;

  2. prop 作為需要被轉變的原始值傳入。

 <div id="app">
    <input type="" name="" v-model="ParentMsg">
        <my-component v-show="display" v-bind:parentmsg="ParentMsg">
        </my-component>
    </div>
    <template id="myComponent">
        <div>
            <h2 v-show="display">{{msg}}</h2>
            <p>{{parentmsg}}</p>
            <p>{{childprops}}</p>
            <button @click="showMsg">Show Message</button>
        </div>
    </template>
new Vue({
    el: '#app',
    data:{
        display:true,
        ParentMsg:"Hello This is Parent"
    },
    components: {
        'my-component': {
            template: '#myComponent',
            props:['parentmsg'],
            data: function() {
                return {
                    msg: 'This is a Component!',
                    childprops:"child:"+this.parentmsg, //可以在data中獲取props,並生成新的data
                    display: false //Vue中component的data必須通過function() return
                }
            },
            methods: {
                showMsg: function() {
                    alert(this.msg);
                }
            }
        }
    }
})

a. 子元素向父元素傳遞資訊:自定義事件

我們知道,父元件是使用 props 傳遞資料給子元件,但如果子元件要把資料傳遞回去,應該怎樣做?那就是自定義事件!

使用 $on(eventName) 監聽事件 
使用 $emit(eventName) 觸發事件
 <button-counter v-on:increment="incrementTotal"></button-counter>
Vue.component('button-counter', {
    template: '<button v-on:click="increment">{{counter}}</button>',
    data: function() {
        return {
            counter: 0
        }
    },
    methods: {
        increment: function() {
            alert("increment")
            this.counter += 1;
            this.$emit('increment');
        }
    }
})
new Vue({
    el: '#app',
    data: {
        display: true,
        ParentMsg: "Hello This is Parent",
        total: 0
    },
    methods: {
        incrementTotal: function() {
            alert("incrementTotal")
            this.total += 1;
        }
    },
})

先執行increment,再執行incrementTotal
感覺這兩句是非常重要的:

v-on:increment="incrementTotal"
this.$emit('increment');

分析,button上面綁定了v-on:click=”increment”,當點選按鈕觸發increment事件,當increment函式執行完畢,觸發incrementTotal函式。全在於 this.$emit(‘increment’); 不然執行完畢increment就完畢了。
官網的例子:

<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="increment">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    increment: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
})
new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
})

這裡寫圖片描述
這樣點選任意一個button都會呼叫 incrementTotal。

input v-model="something">

其實v-model僅僅是一顆語法糖

<input v-bind:value="something" v-on:input="something = $event.target.value">

所以要讓元件的 v-model 生效,它必須:

1.接受一個 value 屬性
2. 在有新的 value 時觸發 input 事件

<div id="app2">
        <currency-input label="Price" v-model="price"></currency-input>  
        <currency-input label="Shipping" v-model="shipping"></currency-input>
        <currency-input label="Handling" v-model="handling"></currency-input>
        <currency-input label="Discount" v-model="discount"></currency-input>
        <p>Total: ${{ total }}</p>
    </div>
    <template id="myComponent">
        <div>
            <h2 v-show="display">{{msg}}</h2>
            <p>{{parentmsg}}</p>
            <p>{{childprops}}</p>
            <button @click="showMsg">Show Message</button>
        </div>
    </template>
Vue.component('currency-input', {
  template: '\
    <div>\
      <label v-if="label">{{ label }}</label>\
      $\
      <input\
        ref="input"\
        v-bind:value="value"\
        v-on:input="updateValue($event.target.value)"\
        v-on:focus="selectAll"\
      >\
    </div>\
  ',
  props: {
    value: {
      // type: Number,
      default: 0
    },
    label: {
      // type: String,
      default: ''
    }
  },
  methods: {
    updateValue: function (value) {
      this.$emit('input', value) //觸發input事件
    },
    selectAll: function (event) {
      setTimeout(function () {
        event.target.select()
      }, 0)
    }
  }
})

new Vue({
  el: '#app2',
  data: {
    price: 0,
    shipping: 0,
    handling: 0,
    discount: 0
  },
  computed: {
    total: function () {
      return ((
        this.price * 100 + 
        this.shipping * 100 + 
        this.handling * 100 - 
        this.discount * 100
      ) / 100).toFixed(2)
    }
  }
})

使用slot