正向属性传值、反向传值-$emit、反向传值-sync、反向传值v-model、多层传值$attrs$listeners、$ parent$ root$ children$refs。。。-vue2

1、正向属性传值

父组件通过属性给子组件传值: 子组件的props接受数据
在页面模板中 使用变量名:属性 data 计算属性(重点)

  • 注意属性传值是单向的
    父组件:
<template>
	<div>
		<Box v-for="(item,index) in arr" 
		:title="item.title" 
		:price="item.price" 
		:count="item.count" 
		:key="item.id">
		</Box>
		<button>总价:{{total}}</button>
		<!-- <input type="text" :value="msg"> -->
	</div>
</template>

子组件:

<template>
   <div>
      <div>
         {{ title }}----{{ price }}元 数量:
         <button>-</button>
         <span>{{ count }} </span>
         <button @click="add">+</button>
      </div>
   </div>
</template>
<script>
export default {
   props: ["title", "price", "count"],
   methods: {
      add() {
         console.log(this.count);
         this.count++;
      },
   },
};
</script>

2、反向传值-$emit

2.1 子组件通过自定义事件给父组件传新值 $emit
子组件通过自定义事件给父组件传count的新值n,父组件收到新值后修改自己的data,自然就会刷新自己 和子组件的模板

子组件通过调用父组件的方法给父组件传值:子组件的自定义事件中,用$emit触发事件调用父组件方法给父组件传值 (重点)

因为通过属性传值是单向的,有时候我们需要子组件的data 数据需要交给父组件使用:
通过在注册的子组件上定义自定义事件,在子组件中通过$emit 来触发事件;子组件的事件被触发并传参,事件处理函数可以接收到子组件的数据;事件绑定的事件处理函数在父节点上,故可在事件处理函数中用到子组件的数据值来修改父节点的数据。
父组件:

<template>
	<div>
		<Box @mycountincrement="countin" v-for="(item,i) in arr" 
		:title="item.title" 
		:price="item.price" 
		:count="item.count" 
		:key="item.id"
		:index="i"
		>		
		</Box>
		<button>总价:{{total}}</button>
		<button @click="change1">父组件修改数据</button>
		<!-- <input type="text" :value="msg"> -->
	</div>
</template>
<script>
	import Box from "./Box.vue"
	export default {
		data() {
			return {
				msg:"hello",
				arr: []
			}
		},
		methods:{
			countin(arg,index){
				console.log(arg,1234,index)
				this.arr[index].count=arg
				this.$set(this.arr,index,this.arr[index])
			},
			change1(){
				
				this.arr[0].count=100
				this.$set(this.arr,0,this.arr[0])
			}
		},
		async mounted() {
			// axios.defaults.baseURL = "http://localhost:8080/api"
			// "/goods" ==>"http://localhost:8080/api/goods"
			//被proxy为 8080服务器请求了 "http://localhost:7001/goods" 返回给了$axios
			let res = await this.$axios("/goods")
			console.log(res, 123123)
			this.arr = res.data
		},
		components:{
			Box
		},
		computed:{
			total(){
				return  this.arr.reduce((n1,n2)=>{
					return n1+n2.price*n2.count
				},0)
			}
		}
	}
</script>

子组件:

<template>
   <div>
      <div>
         {{ title }}----{{ price }}元 数量:
         <button>-</button>
         <span>{{ count }} </span>
         <button @click="add">+</button>
      </div>
   </div>
</template>

<script>
export default {
   props: ["title", "price", "count", "index"],
   methods: {
      add() {
         // console.log(this.count)
         // this.count=100
         //触发事件 并传值
         let n = this.count + 1;
         this.$emit("mycountincrement", n, this.index);
      },
   },
};
</script>

3、反向传值-sync

—父组件(在传入子组件的数据后.sync 不需要在对子组件的$emit进行接收)

<template>
  <div class="content">
     <btn :btnName.sync='num' ></btn>
  </div>
