目 录CONTENT

文章目录

Vue组件之间的数据传递(通信、交互)详解

俊阳IT知识库
2023-01-29 / 0 评论 / 0 点赞 / 228 阅读 / 4,348 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2023-01-29,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。
广告

文章已同步至掘金:https://juejin.cn/post/6844903911564787720
欢迎访问😃,有任何问题都可留言评论哦~

默认情况下数据不能在组件之间共享,但是可以通过以下几种方法实现组件之间的数据传递:

props

父子组件之间的数据传递:

  • 父传子
  • 子传父

父传子

  • 确定父中有数据
  • 在父组件的模板中通过属性绑定,把数据绑到子组件上
  • 在子组件中定义props属性,用来接收父传递过来的数据
  • 在子组件的模板就可以直接使用接收过来的数据

父绑定数据,子接收数据

详细的格式:

Props:{
	数据项名字:{
		type:类型。指明从父组件中传递过来的数据必须是什么类型。它的取值是:Object,Array,String,Number,Boolean 都是构造器。不要写成字符串  
		default://默认值。当父组件没有传数据时,就用这个值  
		required:true/false 。是否必须一定要传递过来
	}
}

子传父

  • 在父组件的模板中,给子组件添加一个事件监听
  • 在子组件中,某个时间通过this.$emit发出这个事件,发出事件的同时可以携带数据 (this.$emit(“事件名”,附加的数据))
  • 当父中的方法触发,数据作用与这个方法的第一个参数

父传子传孙,只能一级一级的传,不能跨级传

示例:

<body>
    <!-- ******************************************************************** -->
    <!-- 父传子 -->
    <!-- <div id="app">
        <h1>父组件 ->数据:{{num}}</h1>
        <hr>
        <son :sonnum="num" :sonname="name"></son>
    </div>

    <template id="son">
        <div>
            子组件 -> 数据:{{mysum}} -> {{sonname}}
            <button @click="sonnum=200">修改数据为200</button>
            <h2>{{mysum}}</h2>
            <button @click="mysum=200">修改数据为200</button>
        </div>
    </template> -->
    <!-- ******************************************************************** -->

    <!-- 子传父 -->
    <div id="app">
        <h1>父组件</h1>

        <son @submitmsg="addmsg"></son>
        <h2>{{a}}</h2>
    </div>

    <template id="son">
        <div>
            <h3>子组件</h3>
            <button @click="fashe">发射</button>
        </div>
    </template>


    <script>
        //  子传父************************************************************************
        let Son = {
            template: "#son",
            data() {
                return {
                    num:111,
                }
            },
            methods: {
                fashe() {
                    this.$emit("submitmsg", this.num)
                }
            }
        }
        let vm = new Vue({
            el: "#app",
            data: {
                a: 0,
            },
            methods: {
                addmsg(info) {
                    this.a = info
                }
            },
            components: {
                Son,
            }
        })

//  父传子*************************************************************************       
        // let Son = {
        //     template: "#son",
        //     data() {
        //         return {
        //             mysum: this.sonnum,

        //         }
        //     },
        //     props: {
        //         sonnum: Number,
        //         sonname: {
        //             type: String,
        //             // required:true,
        //             default: "jun",
        //         }
        //     },
        //     methods: {

        //     }
        // }
        // let vm = new Vue({
        //     el: "#app",
        //     data: {
        //         num: 100,
        //         name: "fan"
        //     },
        //     methods: {

        //     },
        //     components: {
        //         Son,
        //     }
        // })
//********************************************************************************
    </script>
</body>

这么多东西,相信你也懒得看,你可以自己建一个文件,复制到里面测试一下

$attrs

如果想要把父组件的数据传递给子子组件,如果使用props绑定来进行信息的传递,显然是比较繁琐的
为了解决该需求,引入了 $attrs

$attrs 可以收集父组件中的所有传过来的属性除了那些在组件中没有通过 props 定义的

