vue2 组件传值的方式 v-model的原理和实现

文章目录

  • vue2 组件传值的方式
    • 面试题
    • 什么是单向数据流
      • 父组件给子组件传值
        • props
    • 如何实现子组件给父组件传值?如何实现双向数据绑定?
      • props父传子 + 绑定事件监听on/触发事件监听emit 子传父
        • 自定义事件的补充知识点
      • v-model语法糖 = props父传子 + 绑定事件监听@input/触发事件监听emit 子传父
      • sync 修饰符 props传参+ 绑定事件监听@update:属性名/触发事件监听emit 子传父
    • 父组件给子组件传值
      • $attrs + $listeners 父组件给子组件传值
      • 插槽 结构父 -> 子
        • 作用域插槽 数据: 子 -> 父 结构:父 -> 子
    • 祖先组件向子孙组件传值
      • provide/inject
    • 全能
      • 全局事件总线 $on

vue2 组件传值的方式

面试题

  • 组件传值方式
  • vue是否是单向数据流
  • 父子组件传值方式
  • v-model语法糖,具体绑定哪些属性
  • v-model双向绑定原理,v-model的实现

什么是单向数据流

  1. 什么是单向数据流
    数据流是指组件之间数据的流向,单向数据流指数据只能从父组件向子组件传递,子组件无法改变父组件的props,如果想修改有其他的方式。
  2. 为什么不能是双向的
    父组件的数据发生改变,会通过props来通知子组件自动更新。 防止多个子组件都尝试修改父组件状态时,导致数据混乱

单向数据流的好处

  • 单向数据流会使所有状态的改变可记录、可跟踪,源头易追溯;
  • 所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性。

父组件给子组件传值

props

  • 通过一般属性实现父向子通信
  • 通过函数属性实现子向父通信 – 方式4

父组件通过属性传值

<Student name="李四"/>

子组件通过props参数接收数据,数据优先被设置在vc组件上(vc.数据)

//写法1 数组形式
props:['age'] 

//写法2 对象形式,设置默认值、接收的数据类型等
 props:{
      age:{
          type:String,
          default:'这是默认数据'
      }
}

说明

  1. props是只读属性,子组件不可以修改传入的值。
  2. 如果子组件不使用prop,子组件的$attrs里会存储传过来的属性,子组件的vc实例上不会存储。
    如果子组件使用prop,子组件的vc实例上会直接存储传过来的属性,子组件的$attrs里不会存储。

$attrs 包含父作用域里除 class 和 style 除外的非 props 属性(子组件没有使用props接收)集合。

如何实现子组件给父组件传值?如何实现双向数据绑定?

什么是双向数据绑定
父组件可以通过props改变子组件的数据,子组件也可以用emit事件通知父组件的改变数据。
双方数据可以互相更新,这就是双向数据绑定。

props父传子 + 绑定事件监听on/触发事件监听emit 子传父

思路

  • 将自定义事件绑定在子组件的实例vc上,回调函数是在父组件中,通过回调函数接收参数
    - 写法1: 在父组件中使用@v-on:将回调函数绑定在子组件的vc上 @自定义事件=’事件回调'
    - 写法2: 在父组件中this.$refs.xxx获取到子组件的实例,采用$on(‘事件名’,回调函数)绑定自定义事件
  • 子组件$emit()触发自定义事件并传递参数,子组件this.$off()解绑自定义事件

父组件代码

<template>
  <div>
    <h1>我是父组件</h1>
    <Son :info="info" @change="fn"></Son>
  </div>
</template>

<script>
import Son from "./Son.vue";
export default {
  data() {
    return {
      info: "我是父组件中的数据",
    };
  },
  components: {
    Son,
  },
  methods: {
    fn(info) {
      this.info = info +  "我是父组件中点击修改后的数据";
    },
  },
};
</script>

子组件代码

<template>
  <div>
    <h2>我是子组件</h2>
    <p>{{ info }}</p>
    <button @click="fn">修改数据(子)</button>
  </div>
</template>