</template>

—子组件($emit传回的不再是函数 而是 update:父组件传过来的变量名称 )

<script>
export default {
  name: 'btn',
  props: {"btnName"},
  methods: {
      changeNum(){
          this.$emit('update:btnName',888)
      }
  },
}
</script>

4、反向传值v-model

父组件:
v-model这是一个语法糖==>
<Box :valuel=“msg” @input=”fm“>
子组件:
props:[“value”],,然后上面的标签使用这个{{value}}
点击写事件:fn(){this.$emit(“input”,“传值”)}
父组件:

<template>
	<div class="app">
		<!-- <Box :value="msg" @input="某个函数"></Box> -->
		<p>app--{{msg}}</p>
		<Box v-model="msg"></Box>
	</div>
</template>
<script>
	import Box from "./Box.vue"
	export default {
		data() {
			return {
				msg:"app666"
			}
		},
		components: {
			Box
		},
		methods:{
		}
	}
</script>
<style scoped="scoped" lang="scss">
	.app {
		width: 600px;
		height: 400px;
		background-color: darkgray;
		margin: 20px;
	}
</style>

子组件:

<template>
	<div class="box">
	  <h2>box--{{value}}</h2>
	  <button @click="change1">chaneg1</button>
	</div>
</template>
<script>
	export default {
		props:["value"],
		methods:{
			change1(){
				this.$emit("input","1235454375687")
			}
		}
		
	}
</script>
<style scoped="scoped" lang="scss">
	.box{
		width: 400px;
		height: 200px;
		background-color: cornflowerblue;
		margin: 20px;
	}
</style>

5、多层传值$ attrs-$listeners

现有3个嵌套组件,A->B,B->C。 现在我么需要在A中对C的props赋值,监听C的emit事件

A组件与C组件通信,我们有多少种解决方案?

  1. 我们使用vuex来进行数据管理,依赖于vuex我们可以一次改变,任何一个组件中都能获取。但是如果多个组件共享状态比较少,使用vuex过于麻烦和难以维护。element-ui中大量采用此方法。
  2. 自定义vue bus事件总线,原理类似vuex,使用特别简单。bus适合碰到组件跨级兄弟组件等无明显依赖关系的消息传递,原生app开发中经常用到,但是缺点是bus破坏了代码的链式调用,大量的滥用将导致逻辑的分散,出现问题后很难定位,降低了代码可读性。
  3. 使用B来做中转,A传递给B,B再给C**,**这是最容易想到的方案,但是如果嵌套的组件过多,需要传递的事件和属性较多,会导致代码繁琐,代码维护困难。

对于我们这种情况,3种方式都有局限性
在vue2.4中,为了解决该需求,引入了$ attrs和$listeners,新增了inheritAttrs选项。我们只需要在B组件中对引入的C组件增加下面两个属性即可绑定所有的属性和事件。
A组件:

<template>
<div>
  <h2>组件A 数据项:{{myData}}</h2>
  <B @changeMyData="changeMyData" :myData="myData"></B>
</div>
</template>
<script>
import B from "./B";
export default {
  data() {
    return {
      myData: "100"
    };
  },
  components: { B },
  methods: {
    changeMyData(val) {
      this.myData = val;
    }
  }
};
</script>

B组件:

<template>
  <div>
    <h3>组件B</h3>
    <h1>{{$attrs.b1}}</h1>
    <C v-bind="$attrs" v-on="$listeners"></C>
  </div>
</template>
<script>
import C from "./C";
export default {
  components: { C },
};
</script> 

C组件:

<template>
  <div>
    <h5>组件C</h5>
    <input v-model="myc" @input="hInput" />
  </div>
</template>
<script>
export default {
  props: ["myData"],
  created() {
    this.myc = this.myData;  // 在组件A中传递过来的属性
    console.info(this.$attrs, this.$listeners);
  },
  methods: {
    hInput() {
      this.$emit("changeMyData", this.myc); // // 在组件A中传递过来的事件
    }
  }
};
</script>