示例:
首先有三个组件A-B-C,然后想A中的属性传入C中,基本的做法是这样的,一层一层通过 props 往下传递

<template>
  <div id="app">
    A{{msg}}
    <component-b :msg="msg"></component-b>
  </div>
</template>
<script>
let vm = new Vue({
  el: "#app",
  data: {
    msg: "100"
  },
  components: {
    ComponentB: {
      props: ["msg"],
      template: `<div>B<component-c :msg="msg"></component-c></div>`,
      components: {
        ComponentC: {
          props: ["msg"],
          template: "<div>C{{msg}}</div>"
        }
      }
    }
  }
});
</script>

ComponentB 组件中并没有使用到父组件传递过来的属性 msg,但是这样写就是想把属性再传递给ComponentC,那么除了这种写法还可以给ComponentC绑定$attrs属性。

<script>
let vm = new Vue({
  el: "#app",
  data: {
    msg: "100"
  },
  components: {
    ComponentB: {
      inheritAttrs: false,
      template: `<div>B<component-c v-bind="$attrs"></component-c></div>`,
      components: {
        ComponentC: {
          props: ["msg"],
          template: "<div>C{{msg}}</div>"
        }
      }
    }
  }
});
</script>

这样就可以很方便的做到数据传递,使用起来也比较简单,避免多写 props 的痛苦

一般在使用 $attrs 时,都会加上 inheritAttrs:false 它的作用就是没有用到的数据,就不会显示在DOM结构上

$listeners

说完了 $attrs,知道了怎么把数据从A传递给C,那么此时我们又想到了一个问题,怎么把C组件的信息同步到A组件呢? 这时就用到了 $listeners

当组件的根元素不具备一些DOM事件,但是根元素内部元素具备相对应的DOM事件,那么可以使用 $listeners 获取父组件传递进来的所有事件函数,再通过v-on="xxx"绑定到相对应的内部元素上即可。 简单的来说,就是父组件向子组件传递的所有方法都存在在$listeners

有时候我们会使用 .native 修饰符把原生事件绑定到组件上,但是这样存在弊端,如果组件的根元素不能使用 某事件时,这个绑定就会失效,而且还不容易控制它的事件范围,所以我们一般不用这个修饰符

示例:

//父组件  
<template>
  <div>
    ParentPage
    <button @click="handleClick">ParentClick</button>
    <Child @customClick="handleClick" />
  </div>
</template>

<script>
import Child from "./Child";
export default {
  name: "ParentPage",

  components: {
    Child
  },

  methods: {
    handleClick() {
      alert("hello");
    }
  }
};
</script>
//子组件
<template>
  <div>
    ChildPage
    <!-- <button @click="$emit('customClick')">ChildClick</button> -->
    <button @click="$listeners.customClick">ChildClick</button>
  </div>
</template>

<script>
import SubChild from "./SubChild.vue";
export default {
  name: "ChildPage",
  components: {
    SubChild
  },
  data() {
    return {};
  }
};
</script>

当多层组件引用时,子组件传递父组件方法 v-on="$listeners" 至子子组件

// 子组件
<template>
  <div>
    ChildPage
    <!-- <button @click="$emit('customClick')">ChildClick</button> -->
    <button @click="$listeners.customClick">ChildClick</button>
    <SubChild v-on="$listeners" />
  </div>
</template>

<script>
import SubChild from "./SubChild.vue";
export default {
  name: "ChildPage",
  components: {
    SubChild
  },
  data() {
    return {};
  }
};
</script>
// 子子组件
<template>
  <div>
    SubChildPage
    <button @click="$listeners.customClick">SubChildClick</button>
  </div>
</template>

<script>
export default {
  name: "SubChildPage",
  data() {
    return {};
  }
};
</script>

相信如果好好看了以上的代码,你就会理解的

$emit

1.父组件可以使用props把数据传给子组件
2.子组件可以使用 $emit 触发父组件的自定义事件

