第7講 組件間傳值

vue框架中懒棉,組件的概念貫穿始終,項目里所有的頁面都是一個組件览绿,它有相對獨立的作用域策严,它們之間如果需要進行數(shù)據(jù)傳遞,就要符合一定的規(guī)則饿敲,比如父子組件傳值妻导,兄弟組件傳值,隔代組件傳值(它們之間隔了好幾個組件)接下來我就講解一下這些組件之間如何傳值:

1. 父組件給子組件傳值(props)

新建src/views/father.vue文件怀各,做為父組件

father.vue

<template>
  <div class="father">
  </div>
</template>
<script>
export default {
    data () {
        return {
        }
    }
}
</script>

新建src/components/children.vue文件作為子組件

children.vue

<template>
  <div class="children">
      <ul>
          <li v-for="(item, index) in userList" :key="index">{{item}}</li>
      </ul>
  </div>
</template>
<script>
export default {
    props: {
        userList: {
            type: Array,
            required: true
        }
    },
    data () {
        return {

        }
    }
}
</script>

注意到子組件里有一個props屬性倔韭,這里來接收父組件傳遞過來的值

父組件里引用子組件并且傳值給子組件,代碼如下:

father.vue

<template>
  <div class="father">
      <children-item :userList="userList"></children-item>
  </div>
</template>
<script>
import childrenItem from '_c/children' // 引入子組件
export default {
    components: {childrenItem}, // 調(diào)用子組件
    data () {
        return {
            userList: ['張三', '李四', '王二'] // 傳值給子組件
        }
    }
}
</script>

2. 子組件給父組件傳值($emit)

在子組件children.vue里面增加一個方法sendData

children.vue

<template>
  <div class="children">
      <ul>
          <li v-for="(item, index) in userList" :key="index">{{item}}</li>
      </ul>
      <button @click="sendData">向父組件傳值:3</button>
  </div>
</template>
<script>
export default {
    props: {
        userList: {
            type: Array,
            required: true
        }
    },
    data () {
        return {

        }
    },
    methods: {
        sendData () {
            this.$emit('sendData', 3)
        }
    }
}
</script>

父組件father.vue里面通過事件接收子組件傳遞過來的值

father.vue

<template>
  <div class="father">
      <children-item :userList="userList" @sendData="getData"></children-item>
      <p>{{value}}</p>
  </div>
</template>
<script>
import childrenItem from '_c/children'
export default {
    components: {childrenItem},
    data () {
        return {
            userList: ['張三', '李四', '王二'],
            value: 0
        }
    },
    methods: {
        getData (val) {
            // 接收子組件傳遞過來的值
            this.value = val
        }
    }
}
</script>

3. 兄弟組件或隔代組件傳值(emit/on)

這種方法通過一個空的Vue實例作為中央事件總線(事件中心)瓢对,用它來觸發(fā)事件和監(jiān)聽事件,巧妙而輕量地實現(xiàn)了任何組件間的通信寿酌,包括父子、兄弟硕蛹、跨級醇疼。但是我們的項目比較大時,可以選擇更好的狀態(tài)管理vuex法焰。

新建src/bus/index.js文件:

import Vue from 'vue'

const Bus = new Vue()

export default Bus

在main.js里面引入:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Bus from './bus/index' // 引入總線

Vue.config.productionTip = false
Vue.prototype.$bus = Bus // 使用總線

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

新建src/components/brother1.vue和brother2.vue文件做為兄弟組件秧荆,讓brother1給brother2傳值

brother1.vue

<template>
  <div class="brother1">
      <h3>兄弟組件1</h3>
      <button @click="sendData">兄弟組件1</button>
  </div>
</template>
<script>
export default {
    data () {
        return {

        }
    },
    methods: {
        sendData () {
            this.$bus.$emit('sendData', 1) // 發(fā)送
        }
    }
}
</script>

brother2.vue

<template>
  <div class="brother2">
      <h3>兄弟組件2</h3>
      <p>{{value}}</p>
  </div>
</template>
<script>
export default {
    data () {
        return {
            value: null
        }
    },
    methods: {
    },
    mounted () {
        this.$bus.$on('sendData', val => { // 接收
            console.log(val)
            this.value = val
        })
    }
}
</script>

4. attrs/listeners

這種方式我在項目里沒有用過,它是Vue2.4版本中新增的內(nèi)容壶栋,以后可以在項目里用一用辰如,接下來的例子我就照著網(wǎng)上給的教程敲一遍,演示一下如何使用:

