Vue——组件高级——传值

目录

组件高级

一、传值

1 正向属性传值——父传子 

2反向传值——子传父

2.1 子组件通过自定义事件给父组件传新值 $emit

2.2 反向传值的sync

2.3 反向传值的v-model

3多层组件传值:$listeners/$attrs(了解)

4 $ parent/$root、$children/$refs(了解)

5中央事件总线bus(了解)  event bus

6Vue 依赖注入 - **Provide/Inject(重点)**

7仓库vuex(重点)

二、笔试题 / 面试题

1、关于 Vue 组件间的参数传递,下列哪项是不正确的?

2、下列关于 v-model 的说法,哪项是不正确的?


组件高级

1、传值     2.动态组件    3.缓存组件keep-alive     4.异步组件(高薪意向)

一、传值

1 正向属性传值——父传子 

父组件通过属性给子组件传值: 子组件的props接受数据

在页面模板中 使用变量名:属性  data  计算属性(重点)

//注意属性传值是单向的

APP.vue

<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>
<script>
import Box from "./Box.vue";
export default {
  data() {
    return {
      msg: "hello",
      arr: [],
    };
  },
  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>
<style scoped="scoped" lang="scss">
</style>

 Box.vue

<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>
<style>
</style>

2反向传值——子传父

2.1 子组件通过自定义事件给父组件传新值 $emit

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

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

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

//父组件中:
<my-search @myevent="handleEvent"></my-search>
//myevent是事子组件的自定义事件 
//handleEvent是绑定的父组件的方法


子组件中:
在任意业务中触发事件:this.$emit("myevent","要给父组件传的数据")

app.vue

<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>
	</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() {
			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>
<style scoped="scoped" lang="scss">
</style>

Box.vue

<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(){
				let n=this.count+1  //将count新值传过去
				this.$emit("mycountincrement",n,this.index)
			}
		}
	}
</script>
<style>
</style>

2.2 反向传值的sync

此方法就省略了写事件名字

自定义事件