vm.$emit( event, arg ) //触发当前实例上的事件
vm.$on( event, fn );//监听event事件后运行 fn;

示例:

//父组件
<template>
  <div>
    <div>$emit子组件调用父组件的方法并传递数据</div>
    <h1>父组件数据:{{msg}}</h1>
    <emit-ch @updateInfo="updateInfo" :sendData="msg"></emit-ch>
  </div>
</template>
<script>
import emitCh from "./$emitCh";
export default {
  name: "emitFa",
  components: { emitCh },
  data() {
    return {
      msg: "北京"
    };
  },
  methods: {
    updateInfo(data) {
      // 点击子组件按钮时触发事件
      console.log(data);
      this.msg = data.city; // 改变了父组件的值
    }
  }
};
</script>
<template>
  <div class="train-city">
    <h3>父组件传给子组件的数据:{{sendData}}</h3>
    <br />
    <button @click="select()">点击子组件</button>
  </div>
</template>

<script>
export default {
  name: "emitCh", // 相当于一个全局 ID,可以不写,写了可以提供更好的调试信息
  props: ["sendData"], // 用来接收父组件传给子组件的数据
  data() {
    return {};
  },
  computed: {},
  methods: {
    select() {
      let data = {
        city: "杭州"
      };
      this.$emit("updateInfo", data); // select事件触发后,自动触发updateInfo事件
    }
  }
};
</script>

总体来说,就是用子组件触发父组件里的方法,子组件里面this.$emit里的this,就指的是父组件

$refs

$refs 的使用方法就是在元素或组件标签上添加ref属性指定一个引用信息,引用信息将会注册在父组件的$refs对象上,在js中使用$refs来指向DOM元素或组件实例;

  • 首先给你的子组件做标记:
    <firstchild ref="test"></firstchild>

  • 然后在父组件中,通过 this.$refs.test 就可以访问这个子组件,包括访问子组件里的 data 里的数据,并且还可以调用它的函数

$parent$children

说了上面的 $refs 接下来说说 $parent$children

  • 使用 this.$parent 可以查找当前组件的父组件。
  • 使用 this.$children 可以查找当前组件的直接子组件,可以遍历全部子组件, 需要注意 $children 并不保证顺序,也不是响应式的。

当然你也可以使用 this.$root 来查找根组件,并可以配合$children遍历全部组件。

注:这两个都是不限制距离的,就是说可以直接查找到最外层数据或者最内层数据,当然,如果你能很清楚的知道子组件的顺序,你也可以用下标来操作

示例:

//父组件
<template>
  <div class="game">
    <h2>{{ msg }}</h2>
    <LOL ref="lol"></LOL>
    <DNF ref="dnf"></DNF>
  </div>
</template>
<script>
import LOL from "@/components/game/LOL";
import DNF from "@/components/game/DNF";
export default {
  name: "game",
  components: {
    LOL,
    DNF
  },
  data() {
    return {
      msg: "Game",
      lolMsg: "Game->LOL",
      dnfMsg: "Game->DNF"
    };
  },
  methods: {},
  mounted() {
    //注意 mounted

    //读取子组件数据,注意$children子组件的排序是不安全的
    console.log(this.$children[0].gameMsg); //LOL->Game

    //读取命名子组件数据
    console.log(this.$refs.dnf.gameMsg); //DNF->Game

    //从根组件查找组件数据
    console.log(this.$root.$children[0].msg); //APP
    console.log(this.$root.$children[0].$children[0].msg); //Game
    console.log(this.$root.$children[0].$children[0].$children[0].msg); //Game->LOL
    console.log(this.$root.$children[0].$children[0].$children[1].msg); //Game->DNF
  }
};
</script>
//子组件LOL  
<template>
  <div class="lol">
    <h2>{{ msg }}</h2>
  </div>
</template>