這里需要新建一個子組件贵试,src\components\children2.vue

建好以后琉兜,再來改造下src\views\father.vue這個組件,還有src\components\children.vue這個組件

father.vue

<template>
  <div class="father">
      <div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
          <h4>父組件</h4>
          <children-item></children-item>
      </div>
  </div>
</template>
<script>
import childrenItem from '_c/children'
export default {
    components: {childrenItem},
    data () {
        return {
        }
    },
    methods: {
    }
}
</script>
<style lang="less" scoped>
    .father {
        height: 300px;
        background: gold;
    }
</style>

children.vue

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
      <h4>子組件-1</h4>
      <children-item2></children-item2>
  </div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
    components: {childrenItem2},
    data () {
        return {

        }
    },
    methods: {
    }
}
</script>

children2.vue

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
      <h4>子組件-2</h4>
  </div>
</template>
<script>
export default {
    data () {
        return {

        }
    },
    methods: {
    }
}
</script>

這樣做的目的毙玻,使它們之間的關(guān)系是這樣的:

father(a)組件引入children(b)做為子組件豌蟋,children(b)組件引入children2(c)組件做為子組件

father組件(A)--子--children組件(B)--子--children2組件(C)

b是a的子組件,c又是b的子組件桑滩,這樣形成一個嵌套的關(guān)系梧疲,現(xiàn)在有一個需求,需要a組件把值直接傳遞給c組件运准,有幾種解決方法呢幌氮?

  1. vuex(大材小用)
  2. a先通過props把值傳遞給b,b再通過props將值傳遞給c(容易出錯)
  3. 利用上面講的事件總線$bus(多人合作開發(fā)時胁澳,代碼維護性較低)

所以建議使用attrs/listeners

假如在父組件a中该互,有name1和name2兩個值需要傳遞給子組件b

a組件

<template>
  <div class="father">
      <div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
          <h4>父組件</h4>
          <children-item :name1="name1" :name2="name2"></children-item>
      </div>
  </div>
</template>
<script>
import childrenItem from '_c/children'
export default {
    components: {childrenItem},
    data () {
        return {
            name1: '張三',
            name2: '李四'
        }
    },
    methods: {
    }
}
</script>

子組件b拿到值以后通過$attrs再傳遞給c組件

b組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
      <h4>子組件-1</h4>
      {{$attrs}}
      <children-item2 v-bind="$attrs"></children-item2>
  </div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
    components: {childrenItem2},
    inheritAttrs: false,
    data () {
        return {

        }
    },
    methods: {
    }
}
</script>

c組件里這樣獲取這兩個值:

c組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
      <h4>子組件-2</h4>
      <span>{{name1}}</span>
      <span>{{name2}}</span>
  </div>
</template>
<script>
export default {
    props: ['name1', 'name2'],
    inheritAttrs: true,
    data () {
        return {

        }
    },
    methods: {
    }
}
</script>

假如b組件通過props接收了name1這個值,那么c組件就不會接收到name1這個值了

b組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
      <h4>子組件-1</h4>
      {{$attrs}}
      <children-item2 v-bind="$attrs"></children-item2>
  </div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
    components: {childrenItem2},
    props: ['name1'],
    inheritAttrs: false,
    data () {
        return {

        }
    },
    methods: {
    }
}
</script>

c組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
      <h4>子組件-2</h4>
      <span>{{name2}}</span>
  </div>
</template>
<script>
export default {
    props: ['name2'],
    inheritAttrs: true,
    data () {
        return {

        }
    },
    methods: {
    }
}
</script>

上面講的是如何將a組件的值傳遞給c組件韭畸,這是往下傳遞的宇智,那么如何將c組件的值傳遞給a組件蔓搞,往上傳遞呢?

c組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
      <h4>子組件-2</h4>
      <span>{{name2}}</span>
      <button @click="sendData">向a組件傳值</button>
  </div>
</template>
<script>
export default {
    props: ['name2'],
    inheritAttrs: true,
    data () {
        return {

        }
    },
    methods: {
        sendData () {
            this.$emit('sendData', 1)
        }
    }
}
</script>

b組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
      <h4>子組件-1</h4>
      {{$attrs}}
      <children-item2 v-bind="$attrs" v-on="$listeners"></children-item2>
  </div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
    components: {childrenItem2},
    props: ['name1'],
    inheritAttrs: false,
    data () {
        return {

        }
    },
    methods: {
    }
}
</script>

