Vue 元件的通訊

_zysndy發表於2018-03-09

元件定義

// 全域性定義
Vue.component('my-lists', {
    // options
})
複製程式碼

定義後,可以在template模版中使用<my-list></mylist>,此時會將元件中定義的內容顯示出來。

<template>
    <div>
        <my-list></mylist>
    </div>
</template>
複製程式碼

通過例子來看一下

<body>
    <div id='app'>
        <my-list></my-list>
    </div>
    
</body>
<script>
    Vue.component('my-list', {
        template: `
            <ul>
                <li>上海</li>
                <li>北京</li>
                <li>南京</li>
            </ul>
        `
    })
    new Vue({
        el: '#app'
    })

</script>
複製程式碼

頁面會顯示出我們定義的my-list的元件內容,``這個符號是ES6中的模版字串,想詳細瞭解的話,可以看MDN上關於模版字串的說明

如果不需要全域性定義的話,那可以在Vue例項中區域性定義

var myList = {
    template: `
        <ul>
            <li>上海</li>
            <li>北京</li>
            <li>南京</li>
        </ul>
    `
}
new Vue({
    el: '#app', // 掛載
    components: {
        'my-list': myList
    }
})
複製程式碼

元件內的data

上面是簡單的元件,如果我們想自己通過變數,來控制列表的展示,這個時候,我們元件內部定義data。我們來重新寫my-list元件:

var citys = ['上海', '北京', '廣州']
Vue.component('my-list', {
    template: `
        <ul>
            <li v-for='(item, index) in citys' :key='index'>{{item}}</li>
        </ul>
    `,
    data () {
        return {
            citys: citys
        }
    }
})
new Vue({
    el: '#app'
})
複製程式碼

有一點要注意,data必須是一個函式,將值通過函式返回。如果是一個變數:

var citys = ['上海', '北京', '廣州']
Vue.component('my-list', {
    template: `
        <ul>
            <li v-for='(item, index) in citys' :key='index'>{{item}}</li>
        </ul>
    `,
    data: {
        citys: citys
    }
})

複製程式碼

會導致一個問題,多個相同元件同時使用時,它們的data是共享的,會出現相互影響的情況。類似於淺拷貝的這種情況

var a = {name: 'a'}
var b = a
var c = a

console.log(b.name, c.name) // a a

console.log(b.name, c.name) // a a

b.name = 'b'
console.log(b.name) // b
console.log(c.name) // b

// b是copy a的引用,所以b改變了的同時會影響a
// 元件內的data同理,所以將data的值以函式返回,以建立不同的物件,而不是共享一個

var a = function () {
    return {
        name: 'a'
    }
}

var b = a() // b是新物件
var c = a() // c也是新物件

b.name = 1
console.log(c.name) // a

複製程式碼

父子元件的通訊

  1. 父元件通過傳遞Props給子元件
  2. 子元件獲取到父元件傳遞的值,執行一些列操作後,通過 emit 觸發事件,告訴父元件發生了什麼
  3. 父元件可以見通過v-on/@監聽這個事件

以上述的my-list為例子,實現當點選城市名稱時,會在下方顯示點選的城市:

<body>
    <div id='app'>
        <my-list 
            :citys='citys'
            @select='getCity'></my-list>

        <p>{{selCity}}</p>
    </div>
    
</body>
<script>
Vue.component('my-list', {
    template: `
        <ul>
            <li 
                v-for='(item, index) in citys' 
                :key='index'
                @click='select(item)'>{{item}}</li>
        </ul>
    `,
    props: ['citys'],
    methods: {
        select (city) {
            this.$emit('select', city)
        }
    }
})
new Vue({
    el: '#app',
    data () {
        return {
            citys: ['上海', '北京', '廣州'],
            selCity: ''
        }
    },
    methods: {
        getCity (city) {
            this.selCity = city
        }
    }
})
</script>
複製程式碼
  • :citys 動態繫結props的使用者,:v-bind的縮寫;如果傳遞的是一個靜態的值,則可以直接寫成citys="['上海','北京']"
  • @select 父元件監聽子元件觸發的事件this.$emit('select', city); @v-on的縮寫;如果子元件觸發了select,會響應父元件的'getCity'函式

我們需要注意的是,父子元件通訊是單向的,即父元件傳遞給子元件的props發生變化時,子元件也會接收到這種變化,但是如果子元件改變props的值時,不會反饋給父元件,防止修改了父元件的狀態,導致整個的資料流難以理解,如果修改了,Vue也會發出警告。 props對於子元件來說應該是隻讀的。有兩種情況下,我們會想改變props:

  • props傳遞進來後,我們需要對props進行處理,然後輸出到頁面上,這種情況下,可以通過computed來計算處理後的變數
    computed: {
        newCitys () {
            this.citys.filter((ele) => {
                return ele !== '上海'
            }) 
        }
    }
