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
<!--v-model简写-->
<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 :xxxx="数据">将数据传给父组件,slot的固定写法,不是之前学习的给子组件的props传值
2.父组件(给插槽传结构的代码)外侧包裹<template scope="yyyy">标签,也可以写成<template slot-scope="yyyy">yyyy接收到的是插槽传过来的{xxxx:数据}
3.父组件接收的数据是一个对象,对象包含传过来的值。也就是说传过来的值外层包裹了一层对象,所以起名的时候yyy可以和xxx不一样。

<Category title="游戏">
	<template scope="ranan">
		<ul>
			{{ranan}} <!--接收到{game:['红色警戒','穿越火线','劲舞团','超级玛丽'],mag:"hello"}-->
		</ul>
 </template>
</Category>

<!--category组件-->
<template>
	<div class="category">
		<slot :game="games">我是默认的一些内容</slot>
	</div>
</template>

<script>
	export default {
		name:'Category',
		props:['title'],
		data() {
			return {
				games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
			}
		},
	}
</script>

祖先组件向子孙组件传值

provide/inject

  • provide提供数据:指定想要提供给后代组件的数据或方法
  • inject在组件中注入数据:在任何后代组件中使用inject接收provide提供的数据或方法,不管组件嵌套多深都可以直接拿来用

injectdata/props之前初始化,providedata/props之后初始化,注入内容时,是将内容注入到当前vc的__provide
inject配置key先在当前组件读取内容(__provide),读取不到则取它的父组件读取,找到最终内容保存到当前实例(vc)中,这样可以直接通过this读取到inject注入的内容。

// 父组件
export default {
  provide: {
    name: "父组件数据",
    say() {
      console.log("say say say");
    },
  },
  // 当需要用到this的时候需要使用函数形式
  provide() {
    return {
      todoLength: this.todos.length
    }
  },
}

// 子组件
<template>
  <div>
    <div>provide inject传递过来的数据: {{ name }}</div>
    <div>provide inject传递过来的方法<button @click="say">say</button></div>
  </div>
</template>

<script>
export default {
  inject: ['name', 'say'],
  },
}
</script>

Vue 不会对 provide 中的变量进行响应式处理。所以,要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的。

全能

全局事件总线 $on

全局事件总线的特点
1.所有组件都可以看见 – 事件总线需要在Vue显式原型上,这样所有的组件都可以看见
2.需要有$on绑定事件、$off解绑事件、$emit 触发事件 – Vue显式原型里的$on$off$emit函数,所以事件总线需要能看见Vue显式原型

import Vue from 'vue'
const vm = new Vue({
	el:'#app',
	render:h=>h(App),
	beforeCreate(){
	Vue.prototype.$bus= this;//安装全局事件总线
	}
})

组件中通过this.$bus.$onthis.$bus.$offthis.$bus.$emit 使用 --使用方法类似组件的自定义事件

1.全局事件总线所有组件都可以看见,所以要小心事件名重复。
2.最好在beforeDestroy钩子中,使用$off去解绑当前组件所用到的事件

Logo

前往低代码交流专区

更多推荐