a組件

<template>
  <div class="father">
      <div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
          <h4>父組件</h4>
          <span>{{value}}</span>
          <children-item :name1="name1" :name2="name2" @sendData="getData"></children-item>
      </div>
  </div>
</template>
<script>
import childrenItem from '_c/children'
export default {
    components: {childrenItem},
    data () {
        return {
            name1: '張三',
            name2: '李四',
            value: null
        }
    },
    methods: {
        getData (val) {
            this.value = val
        }
    }
}
</script>

從以上代碼示例随橘,我們可以看出attrs/listeners的功能喂分,它就像一個橋梁的作用,負責(zé)在a和c直接傳遞和接收數(shù)據(jù)

5. provide/inject

在第四種方式里:attrs/listeners机蔗,其實也可以看到使用的局限在于蒲祈,a,b,c這三個組件,必須是層層嵌套的關(guān)系蜒车,通過在b組件里使用attrs/listeners讳嘱,讓a和c進行隔代通信

這里講的provide/inject,則更加靈活酿愧,它是vue2.2版本新增內(nèi)容,不論是a,b邀泉,c這種嵌套關(guān)系嬉挡,還是a,b,c,d,e...更深層的嵌套關(guān)系,a組件通過provide分享數(shù)據(jù)汇恤,b,c,d,e...都可以通過inject拿到a組件分享的數(shù)據(jù)

來看一下具體的示例代碼:

我還是拿上面的組件來演示:

src\views\father.vue -- a組件
src\components\children.vue -- b組件
src\components\children2.vue -- c組件

a組件中使用provide

<template>
  <div class="father">
      <div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
          <h4>父組件</h4>
          <children-item></children-item>
      </div>
  </div>
</template>
<script>
import childrenItem from '_c/children'
export default {
    components: {childrenItem},
    provide () {
        return {
            name1: this.name1,
            name2: this.name2
        }
    },
    data () {
        return {
            name1: '張三',
            name2: '李四',
        }
    },
    methods: {
    }
}
</script>

b組件使用inject

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
      <h4>子組件-1</h4>
      {{name1}}
      {{name2}}
      <children-item2></children-item2>
  </div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
    components: {childrenItem2},
    inject: ['name1', 'name2'],
    data () {
        return {

        }
    },
    methods: {
    }
}
</script>

c組件使用inject

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
      <h4>子組件-2</h4>
      <span>{{name1}}</span>
      <span>{{name2}}</span>
  </div>
</template>
<script>
export default {
    inject: ['name1', 'name2'],
    data () {
        return {

        }
    },
    methods: {
    }
}
</script>

是不是很簡單庞钢,但是!需要注意:provide 和 inject 綁定并不是可響應(yīng)的因谎,也就是說基括,a組件里修改一個值,后面的組件并不能拿到修改后的值财岔,如:在a組件里修改name1值:

<template>
  <div class="father">
      <div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
          <h4>父組件</h4>
          <span>{{name1}}</span>
          <span>{{name2}}</span>
          <children-item></children-item>
          <button @click="changeName1">改變name1值</button>
      </div>
  </div>
</template>
<script>
import childrenItem from '_c/children'
export default {
    components: {childrenItem},
    provide () {
        return {
            name1: this.name1,
            name2: this.name2
        }
    },
    data () {
        return {
            name1: '張三',
            name2: '李四',
        }
    },
    methods: {
        changeName1 () {
            this.name1 = '旺財'
        }
    }
}
</script>

這name1改變以后风皿,b,c組件里還是修改之前的值

那么有沒有辦法實現(xiàn)數(shù)據(jù)響應(yīng)式呢?有匠璧,看如下代碼示例:

a組件改造以后相當(dāng)于將整個a組件實例分享出去

<template>
  <div class="father">
      <div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
          <h4>父組件</h4>
          <span>{{name1}}</span>
          <span>{{name2}}</span>
          <children-item></children-item>
          <button @click="changeName1">改變name1值</button>
      </div>
  </div>
</template>
<script>
import childrenItem from '_c/children'
export default {
    components: {childrenItem},
    provide () {
        return {
            name: this // 將這個組件實例提供給后面的子組件
        }
    },
    data () {
        return {
            name1: '張三',
            name2: '李四',
        }
    },
    methods: {
        changeName1 () {
            this.name1 = '旺財'
        }
    }
}
</script>