6、$ parent、$ root、$ children、$refs

这些功能都是有劣势或危险的场景的,官方建议我们尽量避开它们,但是高级点的面试题中会出现

$root:

访问根组件vm (就是new Vue)对象,所有的子组件都可以将这个实例作为一个全局 store 来访问或使用,现在有更好的技术vuex代替。

$parent:

访问父组件对象,直接操作父组件的data数据,不需要再使用属性传值,但是容易出现渲染混乱之后只渲染一个的情况

$children:

访问子组件对象数组,不能保证顺序,没有按照顺序加载,加载的顺序是乱的,因此写项目的时候不能按照下标去获取组件,然后操作组件。也不是响应式的

$refs:

只会在组件渲染完成之后生效,并且它们不是响应式的。应该避免在模板或计算属性中访问 $refs。 (就相当于DOM获取元素)

Tips:

1、在Box1中打印this,this. p a r e n t , parent, parentroot,就会打印自己的VueComponent 、app.vue的VueComponent 、 Vue根节点对象

2、 p a r e n t 、 parent 、 parentchildren都是代表组件,不是元素,就算Box1放在一个div中,它的$parent也是app.vue

3、可以直接修改 父/爷爷/孙子/…组件:

this.$parent.msg=“Box1将app.vue里面的msg修改了”

 //在组件或者原生元素绑定ref属性(类似于id):
 <myinput ref="myInput1"></myinput>
 <input ref="myInput2"></input>
 
 //在父组件中可以通过 this.$refs访问到它:
 methods: {
   focus: function () {
     this.$refs.myInput2.focus()
   }
 }

App.vue:

<template>
	<div class="app">
		<h1>app--{{msg}}</h1>
		<div class="app2">
			<Box1></Box1>
		</div>
	</div>
</template>
<script>
	import Box1 from "./Box1.vue"
	export default {
		data() {
			return {
				msg:"hello"
			}
		},
		methods: {},
		components: {
			Box1
		}
	}
</script>
<style scoped="scoped" lang="scss">
	.app {
		width: 600px;
		height: 400px;
		background-color: skyblue;
		margin: 20px;
	}
</style>

Box1.vue:

<template>
	<div class="box1">
		<h1 @click="look">box1</h1>
		<Box2></Box2>
		<p ref="p1">66666</p>
		<p ref="p2">234234</p>
		<button @click="getref1">ref</button>
	</div>
</template>
<script>
	import Box2 from "./Box2.vue"
	export default {
		components: {
			Box2
		},
		methods: {
			getref1(){
				console.log(this.$refs)
			},
			look() {
				console.log(this,this.$parent,this.$children,this.$root)
				console.log(this.$root===this.hqyj1)
				// this.$children代表子组件 不是子元素  没有按照顺序加载,加载顺序是混乱的 
				//因此我们写项目不能按照下标去获取组件然后操作组件
				this.$parent.msg="box1修改了数据"
			}
		}
	}
</script>
<style>
	.box1 {
		width: 500px;
		background-color: deeppink;
		margin: 20px;
	}
</style>

Box2.vue:

<template>
	<div class="box2">
		<p>{{$parent.$parent.msg}}</p>
		box2 <button @click="change1">change</button>
	</div>
</template>
<script>
	export default {
		methods:{
			change1(){
				this.$parent.$parent.msg="box1修改了数据"
			}
		}
	}
</script>
<style>
	.box2 {
		width: 300px;
		height: 200px;
		background-color:goldenrod;
		margin: 20px;
	}
</style>

案例:利用$ref实现放大镜代码迁移

一个网站中有一个很好的功能,别人是DOM操作,而我们用Vue来操作,获取元素

