目录
4 $ parent/$root、$children/$refs(了解)
6Vue 依赖注入 - **Provide/Inject(重点)**
组件高级
父组件通过属性给子组件传值: 子组件的props接受数据
在页面模板中 使用变量名:属性 data 计算属性(重点)
//注意属性传值是单向的
APP.vue
- <div>
- <Box
- v-for="(item, index) in arr"
- :title="item.title"
- :price="item.price"
- :count="item.count"
- :key="item.id"
- >
- Box>
- <button>总价:{{ total }}button>
-
- 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
- <div>
- <div>
- {{ title }}----{{ price }}元 数量:
- <button>-button>
- <span>{{ count }} span>
- <button @click="add">+button>
- div>
- div>
- <script>
- export default {
- props: ["title", "price", "count"],
- methods: {
- add() {
- console.log(this.count);
- this.count++;
- },
- },
- };
- script>
- <style>
- style>
2.1 子组件通过自定义事件给父组件传新值 $emit
子组件通过自定义事件给父组件传count的新值n,父组件收到新值后修改自己的data,自然就会刷新自己 和子组件的模板
子组件通过调用父组件的方法给父组件传值:子组件的自定义事件中,用$emit触发事件调用父组件方法给父组件传值 (重点)
**因为通过属性传值是单向的**,有时候我们需要子组件的data 数据需要交给父组件使用:
通过在子组件上定义自定义事件,在子组件中通过$emit 来触发事件;子组件的事件被触发并传参,事件处理函数可以接收到子组件的数据;事件绑定的事件处理函数在父节点上,故可在事件处理函数中用到子组件的数据值来修改父节点的数据。
- //父组件中:
"handleEvent"> - //myevent是事子组件的自定义事件
- //handleEvent是绑定的父组件的方法
-
-
- 子组件中:
- 在任意业务中触发事件:this.$emit("myevent","要给父组件传的数据")
app.vue
- <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>
- <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
- <div>
- <div>{{title}}----{{price}}元
- 数量:
- <button>-button>
- <span>{{count}} span>
- <button @click="add">+button>
- div>
- div>
- <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","数据更新了“)
父组件==>
app.vue
- <div class="app">
- <h1>app组件--msg-{{msg}}--{{msg2}}h1>
-
- <Box :a1.sync="msg">Box>
-
- <Box :a1.sync="msg" :a2.sync="msg2">Box>
- div>
- <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
- <div class="box">
- <h2>box组件---a1-{{a1}}--{{a2}}h2>
- <button @click="change1">修改a1中的数据button>
- <button @click="change2">修改a2中的数据button>
- div>
- <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
父组件:
(v-model这是一个语法糖==>
) 子组件:
props:["value"],,然后上面的标签使用这个{{value}}
点击写事件:fn(){this.$emit("input","传值")}
v-model.语法==>官网学
APP.vue
- <div class="app">
-
- <p>app--{{msg}}p>
- <Box v-model="msg">Box>
- div>
- <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
- <div class="box">
- <h2>box--{{value}}h2>
- <button @click="change1">chaneg1button>
- div>
- <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>
在不用状态管理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
- <div class="app">
- <h1>app-{{msg}}h1>
- <button @click="change1">点击修改app组件的msgbutton>
- <Box1 :b1="msg" @x="xchange">Box1>
- div>
- <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
- <div class="box1">
- <h1>{{$attrs.b1}}h1>
- <Box2 v-bind="$attrs" v-on="$listeners">Box2>
- div>
- <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
- <div class="box2">
- <h3>box2--{{b1}}h3>
- <button @click="change">changebutton>
- div>
- <script>
- export default {
- props:["b1"],
- methods:{
- change(){
- this.$emit("x","box2修改了数据")
- }
- }
- }
- script>
- <style>
- .box2 {
- width: 300px;
- height: 200px;
- background-color:goldenrod;
- margin: 20px;
- }
- style>
这些功能都是有劣势或危险的场景的,官方建议我们尽量避开它们,但是高级点的面试题中会出现
$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):
-
"myInput1"> - <input ref="myInput2">input>
-
- //在父组件中可以通过 this.$refs访问到它:
- methods: {
- focus: function () {
- this.$refs.myInput2.focus()
- }
- }
APP.vue
- <div class="app">
- <h1>app--{{msg}}h1>
- <div class="app2">
- <Box1>Box1>
- div>
- div>
- <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
- <div class="box1">
- <h1 @click="look">box1h1>
- <Box2>Box2>
- <p ref="p1">66666p>
- <p ref="p2">234234p>
- <button @click="getref1">refbutton>
- div>
- <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
- <div class="box2">
- <p>{{$parent.$parent.msg}}p>
- box2 <button @click="change1">changebutton>
- div>
- <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来获取
- <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>
- <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>
通过创建一个新的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
- <div>
- <Box1>Box1>
- div>
- <script>
- import Box1 from "./Box1.vue"
- export default {
- methods:{},
- components:{
- Box1
- }
- }
- script>
- <style>
- style>
-
Box1.vue
- <div>
- <Box2>Box2>
- <Box3>Box3>
- div>
- <script>
- import Box2 from "./Box2"
- import Box3 from "./Box3"
- export default {
- components:{
- Box2,
- Box3
- }
- }
- script>
- <style>
- style>
Box2.vue
- <div>
- <h1>box2h1>
- div>
- <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
- <div>
- <h1>box3h1>
- <button @click="fn">box3--给box2传值button>
- div>
- <script>
- export default {
- methods: {
- fn() {
- // this.$root.$emit("box3data","box3的数据")
- // this.$bus.$emit("box3data", "box3的数据")
- this.$bus.emit("box3data", "box3的数据")
- }
- }
- }
- script>
- <style>
- style>
提供者/消费者
通常情况下,**父**组件向**孙**组件传递数据,可以采用父子`props`层层传递,也可以使用`bus`和`Vuex`直接交互。
在Vue2.2.0之后,Vue还提供了`provide/inject`选项
官网不建议在应用中直接使用该办法,理由:比较混乱,较难管理。
- //爷爷
- <div>
- <p>{{ title }}p>
- <son>son>
- div>
- <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
- <div>
- <Box1>Box1>
- <button @click="change1">change1button>
- div>
- <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
- <div>
- <Box2>Box2>
- <p @click="fn">box1---{{ msg() }}p>
- div>
- <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
- <div>
- <h1>box2---{{ msg() }}--{{ n.age }}h1>
- div>
- <script>
- export default {
- inject: ["msg", "n"],
- data() {
- return {};
- },
- methods: {},
- mounted() {
- console.log(this.msg);
- },
- computed: {
- tool() {
- console.log(this.msg(), 11111);
- },
- },
- };
- script>
- <style>
- style>
后面讲
A. 若子组件给父组件传值,可使用 $emit 方法
B. 祖孙组件之间可以使用 provide 和 inject 方式跨层级相互传值
C. 若子组件使用 $emit('say') 派发事件,父组件可使用 @say 监听
D. 若父组件给子组件传值,子组件可通过 props 接受数据选B
A. v-model 能实现双向绑定
B. v-model 本质上是语法糖,它负责监听用户的输入事件以更新数据
C. v-model 是内置指令,不能用在自定义组件上
D. 对 input 使用 v-model,实际上是指定其 :value 和 @input选C,能用
A. 若子组件给父组件传值,可使用 $emit 方法
B. 祖孙组件之间可以使用 provide 和 inject 方式跨层级相互传值
C. 若子组件使用 $emit('say') 派发事件,父组件可使用 @say 监听
D. 若父组件给子组件传值,子组件可通过 props 接受数据选B
A. 可通过 this.$parent 查找当前组件的父组件
B. 可使用 this.$refs 查找命名子组件
C. 可使用 this.$children 按顺序查找当前组件的直接子组件
D. 可使用 $root 查找根组件,并可配合 children 遍历全部组件选C 无顺序