Vue.js - 组件通信

在组件间传递数据的操作,称为组件通信。

父组件向子组件传值


通过子组件的 props 选项接收父组件的传值。

Vue..component('my-component', {
  props: ['title'],
  template: '<h3>{{ title }}</h3>'
})

注意:props 不要与 data 存在同名属性。

父组件设置方式如下:

<div id="app">
  <!-- 传入静态内容 -->
  <my-component title="示例内容1"></my-component>
  <!-- 通过绑定形式传入静态内容,需确保内容为字符串 -->
  <my-component :title="'示例内容1'"></my-component>
  <!-- 动态绑定父组件的 title 数据 -->
  <my-component :title="item.title"></my-component>
</div>
new Vue({
  el: '#app',
  data: {
    item: {
      title: '父组件中的数据'
    }
  }
})

Props 命名规则

建议 prop 命名使用 camelCase,父组件绑定时使用 kebab-case。

<div id="app">
  <my-component my-title="示例内容1"></my-component>
  <my-component :my-title="'示例内容1'"></my-component>
  <my-component :my-title="item.title"></my-component>
</div>
Vue..component('my-component', {
  props: ['myTitle'],
  template: '<h3>{{ myTitle }}</h3>'
})

单向数据流

父子组件间的所有 prop 都是单向下行绑定的。如果子组件要处理 prop 数据,应当存储在 data 中后操作。

Vue.component('my-component', {
  props: ['initialTitle'],
  template: '<h3>{{ myTitle }}</h3>',
  data () {
    return {
      myTitle: this.initialTitle
    }
  }
})

注意:如果 prop 为数组或对象时,子组件操作将会影响到父组件的状态。

Props 类型

Prop 可以设置类型检查,这时需要将 props 更改为一个带有验证需求的对象,并指定对应类型。

// 子组件
Vue.component('MyComponentA', {
  props: {
    parStr: String,
    parArr: Array,
    parAny: null // parAny: undefined 任意类型
  },
  template: `
    <div>
      {{ parStr }}
      {{ parArr }}
      {{ parAny }}
    </div>
  `
})
new Vue({
  el: '#app',
  data: {
    str: '示例内容',
    arr: [1, 2, 3],
    any: '任意类型均可'
  }
})
<div>
  <my-component-a
    :par-str="str"
    :par-arr="arr"
    :par-any="any"
  ></my-component-a>
</div>

prop 还可以同时指定多个类型,通过数组方式保存即可。

Vue.component('MyComponentA', {
  props: {
    parData: [String, Number],
  },
  template: `
    <div>
      {{ parData }}
    </div>
  `
})

Props 验证

当 prop 需要设置多种规则时,可以将 prop 的值设置为选项对象。

  • 之前的类型检测功能通过 type 选项设置。
  • required 用于设置数据为必填项。
  • default 用于给可选项指定默认值,当父组件未传递数据时生效。当默认值为数组或对象时,必须为工厂函数返回的形式。
  • validator 用于给传入的 prop 设置校验函数,return 值为 false 时 Vue.js 会发出警告。验证函数中无法使用实例的 data、methods 等功能。
Vue.component('MyComponentA', {
  props: {
    parNum: {
      type: Number,
      required: true
    },
    parData: {
      type: [String, Boolean],
      default: false
    },
    parArr: {
      type: Array,
      default: function () {
        return [1, 2, 3]
      }
    },
    parStr: {
      type: String,
      validator: function (value) {
        return value.startWith('lagou')
      }
    }
  },
  template: `
    <div>
      {{ parNum }|}
      {{ parData }}
      {{ parArr }}
      {{ parStr }}
    </div>
  `
})

非 Props 属性

当父组件给子组件设置了属性,但此属性在 props 中不存在,这时会自动绑定到子组件的根元素上。

Vue.component('MyComponentA', {
  template: `<p>子组件内容</p>`
})
<div id="app">
  <my-component-a
    demo-attr="自定义属性"
    title="示例title"
    style="height: 200px"
    class="colorBlue"
  ></my-component-a>
</div>

如果组件根元素已经存在了对应属性,则会替换组件内部的值。 class 与 style 是例外,当内外都设置时,属性会自动合并。

Vue.component('MyComponentA', {
  template: `<p title="原始title" class="fl" style="width: 200px;">子组件内容</p>`
})

如果不希望继承父组件设置的属性,可以设置 inheritAttrs: false,但只适用于普通属性,class 与 style 不受影响。

Vue.component('MyComponentA', {
  inheritAttrs: false,
  template: `<p title="原始title" class="fl" style="width: 200px;">子组件内容</p>`
})

子组件向父组件传值


子向父传值需要通过自定义事件实现。

什么时候需要子组件向父组件传值?比如当商品为子组件,购物车为父组件时,父组件需要统计商品个数,就需要在子组件个数变化时传值给父组件。

<div id="app">
  <h3>购物车</h3>
  <product-item
    v-for="product in products"
    :title="product.title"
    :key="product.id"
  ></product-item>
  <p>总数为:{{ totalCount }}</p>
</div>
// 父组件
new Vue({
  el: '#app',
  data: {
    products: [
      { id: 1, title: '苹果1斤' }
      { id: 2, title: '橙子2个' }
      { id: 3, title: '香蕉3根' }
    ]
  },
totalCount: 0
})
// 子组件
Vue.component('product-item', {
  props: ['title'],
  template: `
    <div>
      <span>商品名称:{{ title }},商品个数:{{ count }}</span>
      <button @click="countIns">+1</button>
    </div>
  `,
  data () {
    return { count: 0 }
  },
  methods: {
    countIns () {
      this.count++
    }
  }
})