==>mounted之后就去执行,将它的class改为ref,然后用var div1=this.$refs.div1来获取
<template>
  <div>
    <div ref="div1" class="div1">
      <div ref="mask" class="mask"></div>
    </div>
    <div ref="rightdiv" class="rightdiv">
      <div ref="bigimg" class="bigimg"></div>
    </div>
  </div>
</template>
<script>
export default {
  mounted() {
    var div1 = this.$refs.div1;
    var mask = this.$refs.mask;
    var rightdiv = this.$refs.rightdiv;
    var bigimg = this.$refs.bigimg;
    div1.onmouseenter = function () {
      mask.style.display = "block";
      rightdiv.style.display = "block";
    };
  },
};
</script>
<style scoped="scoped" lang="scss">
</style>

7、自定义事件语法

<template>
   <div>
      <button @click="fn1">点击给app组件绑定一个hqyj事件</button>
      <button @click="fn2">触发自定义hqyj事件</button>
   </div>
</template>

<script>
export default {
   //Vue提供的技术:某继承Vue的组件有三个功能:
   //1.触发x组件的a事件: x.$emit("a事件",参数...)
   //2.给x组件绑定a事件  x.$on("a事件",监听器函数)
   //3.给x组件解绑a事件  x.$off("a事件",监听器函数)
   methods: {
      fn1() {
         this.$on("hqyj", function (arg1, arg2) {
            console.log("我自己设计的事件触发了 hqyj", arg1, arg2);
         });
      },
      fn2() {
         this.$emit("hqyj", 100, 200);
      },
   },
};
</script>

<style></style>

8、 中央事件总线bus event bus

通过创建一个新的vm对象,专门统一注册事件,供所有组件共同操作,达到所有组件随意隔代传值的效果

Vue提供的技术:某继承Vue的组件有三个功能:(也就是所有组件,包括根组件都能用这三个方法)

1、触发x组件的a事件:x.$emit(“a”,参数…)

2、给x组件绑定a事件 x.$on(“a事件”,监听器函数)

3、给x组件解绑a事件 x.$off(“a事件”,监听器函数)
通过创建一个新的vm对象,这个对象放在与界面绑定的那个vm的原型上,专门统一注册事件,供所有组件共同操作,达到所有组件随意隔代传值的效果

//vue-bus.js文件
const install = function (Vue) {
  const Bus = new Vue({
    methods: {
      emit(event, ...args) {
        this.$emit(event, ...args);
      },
      on(event, callback) {
        this.$on(event, callback);
      },
      off(event, callback) {
        this.$off(event, callback);
      }
    }
  });
  Vue.prototype.$bus=Bus;
//由于这个新的vm放在与界面绑定的那个vm的原型上,
//因此页面上的所有组件都能通过this.$bus访问这个新vm对象
};
export default install;
 
 
 
 
//main.js文件
import VueBus from './vue-bus'
Vue.use(VueBus);
 
//组件文件中:
任意业务中都可以通过调用来绑定事件,触发事件并传值,和销毁事件 
this.$bus.on(event,callback) 
this.$bus.off(event,callback) 
this.$bus.emit(event, ...args)
 
示例:
组件1:
 this.$bus.on('changedFormObject',(val) =>{
	        //接受并处理传过来的值:val
            this.msg = val;
        });
 
组件2:
this.$bus.emit('changedFormObject',this.inputValue);
//把组件2的data中的给inputValue值传给组件1

案例

main.js

import Vue from 'vue'
import App from './App.vue'
Vue.prototype.$bus=new Vue({
	data:{
		arr:[]
	},
	methods:{
		on(eventname,callback){
			if(this.arr.includes(eventname)){
				throw "eventname events already regist!!"
			}else{
				this.arr.push(eventname)
				this.$on(eventname,callback)
			}		
		},
		emit(eventname,...arg){
			
			this.$emit(eventname,...arg)
		},
		off(eventname,callback){
			// this.arr删除eventname
			this.$off(eventname,callback)
		}
	}
})
var vm=new Vue({
  render: h => h(App),
})
vm.$mount('#app')
 