子组件==>methods里面==>this.$emit("update:a1","数据更新了“)

父组件==>  <Box :a1.sync="msg"></Box>

app.vue

<template>
	<div class="app">
		<h1>app组件--msg-{
   
   {msg}}--{
   
   {msg2}}</h1>
		<!-- <Box @xx="fn" :a1="msg"></Box> -->
		<Box :a1.sync="msg"></Box>
		<!-- sync就省去了点击调用函数了 -->
		<Box :a1.sync="msg" :a2.sync="msg2"></Box>
	</div>
</template>
<script>
	import Box from "./Box.vue"
	export default {
		data() {
			return {
				msg: "app的数据",
				msg2:"app的数据2"
			}
		},
		components: {
			Box
		},
		methods:{//省去了此methods
			// fn(arg){
			// 	this.msg=arg
			// }
		}
	}
</script>
<style scoped="scoped" lang="scss">
	.app {
		width: 600px;
		height: 400px;
		background-color: darkgray;
		margin: 20px;
	}
</style>

Box.vue

<template>
	<div class="box">
		<h2>box组件---a1-{
   
   {a1}}--{
   
   {a2}}</h2>
		<button @click="change1">修改a1中的数据</button>
		<button @click="change2">修改a2中的数据</button>
	</div>
</template>
<script>
	export default {
		props:["a1","a2"],
		methods:{
			change1(){
				// this.a1="box修改了a1的值"
				// this.$emit("xx","box修改了a1的值")
				this.$emit("update:a1","box修改了a1的值")
			},
			change2(){
				this.$emit("update:a2","66666")
			}
		}
	}
</script>
<style scoped="scoped" lang="scss">
	.box{
		width: 400px;
		height: 200px;
		background-color: cornflowerblue;
		margin: 20px;
	}
</style>

2.3 反向传值的v-model

父组件:

<Box v-model="msg"></Box>   

(v-model这是一个语法糖==>  <Box  :valuel="msg" @input=”fm“></Box>   )

子组件:

props:["value"],,然后上面的标签使用这个{ {value}}

点击写事件:fn(){this.$emit("input","传值")}

v-model.语法==>官网学

APP.vue

<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>

Box.vue

<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>

3多层组件传值:$listeners/$attrs(了解)

在不用状态管理vuex的时候,如何让GrandFather与Son通信,我们可以用可以emit一层一层的传递,==>但是会显得冗余

vue2.4之后,提出$attrs、$listeners ,可以实现跨级组件通信。

$listeners官网解说:事件传递     $attrs官网解说:属性传递

在组件中绑定 可以把当前组件的自定义属性和方法传给子元素使用:

one组件:<two v-bind:xx="100" v-on:twoEvent="fn"></two>
two组件中:<three v-bind="$attrs" v-on="$listeners"></three>
three组件:可以访问two的 属性和触发事件: {
   
   {this.$attrs.xx}} this.$emit("twoEvent",20)

APP.vue

<template>
	<div class="app">
		 <h1>app-{
   
   {msg}}</h1>
		 <button @click="change1">点击修改app组件的msg</button>
		 <Box1 :b1="msg" @x="xchange"></Box1>
	</div>
</template>
<script>
	import Box1 from "./Box1.vue"
	export default {
		data() {
			return {
				msg: "app组件的数据"
			}
		},
		methods:{
			change1(){
				this.msg="app组件修改了msg的数据"
			},
			xchange(arg){
				console.log(arg,666666666)
				this.msg=arg
			}
		},
		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>{
   
   {$attrs.b1}}</h1>
		<Box2 v-bind="$attrs" v-on="$listeners"></Box2>
	</div>
</template>
<script>
	import Box2 from "./Box2.vue"
	export default {
		components:{
			Box2
		},
		methods:{
			look(){
				console.log(this.$attrs)
			}
		}
	}
</script>
<style>
	.box1{
		width: 500px;
		height: 300px;
		background-color:deeppink;
		margin: 20px;
	}
</style>

Box2.vue

<template>
	<div class="box2">
		<h3>box2--{
   
   {b1}}</h3>
		<button @click="change">change</button>
	</div>
</template>
<script>
	export default {
		props:["b1"],
		methods:{
			change(){
				this.$emit("x","box2修改了数据")
			}
		}
	}
</script>
<style>
	.box2 {
		width: 300px;
		height: 200px;
		background-color:goldenrod;
		margin: 20px;
	}
</style>

4 $ parent/$root、$children/$refs(了解)

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

$root:

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

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

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

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


Tips:

1、在Box1中打印this,this.$parent,$root,就会打印自己的VueComponent 、app.vue的VueComponent 、 Vue根节点对象

2、$parent 、$children都是代表组件,不是元素,就算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>

5 中央事件总线bus(了解)  event bus

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

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

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

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

3、给x组件解绑a事件  x.$off("a事件",监听器函数)

 点击事件比mounted后运行

//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>

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

提供者/消费者

通常情况下,**父**组件向**孙**组件传递数据,可以采用父子`props`层层传递,也可以使用`bus`和`Vuex`直接交互。

在Vue2.2.0之后,Vue还提供了`provide/inject`选项

 官网不建议在应用中直接使用该办法,理由:比较混乱,较难管理。

//爷爷
<template>
  <div>
    <p>{
   
   { title }}</p>
    <son></son>
  </div>
</template>
<script>
  import Son from "./son"
  export default {
    name: 'Father',
    components: { Son },
    // provide选项提供变量
    provide: {
      message: 'provided by father'
    },
    data () {
      return {
        title: '父组件'
      }
    },
    methods: { ... }
  }
</script>

//爸爸
<template>
  <div>
    <p>{
   
   { title }}</p>
    <grand-son></grand-son>
  </div>
</template>
<script>
import grandSon from "./grandSon "
export default {
  name: "Son",
  components: { grandSon },
  data () {
    return {
      title: '子组件'
    }
  },
};
</script>
//孙子
<template>
  <div>
    <p>message:{
   
   { message }}</p>
  </div>
</template>
<script>
export default {
  name: "GrandSon",
  inject: [ "message" ],
  data () {
    return {
      title: '孙组件'
    }
  },
  methods: { ... }
};
</script>

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

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

解决方法:

1、provide写成一个函数返回对象的形式,然后给自己组件提供data中的引用数据

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

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 () {
    return { msg: () => this.msg };
  },
  methods: {
    change1() {
      this.msg = "6666";
    },
  },
  components: {
    Box1,
  },
  mounted() {
    console.log(this.msg, 111111111111);
  },
};
</script>

Box1.vue

<template>
  <div>
    <Box2></Box2>
    <p @click="fn">box1---{
   
   { msg() }}</p>
  </div>
</template>
<script>
import Box2 from "./Box2.vue";
export default {
  inject: ["msg"],
  components: {
    Box2,
  },
  data() {
    return {
      obj: { age: 20 },
    };
  },
  provide() {
    return { n: this.obj };
  },
  methods: {
    fn() {
      this.obj.age = 30;
    },
  },
};
</script>
<style>
</style>

Box2.vue

<template>
  <div>
    <h1>box2---{
   
   { msg() }}--{
   
   { n.age }}</h1>
  </div>
</template>
<script>
export default {
  inject: ["msg", "n"],
  data() {
    return {};
  },
  methods: {},
  mounted() {
    console.log(this.msg);
  },
  computed: {
    tool() {
      console.log(this.msg(), 11111);
    },
  },
};
</script>
<style>
</style>

7仓库vuex(重点)

后面讲

二、笔试题 / 面试题

1、关于 Vue 组件间的参数传递,下列哪项是不正确的?

A. 若子组件给父组件传值,可使用 $emit 方法
B. 祖孙组件之间可以使用 provide 和 inject 方式跨层级相互传值
C. 若子组件使用 $emit('say') 派发事件,父组件可使用 @say 监听
D. 若父组件给子组件传值,子组件可通过 props 接受数据

选B

2、下列关于 v-model 的说法,哪项是不正确的?

A. v-model 能实现双向绑定
B. v-model 本质上是语法糖,它负责监听用户的输入事件以更新数据
C. v-model 是内置指令,不能用在自定义组件上
D. 对 input 使用 v-model,实际上是指定其 :value 和 @input

选C,能用

3、关于 Vue 组件间的参数传递,下列哪项是不正确的?

A. 若子组件给父组件传值,可使用 $emit 方法
B. 祖孙组件之间可以使用 provide 和 inject 方式跨层级相互传值
C. 若子组件使用 $emit('say') 派发事件,父组件可使用 @say 监听
D. 若父组件给子组件传值,子组件可通过 props 接受数据

选B

4、下列说法不正确的是哪项?

A. 可通过 this.$parent 查找当前组件的父组件
B. 可使用 this.$refs 查找命名子组件
C. 可使用 this.$children 按顺序查找当前组件的直接子组件
D. 可使用 $root 查找根组件,并可配合 children 遍历全部组件

选C  无顺序

猜你喜欢

转载自blog.csdn.net/qq_52301431/article/details/126718690
今日推荐