子组件数据变化时,通过 $emit() 触发自定义事件。自定义事件名称建议使用 kebab-case 命名方式。

Vue.component('product-item', {
  ...
  methods: {
    countIns () {
      this.$emit('count-change')
      this.count++
    }
  }
})

父组件监听子组件的自定义事件,并设置处理程序。

<div id="app">
  ...
  <product-item
    ...
    @count-change="totalCount++"
  ></product-item>
  ...
</div>

自定义事件传值

子组件触发事件时可以向父组件传值。

// 子组件
Vue.component('product-item', {
  props: ['title'],
  template: `
    <div>
      <span>商品名称:{{ title }},商品个数:{{ count }}</span>
      <button @click="countIns5">+5</button>
    </div>
  `,
  ...
  methods: {
    countIns5 () {
      this.$emit('count-change', 5)
      this.count += 5
    }
  }
})

父组件在监听事件时,通过 $event 接收子组件传递的数据。

<div id="app">
  ...
  <product-item
    ...
    @count-change="totalCount += $event"
  ></product-item>
  ...
</div>

除了在监听自定义事件的值中直接书写处理代码之外,还可以通过设置处理程序来处理传递的数据。

<div id="app">
  ...
  <product-item
    ...
    @count-change="onCountChange"
  ></product-item>
  ...
</div>
new Vue({
  ...
  methods: {
    onCountChange (productCount) {
      this.totalCount += productCount
    }
  }
})

组件与 v-model

v-model 用于组件时,需要通过 props 与自定义事件实现。

<div id="app">
  <p>输入内容为:{{ iptValue }}</p>
  <com-input v-model="iptValue"></com-input>
</div>
// 子组件
var ComInput = {
  props: ['value'],
  template: `
    <input type="text" :value="value" @input="$emit('input', $event.target.value)">
  `
}
// 父组件
new Vue({
  el: '#app',
  data: {
    iptValue: ''
  },
  componets: {
    ComInput
  }
})

非父子组件传值


非父子组件指的是兄弟组件或完全无关的两个组件。

兄弟组件传值

兄弟组件可以通过父组件进行数据中转。

<div id="app">
  <com-a
    @value-change="value = $event"
  ></com-a>
  <com-b
    :value="value"
  ></com-b>
</div>
// 子组件 A
Vue.component('ComA', {
  template: `
    <div>
      组件A的内容:{{ value }}
      <button
        @click="$emit('value-change', value)"
      >发送</button>
    </div> 
  `,
  data () {
    return { value: '示例内容' }
  }
})

// 子组件 B
Vue.componet('ComB', {
  props:['value'],
  template: `
    <div>
      组件B接收到:{{ value }}
    </div>
  `
})

// 父组件
new Vue({
  el:'#app',
  data: {
    value: ''
  }
})

EventBus

当组件嵌套关系复杂时,根据组件关系传值会较为繁琐,而组件为了数据中转,data 中会存在许多与当前组件功能无关的数据,这时候就需要一个工具进行数据传递的管理。

EventBus (事件总线)就是一个独立的事件中心,用于管理不同组件间的传值操作。其通过一个新的 Vue 实例来管理组件传值操作,组件通过给实例注册事件、调用事件来实现数据传递。

// EventBus.js
var bus = new Vue()

发送数据的组件触发 bus 事件,接收的组件给 bus 注册对应事件。

Vue.component('product-item', {
  template: `
    <div>
      <span>商品名称:苹果,商品个数:{{ count }}</span>
      <button @click="countIns">+1</button>
    </div>
  `,
  data () {
    return { count: 0 }
  },
  methods: {
    countIns () {
      bus.$emit('countChange', 1)
      this.count++
    }
  }
})

给 bus 注册对应事件通过 $on() 操作。

Vue.component('product-total', {
  template: `
    <p>总个数为:{{ totalCount }}</p>
  `,
  data () {
    return { totalCount: 0 }
  },
  created () {
    bus.$on('countChange', (productCount) => {
      this.totalCount += productCount
    })
  }
})

最后创建根实例执行代码即可。

new Vue({
  el: '#app'
})

其他通信方式


$root

$root 用于访问当前组件树根实例,设置简单的 Vue 应用时可以通过此方式进行组件传值。

<div id="app">
  <p>父组件数据:{{ count }}</p>
  <com-a></com-a>
</div>
// 子组件 A
var ComA = {
  template: `
    <div>
      组件 A:{{ $root.count }}
      <button @click="clickFn">+1</button>
    </div>
  `,
  methods: {
    clickFn () {
      this.$root.count++
    }
  }
}
// 根组件
new Vue({
  el: '#app',
  data: {
    count: 0
  },
  components: {
    ComA
  }
})

除了 $root , Vue.js 中还提供了 $parent$children 用于便捷访问父子组件。

$refs

$refs 用于获取设置了 ref 属性的 HTML 标签或子组件。给普通 HTML 标签设置 ref 属性,$refs 就可以获取 DOM 对象。

<div id="app">
  <input type="text" ref="inp">
  <button @click>按钮</button>
</div>
new Vue({
  el: '#app',
  methods: {
    fn () {
      this.$refs.inp.focus()
    }
  }
})

给子组件设置 ref 属性,渲染后可通过 $refs 获取子组件实例。

<div id="app">
  <com-a ref="comA"></com-a>
</div>
var ComA = {
  template: `<p>组件 A:{{ value }}</p>`,
  data () {
    return {
      value: '这是组件A的数据'
    }
  }
}

new Vue({
  el: '#app',
  components: {
    ComA
  },
  mounted () {
    console.log(this.$refs)
    this.$refs.comA.value = '修改子组件数据'
  }
})
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容