<script>
export default {
  name: "LOL",
  data() {
    return {
      msg: "LOL",
      gameMsg: "LOL->Game"
    };
  },
  methods: {},
  created() {
    //直接读取父组件数据
    this.msg = this.$parent.lolMsg;
  }
};
</script>
//子组件DNF
<template>
  <div class="dnf">
    <h2>{{ msg }}</h2>
  </div>
</template>

<script>
import Bus from "../../utils/bus.js";
export default {
  name: "DNF",
  data() {
    return {
      msg: "DNF",
      gameMsg: "DNF->Game"
    };
  },
  methods: {},
  created() {
    //从根组件向下查找父组件数据
    this.msg = this.$root.$children[0].$children[0].dnfMsg;
    //this.msg = this.$children.dnfMsg;
  }
};
</script>

上面的有些是使用下标的,当然也可以不使用下标,直接 $parent 获取父组件的实例 $children获取所有的子组件

provideinject

provideinject使用场景也是组件传值,尤其是祖父组件–子子组件等有跨度的组件间传值,单向传值(由provide的组件传递给inject的组件)。
不推荐使用

  • provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的属性。
  • inject 通常是一个字符串数组。

示例:

//父组件
<template>
  <div>
    <father-dom>
    </father-dom>
  </div>
</template>
<script>
import sonDom from "./sonDom.vue";
export default {
  provide: {
    fooNew: "bar"
  },
  data() {
    return {};
  },
  components: { sonDom },
  methods: {}
};
</script>
//子组件
<template>
  <div>
    <child-dom></child-dom>
  </div>
</template>
<script>
import childDom from "./childDom.vue";
export default {
  name: "son-dom",
  components: { childDom }
};
</script>
//子子组件  
<template>
  <div>
    <p>fooNew:{{fooNew}}</p>
  </div>
</template>
<script>
export default {
  name: "childDom",
  inject: ["fooNew"],
  methods: {}
};
</script>

Vuex

接下来说一下我们的压轴好戏,最 6 的一种数据传递方式:Vuex

当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏。

  • 多个视图依赖于同一状态。
  • 来自不同视图的行为需要变更同一状态。

对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理呢?在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!

另外,通过定义和隔离状态管理中的各种概念并强制遵守一定的规则,我们的代码将会变得更结构化且易维护。

Vuex

  • state
    Vuex里的state相当于一个全局的state,你可以在component的任何地方获取和修改它。使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
//获取state
this.$store.state.count

//vuex的辅助方法
import { mapState } from 'vuex'
computed:mapState([
  'count'
])
  • getters
    Vuex里的getters类似于computed,有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数,如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。
    Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
//直接使用
this.$store.getters.doneTodosCount

//使用辅助方法
import { mapGetters } from 'vuex'
computed:mapGetters({
  doneCount: 'doneTodosCount'
})
  • mutations
    更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。使用常量替代 mutation 事件类型在各种 Flux 实现中是很常见的模式。这样可以使 linter 之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个 app 包含的 mutation 一目了然。用不用常量取决于你——在需要多人协作的大型项目中,这会很有帮助。但如果你不喜欢,你完全可以不这样做。
    一条重要的原则就是要记住 mutation 必须是同步函数,mutation 中的异步函数中的回调,当 mutation 触发的时候,回调函数还没有被调用,devtools 不知道什么时候回调函数实际上被调用——实质上任何在回调函数中进行的状态的改变都是不可追踪的。(通常请求和计时器方法都是异步函数)
//触发mutations
this.$store.commit('xxx')

//辅助函数
import { mapMutations } from 'vuex'
methods:mapMutations(['increment' ])
  • actions
    对于Action的官方解释如下。
    类似于 mutation,不同在于:
  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。

个人理解如下:
如果有异步的复杂逻辑并且可以重复调用就使用Action。

//触发action
store.dispatch('increment')

//辅助函数
import { mapActions } from 'vuex'
methods:mapActions(['increment' ])
  • Module
    由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。

为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

相信看了本篇文章,你肯定会对组件之间的数据传递和通信有了更深的理解


^_<

0

评论区