目录
1. 属性传值
1.1 语法
1.2 属性和数据源同名
2. 反向传值
2.1 属性绑定+自定义事件
简单案例:
购物车算总价案例:
2.2 v-model 组件的双向数据绑定
3. 透传(多层组件传值)
3.1 类型透传
3.2 属性穿透 v-bind="$attrs"
3.3 方法穿透 v-on="$listeners"
4. 获取组件
5. 依赖provide注入indect
1. 属性传值
1.1 语法
父组件中,其中msg是父组件的数据
// 静态传值 直接将静态数据hello传过去
<Box1 title="hello"></Box1>
// 动态传值 msg为app父组件的数据源
<Box1 :title="msg"></Box1>
子组件中,当数据源中没有同名的数据时,可以通过this来访问这个属性
<!-- template标签中——使用 -->
<h2>box1组件-----{{ title }}</h2>
//script标签中——注册
props:["title"],
methods: {
fn() {
console.log(this.title);
}
},
结果:
1.2 属性和数据源同名
当属性和数据源中的data的数据同名时:vue2.x劫持的顺序不同 所有有优先级;vue3.x data中的同名数据不做代理(不生效)
子组件的模板中显示的是属性传值过来的内容,访问时,得到的是子组件本身数据源中的数据,并且会报警告。
子组件不能通过this直接修改这个同名变量(vue2.x可以直接修改同名变量的值,且子组件模板会刷新,但父组件数据不变;vue3.x不可以,会报错,title为只读)
原因: 单向数据流:数据只能由父级组件流向子组件(父级组件的数据源更新,会重新刷新模板,重新传新的属性,两个模板都会刷新)。反过来,数据不能由子级组件流向父级组件(会造成死循环),否则程序混乱且难以管理
注:vue2.0 一个template标签只能有一个根元素(简单说就是只能有一个div标签,其余的都在这个div中写);vue3.0解决了这个问题,但还是使用2.0的习惯吧
补充知识:函数声明在a作用域,在b作用域调用,那它就在声明作用域中运行
2. 反向传值
像上面可以知道,子组件不能修改父组件传过来的值,但在实际开发中还是需要通过子组件来修改父组件中的数据,那这个时候就需要通过反向传值,将子组件的值传给父组件。
2.1 属性绑定+自定义事件
子级组件想要修改父级组件的数据,需要绑定事件,将子级组件修改的数据通过事件的实参传给父级组件,然后再把父级组件的数据改为子级组件传过来的数据,从而刷新页面。
语法:
父组件:监听事件<Box :title="msg" @myevent="(el)=>{this.msg=el}" />
Box子组件内部:触发事件 this.$emit("myevent","传参数")
简单案例:
//父组件
<Box1 :title="msg" @myevent="box1_change_msg"></Box1>
//方法,监听自定义事件myevent
methods: {
box1_change_msg(arg) {
this.msg = arg;
}
}
//box1子组件
<h2>box1组件-----{{ title }}</h2>
<button @click="change_app_data">box1_change_app_data</button>
//方法:自定义事件myevent 触发事件 将要修改的数据传过去
methods: {
change_app_data() {
this.$emit("myevent","box1向app反向传过去的数据")
}
}
购物车算总价案例:
前面文章中有用html实现购物车的功能,现在将这个功能用Vue框架来实现,体现了反向传值(子组件的按钮点击了,不能直接修改父组件中的count,需要利用属性绑定和自定义事件来实现数据的反向传值)
父组件(App):
<template>
<div>
<Goodsbox v-for="(el, index) in arr" :goods="el" :index="index" @decrease="fn" @increase="fn2"></Goodsbox>
<p>总价:{{ total }}元</p>
</div>
</template>
<script>
import Goodsbox from "./components/Goodsbox.vue"
export default {
data() {
return {
arr: [
{ id: 1, title: "goods1", price: 12, count: 0 },
{ id: 2, title: "goods2", price: 3, count: 0 },
{ id: 3, title: "goods3", price: 22, count: 0 },
{ id: 4, title: "goods4", price: 33, count: 0 },
]
}
},
methods: {
fn(arg) {
if (this.arr[arg].count) {
this.arr[arg].count--;
}
},
fn2(arg) {
this.arr[arg].count++;
}
},
computed: {
total() {
return this.arr.reduce((n1, n2) => {
return n1 + n2.count * n2.price
}, 0)
}
},
components: {
Goodsbox
}
}
</script>
子组件(Goodsbox):
<template>
<div>
商品名:{{ goods.title }}---
价格:{{ goods.price }}---
数量:<button @click="decrease">-</button>
{{ goods.count }}
<button @click="increase">+</button>
</div>
</template>
<script>
export default {
//注册属性
props: ["goods", "index"],
methods: {
decrease() {
// 自定义事件名 通过this访问父级组件传过来的index下标
this.$emit("decrease", this.index);
},
increase() {
this.$emit("increase", this.index);
}
}
}
</script>
2.2 v-model 组件的双向数据绑定
语法:
父组件:<Box2 v-model:value="msg"></Box2>
vue2.0 v-model是语法糖: :value="msg" @input="(arg)=>{this.msg=arg}"
vue3.0 v-model:value是语法糖 :value="msg" @update:value=(arg)=>{ msg = arg}
子组件:注册属性,方法中使用this.$emit("update:"属性名","要传的数据")
简单案例:
// 父组件
<Box2 v-model:value="msg" v-model:keywords="key"></Box2>
//父组件数据源
data() {
return {
msg: "app组件中的数据",
key: "app中的key数据",
}
}
//子组件Box2
<h2>box2组件-----{{ value }}----{{ keywords }}</h2>
<button @click="v_model_passValue">v_model_passValue</button>
//注册属性
props: ["value","keywords"],
//方法 传值
methods: {
v_model_passValue() {
this.$emit("update:value", "box2传给app的value数据");
this.$emit("update:keywords","box2传给app的keywords数据")
}
}
3. 透传(多层组件传值)
3.1 类型透传
语法:<Box3 class="box" />
父组件中设置了类名,子组件渲染时会自动加上父组件设置的类名;子组件也有类名时,会合并两个类名。
父级组件绑定属性 子组件中给孙组件绑定穿透属性:v-bind="$attrs" 事件透传 同理:v-on="$listeners"
3.2 属性穿透 v-bind="$attrs"
Box3将属性传给下一级组件(Box4)时,自己Box3是不能注册该属性的,才能传给下一子级。
//父组件 app中 通过属性传值
<Box3 class="box" :count="num"></Box3>
//子组件 box3
<div class="box3">
<h2>box3组件----{{ count }}</h2>
<Box4 v-bind="$attrs"></Box4>
</div>
//要传给自己,就不注册;不传,就注册来自己用
// props:["count"],
//孙组件 box4
<h2>box4组件----{{ count }}</h2>
//注册的属性名应该为父组件的属性名
props:["count"],
如果即要自己用,又想传给自己的子组件时,使用$attrs属性透传。$attrs中保存了父组件传给一级子组件的所有属性值,后代组件可以通过this.$attrs来访问。一级子组件Box3不注册,要透传属性给谁,就在这个标签上面添加v-bind:$attrs,但不能注册。使用时:this.$attrs.属性名
//父组件 app中 通过属性传值
<Box3 class="box" :count="num"></Box3>
//子组件 box3
<div class="box3">
<h2>box3组件----{{ this.$attrs.count }}</h2>
<Box4 v-bind="$attrs"></Box4>
</div>
//孙组件 box4
<h2>box4组件----{{ this.$attrs.count }}</h2>
// 属性透传给box5
<Box5 v-bind="$attrs"></Box5>
// 曾孙组件 box5
<h2>box5组件----{{ count }}</h2>
//注册的属性名应该为父组件的属性名
props:["count"],
3.3 事件穿透 v-on="$listeners"
vue2.x版本中:父组件app绑定的事件,想要通过孙组件Box4来触发该事件时,需要给子组件Box3添加v-on="$listeners",将事件穿透过去,然后在Box4中来触发该事件。
vue3.x版本中,直接通过v-bind="$attrs"就可以实现事件的穿透,$listenners弃用。
4. 获取组件
$parent/$root/$options——方便,但尽量不要用,会引起不必要的麻烦,不好解决
$refs(获取组件中节点或者组件)
<h2>box4组件----{{ count }}</h2>
<Box5></Box5>
<p ref="p1">box4中的p1标签</p>
<button @click="box4_btn">box4_btn</button>
methods: {
box4_btn() {
// 上级元素
console.log(this.$parent);
// 根级元素
console.log(this.$root);
// 子级元素
console.log(this.$options.components);
// 获取组件中节点或者组件
console.log(this.$refs.p1);
}
}
5. 依赖provide注入indect
父级组件提供数据,组件链上的子级组件注入并使用。
简单案例:
// 父组件 app
provide: {
msgGlobal: 'provided by app',
numGlobal: 999
}
// 子组件们 box2 box3均相同
// 使用
<h2>global---{{ msgGlobal }}</h2>
<h2>numGlobal----{{ numGlobal }}</h2>
// 注入
inject: ["msgGlobal", "numGlobal"],
虽然看起来很好用,但官方还是不建议使用,因为害怕使用者管理不好,容易出错。
总结:
1. 属性传值 <Box title="静态传值" /> <Box title="变量" /> Box内部设计的时候,可以props:["属性名"] 或 props:{属性名:验证函数}
父组件的数据源更新了就会让子组件接收新的属性值
父组件给子组件传的数据,可以通过子组件再传给它自己的子组件
2. 反向传值(子组件给父组件传值)重点
因为父组件属性传值给子组件后,子组件不能直接修改属性值(框架设计时单项数据流:数据只能由父级组件流给子级组件)。父组件监听事件(绑定函数),子组件触发事件(调用函数传数据)
语法:<Box :title="msg" @myevent="(el)=>{this.msg=el}" />
Box内部:this.$emit("myevent","传参数")
组件的双向数据绑定: <Box v-model:title="msg" />
Box内部:this.$emit("update:title","参数")
3 .透传(多层组件传值)
<Box class="box" @click="fn /> box类名会直接绑定在box组件的根元素上
<Box :title="msg" @myevent2="fn />
Box内部可以直接传给下一级组件 <Box2 v-bind="$attrs" v-on="$listeners" />
4. 获取组件 $parent $root $options.components
5. 提供provide注入inject
父级组件提供数据,子级组件注入并使用
6. 中央事件总线、仓库——兄弟组件间的传值