APP.vue

<template>
	<div>
		<Box1></Box1>
	</div>
</template>
<script>
	import Box1 from "./Box1.vue"
	export default {
		methods:{},
		components:{
			Box1
		}
	}
</script>
<style>
</style>
 

Box1.vue

<template>
	<div>
			<Box2></Box2>
			<Box3></Box3>
	</div>
</template>
<script>
	import Box2 from "./Box2"
	import Box3 from "./Box3"
	export default {
		components:{
			Box2,
			Box3
		}
	}
</script>
<style>
</style>

Box2.vue

<template>
	<div>
		<h1>box2</h1>
	</div>
</template>
<script>
	export default {
		mounted() {
			// this.$root.$on("box3data",(arg)=>{
			// 	console.log(arg,"box2组件内部的打印")
			// })
			// this.$bus.$on("box3data",(arg)=>{
			// 	console.log(arg,"box2组件内部的打印")
			// })
			this.$bus.on("box3data",(arg)=>{
				console.log(arg,"box2组件内部的打印")
			})
		}
	}
</script>
<style>
</style>

Box3.vue

<template>
	<div>
		<h1>box3</h1>
		<button @click="fn">box3--给box2传值</button>
	</div>
</template>
<script>
	export default {
		methods: {
			fn() {
				// this.$root.$emit("box3data","box3的数据")
				// this.$bus.$emit("box3data", "box3的数据")
				this.$bus.emit("box3data", "box3的数据")
			}
		}
	}
</script>
<style>
</style>

9、Vue 依赖注入 - Provide/Inject(重点)

提供者/消费者
通常情况下,组件向组件传递数据,可以采用父子props层层传递,也可以使用busVuex直接交互。在Vue2.2.0之后,Vue还提供了provide/inject选项

官网不建议在应用中直接使用该办法,理由很直接:他怕你"管不好"

案例

App.vue

<template>
   <div>
      <Box1></Box1>
      <button @click="change1">change1</button>
   </div>
</template>

<script>
import Box1 from "./Box1.vue";
export default {
   data() {
      return {
         msg: "app组件提供的数据",
      };
   },
   provide: function () {
      // provide 选项应该是:一个对象或返回一个对象的函数
      //data->provide->created->mounted
      //inject 选项应该是:一个字符串数组,inject:["msg","n"]
      return { msg: this.msg };
   },
   //provide和inject选项需要一起使用,它允许祖先组件向其所有子孙组件注入依赖,
   //并在其上下游关系成立的时间里始终生效,不论组件层级有多深。
   //provide 和 inject 绑定并不是可响应的。这是刻意为之的。
   methods: {
      change1() {
         this.msg = "6666";
      },
   },
   components: {
      Box1,
   },
   mounted() {
      console.log(this.msg, 111111111111);
   },
};
</script>

Box1.vue

<template>
	<div>
		<Box2></Box2>
		<p>box1---{{msg}}</p>
	</div>
</template>

<script>
	import Box2 from "./Box2.vue"
	export default {
		inject:["msg"],
		components:{
			Box2
		},
		provide:{n:100}
	}
</script>

<style>
</style>

Box2.vue

<template>
	<div>
		<h1>box2---{{msg}}--{{n}}</h1>
	</div>
</template>

<script>
	export default {
		inject:["msg","n"],
		data() {
			return {
				
			}
		},
		methods:{},
		mounted() {
			console.log(this.msg)
		},
	}
</script>

<style>
</style>

10、组件传值——provid-inject 响应式设计

provide-inject 父级组件给子级组件的值是基本数据类型的话,就不会是响应式的

解决方法:
1、provide写成一个函数返回对象的形式,然后给自己组件提供data中的引用数据
2、provide写成一个函数返回对象的形式,然后给自己组件提供data中的基本数据用一个函数的返回形式,自己组件中用computed对injecy接受的数据监听改变

案例:

如上

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值