
Vue的組件化給前端開發(fā)帶來極大的便利,這種依賴數(shù)據(jù)來控制Dom的模式,區(qū)別于以前的開發(fā)控制Dom的開發(fā)理念,這也導(dǎo)致了一種情況,在Vue中是單向數(shù)據(jù)流的,意味著只能從父組件向子組件傳值,不允許子組件向父組件傳值。
這樣會防止從子組件意外改變父級組件的狀態(tài),從而導(dǎo)致你的應(yīng)用的數(shù)據(jù)流向難以理解。 ---vue教程
然而當(dāng)我們把組件拆分到足夠細(xì)的時候,子組件控制父組件的數(shù)據(jù),或者兄弟組件之間的傳值就變得尤為突出,這里我將總結(jié)各式各樣的傳值,函數(shù)調(diào)用的方法。
父組件中的通信方法
閱讀完官方文檔后,我們一定會對props有強烈的印象,然而在父組件中可不止有這種通信的方式。
| 序號 | 方法名 | 概要 | 推薦程度 |
|---|---|---|---|
| 1 | props | 通過對子組件的v-bind綁定或標(biāo)簽屬性值進行傳值 | 強烈推薦 |
| 2 | provide/inject | 通過注入的方式,以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深,并在起上下游關(guān)系成立的時間里始終生效。 | 謹(jǐn)慎使用 |
| 3 | vm.$children | 通過查詢當(dāng)前父級的子組件獲得子組件的數(shù)據(jù)與方法,并不保證順序,也不是響應(yīng)式的
|
謹(jǐn)慎使用 |
| 4 | vm.$slots | 插槽<slot></slot>或者用vm.$slots獲取 | 推薦 |
props
-
命名規(guī)范
HTML 中的特性名是大小寫不敏感的,所以瀏覽器會把所有大寫字符解釋為小寫字符。這意味著當(dāng)你使用 DOM 中的模板時,camelCase (駝峰命名法) 的 prop 名需要使用其等價的 kebab-case (短橫線分隔命名) 命名:
<blog-post post-title="hello!"></blog-post>
Vue.component('blog-post', {
// 在 JavaScript 中是 camelCase 的
props: ['postTitle'],
template: '<h3>{{ postTitle }}</h3>'
})
- 傳遞靜態(tài)或動態(tài) Prop
假如你想傳字符串給子組件只需要給便簽添加屬性就可以了
//這里的title的內(nèi)容就會傳給子組件
<blog-post title="My journey with Vue"></blog-post>
當(dāng)然那些非字符串的類型就直接依賴于v-bind進行傳值(Number、Boolean、Array...)
<!-- 動態(tài)賦予一個變量的值 -->
<blog-post v-bind:title="post.title"></blog-post>
<!-- 動態(tài)賦予一個復(fù)雜表達式的值 -->
<blog-post
v-bind:title="post.title + ' by ' + post.author.name"
></blog-post>
-
子組件接收
常見的就是聲明式接收:
props: [...] 數(shù)組形式
// 定義一個名為 button-counter 的新組件
Vue.component('button-counter', {
props: ['title', 'count', 'isPublished', 'commentIds', 'author'],
data: function () {
return {
counts: this.count //this指向即可獲取props值
}
},
template: ''
})
但官方更推薦對象形式的接收,至少為每個prop指定類型
Vue.component('my-component', {
props: {
// 基礎(chǔ)的類型檢查 (`null` 和 `undefined` 會通過任何類型驗證)
propA: Number,
// 多個可能的類型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 帶有默認(rèn)值的數(shù)字
propD: {
type: Number,
default: 100
},
// 帶有默認(rèn)值的對象
propE: {
type: Object,
// 對象或數(shù)組默認(rèn)值必須從一個工廠函數(shù)獲取
default: function () {
return { message: 'hello' }
}
},
// 自定義驗證函數(shù)
propF: {
validator: function (value) {
// 這個值必須匹配下列字符串中的一個
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
type 可以是下列原生構(gòu)造函數(shù)中的一個:
StringNumberBooleanArrayObjectDateFunctionSymbol
使用上我們除了this.prop外還可以使vm.$props(當(dāng)前組件接收到的 props 對象。Vue 實例代理了對其 props 對象屬性的訪問。)
-
禁用特性繼承
如果你不希望組件的根元素繼承特性,這尤其適合配合實例的 $attrs 屬性使用,你可以在組件的選項中設(shè)置 :
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
當(dāng)然還有非聲明式的接收 $attrs (2.4.0 新增):
包含了父作用域中不作為 prop 被識別 (且獲取) 的特性綁定 (class 和 style 除外)。當(dāng)一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外),并且可以通過 v-bind="$attrs" 傳入內(nèi)部組件——在創(chuàng)建高級別的組件時非常有用。
<base-input label="姓名" class="username-input"
placeholder="Enter your username" data-date-picker="activated"
></base-input>
Vue.component("base-input", {
inheritAttrs: false, //此處設(shè)置禁用繼承特性
props: ["label"],
template: `
<label>
{{label}}
{{$attrs.placeholder}} //這里不用聲明props可以直接調(diào)用
{{$attrs["data-date-picker"]}}
<input v-bind="$attrs"/>
</label>
`,
mounted: function() {
console.log(this.$attrs);
}
})
provide/inject
provide 和 inject 主要為高階插件/組件庫提供用例。并不推薦直接用于應(yīng)用程序代碼中。
// 父級組件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子組件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
提示:provide 和 inject 綁定并不是可響應(yīng)的。這是刻意為之的。然而,如果你傳入了一個可監(jiān)聽的對象,那么其對象的屬性還是可響應(yīng)的。
更多的用法是用來注入方法的:
// 父級組件提供 'foo'
var Provider = {
provide: {
foo: this.fn
},
methods:{
fn(){
console.log('bar')
}
}
// ...
}
// 子組件注入 'foo'
var Child = {
inject: ['foo'],
created () {
this.foo(); // => "bar"
}
// ...
}
vm.$children
通過Vue實例代理的$children獲取其子組件的值,為一個類數(shù)組,你可以在控制臺中打印出來,里面有子組件中的一切屬性。
vm.$slots
插槽的使用更多在官方文檔中查看,這里顯示基礎(chǔ)的使用方法:
<navigation-link url="/profile">
Your Profile
</navigation-link>
然后你在 <navigation-link> 的模板中可能會寫為:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
除了用標(biāo)簽獲取傳遞的數(shù)據(jù),我更喜歡用$slots處理,比如在這個輸入框中,你既要設(shè)定lable又要設(shè)定placeholder,然而它們兩其實是同一個數(shù)據(jù),這時候我們可以這樣:

<inputBox :readonly='readonly' v-model='item.contactName'>聯(lián)系人</inputBox>
<inputBox :readonly='readonly' v-model='item.mobile'>電話</inputBox>
var Component = {
props: {
readonly: {
default: false
},
type: {
default: 'text'
},
value: [String, Number],
maxlength: {
default: 20
}
},
data() {
return {
inputValue: this.value
}
},
watch: {
value(newValue) {
this.inputValue = newValue
},
inputValue(newValue) {
this.$emit('input', newValue)
}
},
template: `
<div class='input'>
<cube-input v-model.trim="inputValue" :placeholder="'請輸入' + this.$slots.default[0].text" :readonly="readonly" :type="type" :maxlength="maxlength">
<template v-slot:prepend>
<slot></slot>
</template>
</cube-input>
</div>
`
};
其他的為ui框架的組件不用在意,重要的是this.$slots.default[0].text這一段對$slots的應(yīng)用。
小結(jié)
通過上面的四種方法,我們可以看出,它們都是可以完成值得傳遞或者獲取的,除了$slots不能傳遞方法函數(shù)外,其他三種均可完成。
子組件中的通信方法
首先我們先要理解子組件傳遞的原理,下面這張圖大家在很多的地方都看過了,實際上就是父組件通過prop給子組件下發(fā)數(shù)據(jù),子組件通過事件給父組件發(fā)送信息,而使用的工具為:

$on(evntName)監(jiān)聽事件;
$emit(eventName,optionalPayload)觸發(fā)事件;
接下來的方法都是對這個原理實現(xiàn)的變形
| 序號 | 方法名 | 概要 | 推薦程度 |
|---|---|---|---|
| 1 | v-on/vm.$emit | 通過綁定父級函數(shù),用子級觸發(fā),父子組件雙向綁定 | 推薦 |
| 2 | v-model/vm.$emit | 官方提供的父子組件雙向綁定方法 | 強烈推薦 |
| 3 | this.$parent | 獲取直接上級的實例,調(diào)用父組件的函數(shù) | 推薦 |
v-on / $emit
首先我們來實現(xiàn)一個單向傳遞的例子:
//父組件
<template>
<child @childHandler="parentHandle"></child>
</template>
<script>
export default {
data: {
message: ''
},
methods: {
parentHandle(send) {
this.message = send;
}
}
}
</script>
//子組件
<template>
<button @cilck="sendHandle"></button>
</template>
<script>
export default {
date: {
news:"from children"
},
methods: {
sendHandle() {
this.$emit('childHandler',this.news);
}
}
}
</script>
上面的過程我就不再描述一遍了,你們自己看代碼理解理解,這樣我們就實現(xiàn)了由子組件傳遞數(shù)據(jù)到父組件。但在更復(fù)雜的場景時,我們不僅需要修改父組件的值,還需要通過父組件的值影響到子組件的渲染,也就是實現(xiàn)子父組件的數(shù)據(jù)雙向綁定。
更多的場景下我們希望其是自動觸發(fā)數(shù)據(jù)交互的,這時我們需要用到watch,現(xiàn)在我們對上面的代碼進行修改一下。
//父組件
<template>
<child @childHandler="parentHandle" :status="status" :name="name">
<button @cilck="emptyHandle">清空</button>
</child>
</template>
<script>
export default {
data: {
status:'男',
name:'Max.Law'
},
methods: {
parentHandle(send) {
this.child.status = send;
},
emptyHandle(){
status = '';
}
}
}
</script>
//子組件
<template>
<p><span>姓名:</span>{{name}}</p>
<p><span>性別:</span>{{childStatus}}</p>
<button @cilck="sendHandle('男')">男</button>
<button @cilck="sendHandle('女')">女</button>
</template>
<script>
export default {
props: {
status: [String, Number],
name: [String, Number]
},
date: {
childStatus:this.status
},
methods: {
sendHandle(data) {
this.childStatus = data
},
watch: {
status(newValue) {
this.childStatus = newValue
},
childStatus(newValue) {
this.$emit('childHandler', newValue)
}
},
}
</script>
這里實現(xiàn)的是在子組件的性別狀態(tài)繼承于父組件,子組件修改性別的狀態(tài)的同時改變父組件的數(shù)據(jù)以便父組件使用。
我們在使用vue的過程中很清楚,假如子組件直接寫{{status}}來繼承父組件的值,在修改狀態(tài)的時候vue會提示不建議直接修改父組件的值來改變子組件(vue中一個重要邏輯,當(dāng)前組件只處理當(dāng)前組件的數(shù)據(jù)),所以我們使用childStatus來接收父組件的值。
那我們?nèi)绾巫龅阶咏M件的改變能影響父組件,父組件更新值時又能影響子組件呢?這時候重點都在這個watch上:
當(dāng)status改變時則改變childStatus的值,
當(dāng)childStatus改變時,用上面的方法與父組件通信,改變父組件的值
這樣我們就完成子父組件的數(shù)據(jù)雙向綁定,整個過程為:
- 給子組件幫一個通信方法
- 綁定一個傳值對象
- 監(jiān)控數(shù)據(jù)的變化
看到這里有沒有熟悉的感覺,是不是跟 v-modle 很像,vue提供的雙向綁定的指令,而 v-modle 的本質(zhì)就是綁定了一個input的方法,和一個value值,這時候我們就能把上面的雙向數(shù)據(jù)綁定的方法簡化了(v-modle是在本組件內(nèi)實現(xiàn)雙向綁定,并沒有做到子父組件雙向綁定)
v-model/vm.$emit
還記得我上面講$slot的代碼嗎?這里我們簡化一下:
//父組件
<template>
<inputBox :readonly='readonly' v-model='brand'>設(shè)備品牌</inputBox>
</template>
<script>
export default {
data: {
brand:'默認(rèn)'
}
}
</script>
//子組件
<template>
<div>
<h1><slot></slot></h1>
<input v-model="inputValue"></input>
</div>
</template>
<script>
export default {
props: {
value: [String, Number]
},
data() {
inputValue: this.value
},
watch: {
value(newValue) {
this.inputValue = newValue
},
inputValue(newValue) {
this.$emit('input', newValue)
}
}
}
</script>
這時候,我們不僅可以修改子組件中的輸入框值,還是能同時改變父組件的值,這在做上傳表單的時候尤為重要,保證我們父組件一直都是獲取用戶最新輸入的值。(這個在我做微信公眾號一個表單項目時候突發(fā)奇想的實現(xiàn))
this.$parent
這個是vue提供的api,使用也很簡單,場景一般適合在父組件寫入多個子組件需要調(diào)用的公共方法,比provide/inject占用性能要低,也更明確作用域。
只需要在父組件注冊方法,在子組件這樣使用:
this.$parent.parentFn();
這里要注意層級,也許是孫組件,或者從孫組件,可以這樣調(diào)用:
this.$parent.$parent.grandfatherFn();
只需要打印出當(dāng)前的this.$parent查看,確定層級即可。
好了,上面就是 《 Vue組件傳值與通信集合 》的全部內(nèi)容了,希望能夠?qū)δ阌兴鶐椭?,如有疑問歡迎留言~
該篇收錄于文集:前端技術(shù)棧