複製程式碼
  • 作為一個初始值,後續需要對其進行修改,計算等;這種情況下,可以將props賦值給自身的屬性
data () {
    return {
        initCitys: this.citys
    }
}

複製程式碼

如果我們想修改props的值,並將變化反饋給父元件,除了$emit觸發事件 + 父元件監聽事件這種方式外,還有一種較為簡單的方式,通過sync操作符

// 父元件
<child-component :name.sync='name'></child>

// 子元件想要能夠修改name,可以這麼做
this.$emit('update:name', 'newVal')

複製程式碼

上述我們是直接傳遞props,我們可以在子元件中通過定義props格式來對props進行簡單的驗證,以下一段驗證程式碼來自官網,允許偷個懶= _=||

Vue.component('example', {
  props: {
    // 基礎型別檢測 (`null` 指允許任何型別)
    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
      }
    }
  }
})
複製程式碼

多個同級元件間的通訊

在複雜專案中,可能會出現多個同級的元件需要通訊,而僅僅是簡單的父子元件通訊,這個時候怎麼去做呢?

通過新建一個Vue例項,作為事件匯流排

var emitEvent = new Vue()

// 在不同的元件中,通過觸發事件,監聽事件來實現通訊
emitEvent.$emit('event-name', val)
emitEvent.$on('event-name', funciton (val) {

})

複製程式碼

統一狀態管理,可以在專案中使用Vuex

Vuex 官網

Vuex 是一個專門為Vue.js開發狀態管理軟體。 Vuex中有幾個核心的概念

  • State 儲存狀態,類似於data屬性

  • Getter 從state中派生出一些屬性,類似於computed

  • Mutation 更改state的屬性,state屬性是無法直接通過賦值進行更改的,需要通過Mutation定義的函式來進行賦值,提交的是state

  • Actions Mutation必須是同步的函式,所以在Actions中處理非同步的操作,並且提交的是Mutation

  • Module 當專案很大的時候,需要根據不同的功能分割成不同的模組,每次需要的時候,只需要引用需要的模組即可

比如一個專案中使用Vuex,首先安裝Vuex npm install vuex --save,通過腳手架vue-cli,一般預設自帶Vuex

Vuex.store的目錄結構可以是這樣的

store
  - index.js  // 統一模組的index
  - modules
      - a.js // 模組 a
      - b.js // 模組 b

複製程式碼

store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

import a from './modules/a'
import b from './modules/b'

Vue.use(Vuex)

export default new Vuex.Store({
  modules: {
    a,
    b
  }
})

複製程式碼

store/modules/a.js

export default {
  namespaced: true, // 必要的
  state: {
    data: null
  },
  mutations: {
    setData(state, data) {
        state.data = data
    }
  },
  actions: {
      getData (context, cb) {
        // 假設執行的從服務端獲取的資料
        http.post(url, {name: 'b'}, function (res) {
            // 提交mutation操作
            context.commit('setData', res)

            // 如果有回撥函式的話
            typeof cb === 'funciton' && cb(res)
        })
      }
      
  
  }
}
複製程式碼

store/modules.b.js 類似於a,在此就不舉例子了。

在元件中如何是使用呢?

首先匯入Vuex提供的方法

// components/a.vue

// 首先,確定需要哪些功能
// 如果只需要state中的資料
import { mapState } from 'vuex'

// 如果只需要state中的資料 和 mutations操作
import { mapState, mapMutations } from 'vuex'

// 如果還對Actions有需求
import { mapState, mapMutations, mapActions } from 'vuex'
// 需要什麼匯入什麼即可

// 小tips: 可以先寫 from 'vuex',再寫{}中的,編輯器會自動不全
複製程式碼

然後匯入我們在Store中定義的屬性和方法

# 使用 a 模組,並且把a模組中的data賦值給aData
computed: {
    ...mapState('a', {
        aData: state => state.data
    })
    ...mapGetters('a', {
        xxx: state => state.xxx
    })
}

# 匯入方法
methods: {
    ...mapMutations('a', [
      'setData'
    ])

    ...mapActions('a', [
      'getData'
    ])
}
複製程式碼

使用屬性和方法

// 屬性和方法通過this呼叫
this.aData

// 重新賦值
this.setData('aaa')

// 傳入回撥函式
this.getData(function (res) {
    console.log(res)
})
複製程式碼

上面的程式碼中我們使用... Javascript的擴充套件操作符和 aData: state => state.data的箭頭函式,如果對這兩項有不瞭解的,可以查閱連結

擴充套件操作符

箭頭函式

感謝,如果有什麼寫的不好的地方,請聯絡作者,比心~

相關文章