目录
4 $ parent/$root、$children/$refs(了解)
6Vue 依赖注入 - **Provide/Inject(重点)**
组件高级
一、传值
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 无顺序