Vue组件之间传递数据的五种方式

Vue 组件之间数据传递的几种方式:

    1. 父组件向子组件传递数据,使用props属性;子组件向父组件中传递数据,在子组件中使用$emit派发事件,父组件中使用v-on
      监听事件;缺点:组件嵌套层次多的话,传递数据比较麻烦。
    1. 祖先组件通过依赖注入(inject / provide)的方式,向其所有子孙后代传递数据;缺点:无法监听数据修改的来源,不支持响应式。
    1. 通过属性root /parent / $children /
      ref,访问根组件、父级组件、子组件中的数据;缺点:要求组件之间要有传递性。
    1. 通过事件总线(event
      bus)的方式,可以实现任意两个组件间进行数据传递;缺点:不支持响应式,这个概念是vue1.0版本中的,现在已经废弃。
  1. 通过 VueJs 的状态管理模式 Vuex,实现多个组件进行数据共享,推荐使用这种方式进行项目中各组件间的数据传递。
    下面详细介绍数据传递的几种方式:

1.props/$emit

prop 是在组件上注册的一些自定义的 attribute。就像下面的 :sub-num 。

<div :sub-num="num"></div>

1.1 父组件向子组件中传递数据

1、一个组件默认可以拥有任意数量的 prop,任何值都可以传递给任何 prop。
2、当一个值传递给一个 prop attribute 的时候,它就变成了那个组件实例的一个属性(例如下面的 subNum),通过属性名,我们就能够在组件实例中访问这个值。
父组件向子组件中传递数据,可以在子组件中通过设置props属性来接收传递过来的数据。

<div id="app">
    <div>{{num}}</div>
    <!-- 将父组件中的num,传递给子组件中的sub-num -->
    <blog-count :sub-num="num" :sub-user="user"></blog-count>
</div>

<script>
   const blogCount={
        //子组件中通过props属性接收父组件传递过来的数据
       props:["subNum","subUser"],
       template:`<div>
                   <p>这是从父组件传进来的数字:{{subNum}}</p>
                   <p>这是从父组件传进来的对象:{{subUser.name}}-{{subUser.age}}</p>
               </div>`,

   }
   var vm=new Vue({
       el:"#app",
       data:{
           num:2,
           user:{
               name:"zhangsan",
               age:18
           }
       },
       components:{
            blogCount
       }
   })
</script>

1.2 子组件向父组件传递数据

子组件向父组件传递数据,通过 $emit派发事件,父组件中通过 v-on 接收该事件,拿到传递的数据。

语法:$emit(eventName,data),第一个参数为事件名称,需要跟父组件中 v-on 监听的事件名称一致;第二个参数为要传递的数据。

<div id="app">
    <div>{{num}}</div>
    <!-- 通过 v-on 监听事件-->
    <blog-count @countchange="changeHandle"></blog-count>
</div>

<script>
    const blogCount={
        template:`<button @click="clickHandle">点击接收子组件传递过来的数据</button>`,
        data(){
            return {num:6}
        },
        methods: {
            clickHandle(){
                //使用 $emit派发事件
                this.$emit("countchange",this.num);
            }
        }
    }
    var vm=new Vue({
        el:"#app",
        data:{
            num:0
        },
        methods: {
            changeHandle(data){
                this.num=data;
            }
        },
        components:{
            blogCount
        }
    })
</script>

1.3 .sync 修饰符

如果使用 update:myPropName 的模式触发事件,上面的代码可以写成下面这样:

<div id="app">
   <div>{{num}}</div>
    <blog-count :num="num" @update:countchange="num=$event"></blog-count>
    <!--最终可以简写为-->
    <!--  <blog-count :countchange.sync="num"></blog-count>  -->
</div>