<script>
export default {
  props: ["info"], //父传子
  methods: {
    fn() {
      //这种直接赋值prop是不可取的,vue会直接报错
      //this.info=this.info+"子组件直接赋值prop"
      // 修改数据
      this.$emit('change',this.info + ",我现在被子组件emit了"); //触发自定义事件并传值,父组件自定义事件的回调函数触发
    },
  },
};
</script>

自定义事件的补充知识点

  1. 组件在绑定事件时,默认认为绑定的是自定义事件。
    可以使用.native标识符比如@click.native,将自定义事件变为原生的DOM事件,是将事件绑定在了子组件的根节点上。
  2. 给原生的DOM绑定自定义事件没有意义,因为$emit()触发自定义事件的函数是Vue原型上的,组件实例vc可以看见,但是原生的DOM看不见

v-model语法糖 = props父传子 + 绑定事件监听@input/触发事件监听emit 子传父

v-model实现原理 单向绑定默认是表单元素的value属性 + @input事件监听

<input type='text' :value="msg" @input = "msg = $event.target.value"/>
<span>{{msg}}</span>

v-model 父组件通过子组件标签传值,子组件通过$emit触发

  1. 父组件给子组件传值,并绑定自定义事件
    默认传递的属性是value,自定义事件名为input

<Son v-model="msg" />

<Son :value="msg" @input= "val => msg=val "/>
  1. 子组件使用props接收,通过 $emit 修改父组件的数据
props:['value']

text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。

sync 修饰符 props传参+ 绑定事件监听@update:属性名/触发事件监听emit 子传父

.sync修饰符可以实现和v-model同样的功能,而且它比v-model更加灵活。
v-model一个组件只能用一个,sync可以有多个。

xxx.sync的原理
①父组件给字子组件传递props:属性名
②给当前子组件绑定了一个自定义事件,事件名为update:属性名,该事件会更新xxx的值

// 正常父传子: 
<son :info="str" :title="str2"></son>

// 加上sync之后父传子(.sync没有数量限制): 
<son :info.sync="str" .title.sync="str2"></son> 

// 它等价于
<son
  :info="str" @update:info="val=>str=val"
  :title="str2" @update:title="val=>str2=val"></son> 

子组件

<template>
  <div>
    <p>{{ info }}</p>
    <button @click="fn">修改数据(子)</button>
  </div>
</template>
<script>
export default {
  props: ["info","title"],
  name:'son',
  methods: {
    fn() {
      // 修改数据:`$emit`所调用的事件名必须是`update:属性名`
      this.$emit('update:info',this.info + ",我现在被子组件emit了")
    },
  },
};
</script>

父组件给子组件传值

$attrs + $listeners 父组件给子组件传值

多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用这个,比如父组件向孙子组件传递数据时

  • $attrs:包含父作用域里除 class 和 style 除外的非 props 属性集合。通过 this.$attrs 获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过 v-bind="$attrs"
  • $listeners:包含父作用域里 .native 除外的监听事件集合。如果还要继续传给子组件内部的其他组件,就可以通过 v-on="$linteners"

需求:对el-button进行二次封装,由传递的参数决定是什么类型的button

父组件

<Son type="success" icon="el-icon-delete" size="mini" title="提示按钮" @click=”handler“>Son>

子组件Son,不使用props接收

<template>
	<div>
		/*把attrs上的属性都绑定在el-button上,注意v-bind不可以简写*/
		/*把父组件传递的自定义事件绑定在子组件上,注意v-on不可以简写*/
		<el-button v-bind="$attrs" v-on="$listeners"></el-button>
	</div>
</template>

插槽 结构父 -> 子

让父组件可以向子组件指定位置插入html结构,主要通信的数据是html结构

提到了这个感觉就可能会问有哪些插槽了

  • 默认插槽
  • 具名插槽 slot标签的name属性命名
  • 作用域插槽 数据在插槽位置,但是根据数据生成的结构需要父组件来决定

作用域插槽 数据: 子 -> 父 结构:父 -> 子

使用场景:数据在插槽位置,但是根据数据生成的结构需要父组件来决定

数据在子组件(作用域),结构由父组件传。
数据: 子 -> 父 结构:父 -> 子

步骤
1.将数据传给父组件,slot的固定写法,不是之前学习的给子组件的props传值
2.父组件(给插槽传结构的代码)外侧包裹