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

默认状况下数据不能在组件之间共享,可是能够经过如下几种方法实现组件之间的数据传递:vue

props

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

  • 父传子
  • 子传父

父传子数组

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

父绑定数据,子接收数据缓存

详细的格式:安全

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

子传父bash

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

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

示例:dom

<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绑定来进行信息的传递,显然是比较繁琐的
为了解决该需求,引入了 $attrside

$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
  }
})
复制代码

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


^_<

相关文章
相关标签/搜索