文章目录
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组件通信,我们有多少种解决方案?
- 我们使用vuex来进行数据管理,依赖于vuex我们可以一次改变,任何一个组件中都能获取。但是如果多个组件共享状态比较少,使用vuex过于麻烦和难以维护。element-ui中大量采用此方法。
- 自定义vue bus事件总线,原理类似vuex,使用特别简单。bus适合碰到组件跨级兄弟组件等无明显依赖关系的消息传递,原生app开发中经常用到,但是缺点是bus破坏了代码的链式调用,大量的滥用将导致逻辑的分散,出现问题后很难定位,降低了代码可读性。
- 使用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, parent,root,就会打印自己的VueComponent 、app.vue的VueComponent 、 Vue根节点对象
2、 p a r e n t 、 parent 、 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>
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
层层传递,也可以使用bus
和Vuex
直接交互。在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接受的数据监听改变
案例:
如上