<script>
    const blogCount={
        template:`<button @click="$emit('update:countchange',num)">点击接收子组件传递过来的数据</button>`,
        data(){
            return {num:6}
        }
    })
    var vm=new Vue({
        el:"#app",
        data:{num:0}
    }
</script>

提取出组件的代码为:

<blog-count :num="num" @update:countchange="num=$event"></blog-count>```
为了方便起见,我们为这种模式提供一个缩写,即 .sync 修饰符:

```javascript
<blog-count :countchange.sync="num"></blog-count>

子组件向父组件传递数据,也可以通过使用 .sync 修饰符来完成。

2. 依赖注入 provide inject

  1. 这对选项是2.2.0版本新增的。需要一起使用,它允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
  2. 主要解决了跨级组件间的通信问题。
  3. 在祖先组件中增加属性 provide,它的属性值是一个对象或返回一个对象的函数。该对象包含了给子组件要传递的数据。
  4. 在子组件中增加属性 inject ,用来接收数据,它的选项是一个字符串数组,或一个对象。
<div id="app">
    <div>{{num}}</div>
    <blog-count></blog-count>
</div>

<script>
    const blogCount={
        //子组件中使用inject属性来接收数据
        inject:["num"],
        template:`<div>{{num}}</div>`
    }
    var vm=new Vue({
        el:"#app",
        data:{
            num:10
        },
        //父组件中使用provide属性存放要传递的数据
        provide:function(){
            return {
                num:this.num
            }
        },
        components:{
            blogCount
        }
    })
</script>

依赖注入 provide inject,这种方式的缺点:

  1. 祖先组件不需要知道哪些后代组件使用它提供的属性。
  2. 后代组件不需要知道被注入的属性来自哪里。
  3. 会将应用程序中的组件与它们当前的组织方式耦合起来,使重构变得更加困难。
  4. 所提供的属性是非响应式的。

3. root /parent / $children / ref

  1. 通过 $root 属性访问根实例 new Vue()。
  2. 通过$parent 属性访问父组件的实例。
  3. 通过children 属性访问当前实例的直接子组件。需要注意children 并不保证顺序,也不是响应式的。
  4. 通过 r e f s 属 性 访 问 子 组 件 中 的 数 据 , 子 组 件 标 签 上 加 r e f 的 属 性 。 例 如 在 子 组 件 的 d o m 元 素 上 增 加 属 性 r e f = " a b c " , 就 可 以 使 用 t h i s . refs 属性访问子组件中的数据,子组件标签上加 ref 的属性。例如在子组件的dom元素上增加属性 ref = "abc",就可以使用this.refs属性访问子组件中的数据,子组件标签上加ref的属性。例如在子组件的dom元素上增加属性ref="abc",就可以使用this.refs.abc 拿到这个子组件的实例。
  5. ref,如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例。
  6. $refs 只会在组件渲染完成之后生效,并且它们不是响应式的。
<div id="app">
    <blog-count></blog-count>
</div>

<script>
    const blogItem={
        template:`<div>{{$root.num}}-{{$parent.num}}</div>`,
        data(){
            return {
                num:3
            }
        },
        mounted() {
            //访问根实例中的数据
            console.log("Vue.num:"+this.$root.num);
            //访问父级组件中的数据
            console.log("blogCount.num:"+this.$parent.num);
            //调用父级组件中的方法
            this.$parent.start()
        },
    }
    const blogCount={
        template:`<div><blogItem ref="refItem"></blogItem></div>`,
        data(){
            return {num:6}
        },
        components:{
            blogItem
        },
        methods: {
            start(){
                console.log("blogCount start...");
                console.log(this.$children[0].num)
            }
        },
        mounted() {
            //访问子组件的实例
            console.log(this.$refs.refItem.num);
        },
    }
    var vm=new Vue({
        el:"#app",
        data:{
            num:0
        },
        components:{
            blogCount
        }
    })
</script>

4. event bus 事件总线

事件的触发器,vue 1.0版本使用比较多,现在已经不用。

这种方法可以看作是通过一个空的实例 new Vue()作为事件总线(事件中心),用它来派发和监听事件,可以实现任何组件间的通信,包括父子、兄弟、跨级。缺点:这种传递数据的方式不是响应式。

<div id="app">
    <div>{{num}}</div>
    <blog-count></blog-count>
    <button @click="clickHandle">send</button>
</div>

<script>
    //创建一个空的vue实例
    const eventbus=new Vue();
    //子组件
    const blogCount={
        data(){
            return {num:1}
        },
        template:`<div>{{num}}</div>`,
        mounted() {
            //监听事件
            eventbus.$on("message",(msg)=>{
                this.num=msg;
            })
        }
    }
    var vm=new Vue({
        el:"#app",
        data:{
            num:10
        },
        components:{
            blogCount
        },
        methods: {
            clickHandle(){
                //派发事件
                eventbus.$emit('message',this.num)
            }
        }
    })
</script>

5. vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它的状态存储是响应式的。采用集中式存储管理应用的所有组件的状态,也就是说,对数据的所有操作都要在vuex中进行。

5.1 vuex 原理

vuex 实现了一个单向数据流,在全局拥有一个 State 用来存放数据,当组件用同步的方式更改 State 中的数据时,必须通过 Mutation 进行。当使用异步方式(例如 ajax请求)修改数据,必须先经过 Actions ,再由 Actions 经过 Mutation来操作。

[图片上传失败...(image-f4a68b-1650376415186)]

5.2 store

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。

const store = new Vuex.Store({
    state: {
        //相当于自定义组件中的data
    },
    getters:{
        //相当于自定义组件中的computed
    },
    mutations: {
        //相当于自定义组件中的methods,只能做同步的操作
        //对state中的数据进行修改
    },
    actions: {
        //异步操作,例如ajax请求
        //使用commit 触发 mutations
    }
})

5.3 vuex的核心概念

5.3.1 State

  1. State相当于自定义组件中的data,用来存放数据,页面中所有的数据都是从该对象中进行读取。
  2. 在组件中使用 store.state / this.$store.state 来读取vuex中的数据。
//创建 vuex 实例
const store = new Vuex.Store({
    state: {
        count:3
    }
}
//创建 vue 实例
const app = new Vue({
    el: '#app',
    store,
    components: { Counter },
    template: `<div class="app"><counter></counter></div>`
})
//自定义组件
const Counter = {
  template: `<div>{{ count }}</div>`,
  computed: {
    count () {
      return this.$store.state.count
    }
  }
}

5.3.2 getter

  1. getter 相当于自定义组件中的computed,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
  2. Getter 接受 state 作为其第一个参数,也可以接受其他 getter 作为第二个参数。
  3. 可以使用 store.getters / this.$store.getters 访问这些值。
const store = new Vuex.Store({
    state: {
        sourceList:[],
        pageNo:1,
        pageSize:5,
    },
    getters: {
        dataList:(state)=>{
           //计算分页后的数据
            let start=(state.pageNo-1)*state.pageSize;
            let end=start+state.pageSize;
            let result=state.sourceList.slice(start,end);
            return result;
        },
        pages:(state,getters)=>{
            //计算页码的范围
            return Math.ceil(getters.dataList.length/state.pageSize);
        }
    }
})

5.3.3 Mutation

  1. mutation 相当于自定义组件中的methods。
  2. mutation是更改 Vuex 的 State 中数据的唯一方法。
  3. 通过 store.commit(type,data)调用 mutation,第一个参数为事件类型,需要和mutation中函数名称一致;第二个参数为要传递的参数。
  4. mutation中的函数接受 state 作为其第一个参数。
const store = new Vuex.Store({
    state: {
      count: 1
    },
    mutations: {
      increment (state) {
        // 变更状态
        state.count++
      }
    }
})

const vm = new Vue(){
    methods:{
        change(){
            store.commit("increment");
        }
}

5.3.4 Action

  1. action 主要用来操作所有的异步请求。
  2. action 不能直接对State 中的数据进行操作,只能通过commit(type,data) 方法调用 mutation。
  3. action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。
  4. 通过 store.dispatch(type)方法触发action,参数为事件类型,需要和action中函数名称一致。
const store = new Vuex.Store({
    state: {
        dataList:[]
    },
    mutations: {
        render(state,data){
            state.dataList=data;
        },
    },
    actions: {
        getData({commit}){
            fetch("./data.json").then((res)=>res.json()).then((res)=>{
                  commit("render",res.data);
            })
**加粗样式**        }
    }
})
const vm = new Vue(){
    created(){
        store.dispatch("getData");
    }
}

总结:

父组件和子组件间通信:

  1. 父向子传递数据是通过props,子向父传递数据是通过event($emit);
  2. 通过父链/子链进行数据传递(parent /children);
  3. 通过 ref 也可以访问组件实例;
  4. 依赖注入:provide / inject;
    兄弟组件间通信:
  5. event bus
  6. Vuex
    跨级组件间通信:
  7. event bus;
  8. Vuex;
  9. 依赖注入:provide / inject;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341