b組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
      <h4>子組件-1</h4>
      {{name.name1}}
      <children-item2></children-item2>
  </div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
    components: {childrenItem2},
    inject: {
        name: {
            default: () => ({})
        }
    },
    data () {
        return {
        }
    },
    methods: {
    },
}
</script>

c組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
      <h4>子組件-2</h4>
      <span>{{name.name1}}</span>
  </div>
</template>
<script>
export default {
    inject: {
        name: {
            default: () => ({})
        }
    },
    data () {
        return {

        }
    },
    methods: {
    },
}
</script>

以上就可以實現(xiàn)響應(yīng)式的往子組件傳遞數(shù)據(jù)

題外話桐款,我說一下,方法4和方法5夷恍,這些都是不常用的api魔眨,其實在做項目的時候,vue提供給我們的api大概能用到50%可能就已經(jīng)不錯了酿雪,但是我想說的是遏暴,即使不常用的也要知道怎么用,并且做項目的時候用一用比較好

5. parent/children/$refs

使用這3種方式都會得到組件實例指黎,然后就可以直接調(diào)用組件里的方法或者數(shù)據(jù)

同樣還是使用上面的a,b,c三個組件做為例子演示一下:

a組件

<template>
  <div class="father">
      <div style="width: 300px;height: 300px;border: 1px solid red;margin: 0 auto;">
          <h4>父組件</h4>
          <span>{{name1}}</span>
          <span>{{name2}}</span>
          <children-item ref="childrenItem"></children-item>
      </div>
  </div>
</template>
<script>
import childrenItem from '_c/children'
export default {
    components: {childrenItem},
    provide () {
        return {
            name: this
        }
    },
    data () {
        return {
            name1: '張三',
            name2: '李四',
        }
    },
    methods: {
    },
    mounted () {
        console.log(this.$refs.childrenItem.name)
        this.$refs.childrenItem.getName()
        // 效果相同
        console.log(this.$children[0].name)
        this.$children[0].getName()
    }
}
</script>

b組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 100px);">
      <h4>子組件-1</h4>
      <children-item2 ref="childrenItem2"></children-item2>
  </div>
</template>
<script>
const childrenItem2 = () => import('_c/children2')
export default {
    components: {childrenItem2},
    data () {
        return {
            name: '子組件-1'
        }
    },
    methods: {
        getName () {
            console.log(this.name)
        }
    },
    mounted () {
        console.log(this.$parent.name1) // 張三

        console.log(this.$refs.childrenItem2.name) // 子組件-2
        console.log(this.$children[0].name) // 子組件-2
    }
}
</script>

c組件

<template>
  <div class="children" style="margin: 30px;border:1px solid black;height: calc(100% - 90px);">
      <h4>子組件-2</h4>
  </div>
</template>
<script>
export default {
    data () {
        return {
            name: '子組件-2'
        }
    },
    methods: {
        getName () {
            console.log(this.name)
        }
    },
    mounted () {
        console.log(this.$parent.name) // 子組件-1
    }
}
</script>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朋凉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子袋励,更是在濱河造成了極大的恐慌侥啤,老刑警劉巖当叭,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盖灸,居然都是意外死亡蚁鳖,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門赁炎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醉箕,“玉大人,你說我怎么就攤上這事徙垫〖タ悖” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵姻报,是天一觀的道長己英。 經(jīng)常有香客問我,道長吴旋,這世上最難降的妖魔是什么损肛? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮荣瑟,結(jié)果婚禮上治拿,老公的妹妹穿的比我還像新娘。我一直安慰自己笆焰,他們只是感情好劫谅,可當(dāng)我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著嚷掠,像睡著了一般捏检。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叠国,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天未檩,我揣著相機與錄音,去河邊找鬼粟焊。 笑死冤狡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的项棠。 我是一名探鬼主播悲雳,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼香追!你這毒婦竟也來了合瓢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤透典,失蹤者是張志新(化名)和其女友劉穎晴楔,沒想到半個月后顿苇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡税弃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年纪岁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片则果。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡幔翰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出西壮,到底是詐尸還是另有隱情遗增,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布款青,位于F島的核電站做修,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏抡草。R本人自食惡果不足惜缓待,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望渠牲。 院中可真熱鬧,春花似錦步悠、人聲如沸签杈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽答姥。三九已至,卻和暖如春谚咬,著一層夾襖步出監(jiān)牢的瞬間鹦付,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工择卦, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留敲长,地道東北人。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓秉继,卻偏偏與公主長得像祈噪,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子尚辑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,452評論 2 348