Vue组件通信 --- Transfer Data in Vue

1. props / $emit

The following is showing the parent component passing data to child.

// parent
<template>
  <div class="section">
    <com-article :articles="articleList"></com-article>
  </div>
</template>
<script>
import comArticle from './test/article.vue'
export default {
  name: 'HelloWorld',
  components: { comArticle },
  data() {
    return {
      articleList: ['Hong', 'Xi', 'San']
    }
  }
}
</script>
// child
<template>
  <div>
    <span v-for="(item, index) in articles" :key="index">{{item}}</span>
  </div>
</template>
<script>
export default {
  props: ['articles']
}
</script>

Child component passes data to parent by $emit

//parent
<template>
  <div class="section">
    <com-article :articles="articleList" @onEmitIndex="onEmitIndex"></com-article>
    <p>{{currentIndex}}</p>
  </div>
</template>
<script>
import comArticle from './test/article.vue'
export default {
  name: 'HelloWorld',
  components: { comArticle },
  data() {
    return {
      currentIndex: -1,
      articleList: ['Hong', 'Xi', 'Three']
    }
  },
  methods: {
    onEmitIndex(idx) {
      this.currentIndex = idx
    }
  }
}
</script>
//child
<template>
  <div>
    <div v-for="(item, index) in articles" :key="index" @click="emitIndex(index)">{{item}}</div>
  </div>
</template>
<script>
export default {
  props: ['articles'],
  methods: {
    emitIndex(index) {
      this.$emit('onEmitIndex', index)
    }
  }
}
</script>

2. $children / $parent

// parent
<template>
  <div class="hello_world">
    <div>{{msg}}</div>
    <com-a></com-a>
    <button @click="changeA">change value</button>
  </div>
</template>
<script>
import ComA from './test/comA.vue'
export default {
  name: 'HelloWorld',
  components: { ComA },
  data() {
    return {
      msg: 'Welcome'
    }
  },
  methods: {
    changeA() {
      // get data from child
      this.$children[0].messageA = 'this is new value'
    }
  }
}
</script>
//child
<template>
  <div class="com_a">
    <span>{{messageA}}</span>
    <p>get parent value:  {{parentVal}}</p>
  </div>
</template>
<script>
export default {
  data() {
    return {
      messageA: 'this is old'
    }
  },
  computed:{
    parentVal(){
      // parent data
      return this.$parent.msg;
    }
  }
}
</script>

3. provide/inject

The following is showing: A has B, B has C

// A.vue
<template>
  <div>
	<comB></comB>
  </div>
</template>
<script>
  import comB from '../components/test/comB.vue'
  export default {
    name: "A",
    provide: {
      for: "demo"
    },
    components:{
      comB
    }
  }
</script>
// B.vue
<template>
  <div>
    {{demo}}
    <comC></comC>
  </div>
</template>
<script>
  import comC from '../components/test/comC.vue'
  export default {
    name: "B",
    inject: ['for'],
    data() {
      return {
        demo: this.for
      }
    },
    components: {
      comC
    }
  }
</script>
// C.vue
<template>
  <div>
    {{demo}}
  </div>
</template>
<script>
  export default {
    name: "C",
    inject: ['for'],
    data() {
      return {
        demo: this.for
      }
    }
  }
</script>

4. ref / refs

//child: A.vue
export default {
  data () {
    return {
      name: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      console.log('hello')
    }
  }
}
//parent: app.vue
<template>
  <component-a ref="comA"></component-a>
</template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;
      console.log(comA.name);  // Vue.js
      comA.sayHello();  // hello
    }
  }
</script>

5.全局事件总线(GlobalEventBus)

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

    new Vue({
    	......
    	beforeCreate() {
    		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
    	},
        ......
    }) 
  3. 使用事件总线:

    1. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。

      methods(){
        demo(data){......}
      }
      ......
      mounted() {
        this.$bus.$on('xxxx',this.demo)
      }
    2. 提供数据:this.$bus.$emit('xxxx',数据)

  4. 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
    create event-bus.js

    // event-bus.js
    import Vue from 'vue'
    export const EventBus = new Vue()

    we got 2 components

    <template>
      <div>
        <show-num-com></show-num-com>
        <addition-num-com></addition-num-com>
      </div>
    </template>
    <script>
    import showNumCom from './showNum.vue'
    import additionNumCom from './additionNum.vue'
    export default {
      components: { showNumCom, additionNumCom }
    }
    </script>

    Additon.vue passes data to Show.vue

    // addition.vue
    <template>
      <div>
        <button @click="additionHandle">+adder</button>    
      </div>
    </template>
    <script>
    import {EventBus} from './event-bus.js'
    export default {
      data(){
        return{
          num:1
        }
      },
      methods:{
        additionHandle(){
          EventBus.$emit('addition', {
            num:this.num++
          })
        }
      }
    }
    </script>
    // showNum.vue 
    <template>
      <div>total: {{count}}</div>
    </template>
    
    <script>
    import { EventBus } from './event-bus.js'
    export default {
      data() {
        return {
          count: 0
        }
      },
    
      mounted() {
        EventBus.$on('addition', param => {
          this.count = this.count + param.num;
        })
      }
    }
    </script>

    6. Vuex

  • state: store data
  • getter: similiar with vue computed, using for filter or computed
  • mutations: doing calculation, not for asynchronous
  • actions: data filter, good for asynchronous
    // parent
    <template>
      <div id="app">
        <ChildA/>
        <ChildB/>
      </div>
    </template>
    <script>
      import ChildA from './components/ChildA' 
      import ChildB from './components/ChildB'
      export default {
        name: 'App',
        components: {ChildA, ChildB} 
      }
    </script>
    //childA
    <template>
      <div id="childA">
        <h1>I am A</h1>
        <button @click="transform">send data to B</button>
        <p>get data from B:{{BMessage}}</p>
      </div>
    </template>
    <script>
      export default {
        data() {
          return {
            AMessage: 'Hello,B,I am A'
          }
        },
        computed: {
          BMessage() {
            return this.$store.state.BMsg
          }
        },
        methods: {
          transform() {
            this.$store.commit('receiveAMsg', {
              AMsg: this.AMessage
            })
          }
        }
      }
    </script>
    // childB
    <template>
      <div id="childB">
        <h1>I am B</h1>
        <button @click="transform">send data to A</button>
        <p>get data from A:{{AMessage}}</p>
      </div>
    </template>
    <script>
      export default {
        data() {
          return {
            BMessage: 'Hello,A,I am B'
          }
        },
        computed: {
          AMessage() {
            return this.$store.state.AMsg
          }
        },
        methods: {
          transform() {
            this.$store.commit('receiveBMsg', {
              BMsg: this.BMessage
            })
          }
        }
      }
    </script>
    // store.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    Vue.use(Vuex)
    
    // store data
    const state = {
      AMsg: '',
      BMsg: ''
    }
    const mutations = {
      receiveAMsg(state, payload) {
        state.AMsg = payload.AMsg
      },
      receiveBMsg(state, payload) {
        state.BMsg = payload.BMsg
      }
    }
    export default new Vuex.Store({
      state,
      mutations
    })

    四个map方法的使用

  1. mapState方法:用于帮助我们映射state中的数据为计算属性

    computed: {
        //借助mapState生成计算属性:sum、school、subject(对象写法)
         ...mapState({sum:'sum',school:'school',subject:'subject'}),
             
        //借助mapState生成计算属性:sum、school、subject(数组写法)
        ...mapState(['sum','school','subject']),
    },
  2. mapGetters方法:用于帮助我们映射getters中的数据为计算属性

    computed: {
        //借助mapGetters生成计算属性:bigSum(对象写法)
        ...mapGetters({bigSum:'bigSum'}),
    
        //借助mapGetters生成计算属性:bigSum(数组写法)
        ...mapGetters(['bigSum'])
    },
  3. mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数

    methods:{
        //靠mapActions生成:incrementOdd、incrementWait(对象形式)
        ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
    
        //靠mapActions生成:incrementOdd、incrementWait(数组形式)
        ...mapActions(['jiaOdd','jiaWait'])
    }
  4. mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数

    methods:{
        //靠mapActions生成:increment、decrement(对象形式)
        ...mapMutations({increment:'JIA',decrement:'JIAN'}),
        
        //靠mapMutations生成:JIA、JIAN(对象形式)
        ...mapMutations(['JIA','JIAN']),
    }

备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象。

模块化+命名空间

  1. 目的:让代码更好维护,让多种数据分类更加明确。

  2. 修改store.js

    const countAbout = {
      namespaced:true,//开启命名空间
      state:{x:1},
      mutations: { ... },
      actions: { ... },
      getters: {
        bigSum(state){
           return state.sum * 10
        }
      }
    }
    
    const personAbout = {
      namespaced:true,//开启命名空间
      state:{ ... },
      mutations: { ... },
      actions: { ... }
    }
    
    const store = new Vuex.Store({
      modules: {
        countAbout,
        personAbout
      }
    })
  3. 开启命名空间后,组件中读取state数据:

    //方式一:自己直接读取
    this.$store.state.personAbout.list
    //方式二:借助mapState读取:
    ...mapState('countAbout',['sum','school','subject']),
  4. 开启命名空间后,组件中读取getters数据:

    //方式一:自己直接读取
    this.$store.getters['personAbout/firstPersonName']
    //方式二:借助mapGetters读取:
    ...mapGetters('countAbout',['bigSum'])
  5. 开启命名空间后,组件中调用dispatch

    //方式一:自己直接dispatch
    this.$store.dispatch('personAbout/addPersonWang',person)
    //方式二:借助mapActions:
    ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
  6. 开启命名空间后,组件中调用commit

    //方式一:自己直接commit
    this.$store.commit('personAbout/ADD_PERSON',person)
    //方式二:借助mapMutations:
    ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),

7. localStorage/ sessionStorage

we can read date by window.localStorage.getItem(key) and save data by window.localStorage.setItem(key,value). Remember to use JSON.parse() and JSON.stringify()

8. slot

  1. 默认插槽:
    父组件中:
            <Category>
               <div>html结构1</div>
            </Category>
    子组件中:
            <template>
                <div>
                   <!-- 定义插槽 -->
                   <slot>插槽默认内容...</slot>
                </div>
            </template>
  2. 具名插槽:
    父组件中:
            <Category>
                <template slot="center">
                  <div>html结构1</div>
                </template>
    
                <template v-slot:footer>
                   <div>html结构2</div>
                </template>
            </Category>
    子组件中:
            <template>
                <div>
                   <!-- 定义插槽 -->
                   <slot name="center">插槽默认内容...</slot>
                   <slot name="footer">插槽默认内容...</slot>
                </div>
            </template>
  3. 作用域插槽:数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定。(games数据在Category组件中,但使用数据所遍历出来的结构由App组件决定)
    父组件中:
    <Category>
    <template scope="scopeData">
    <!-- 生成的是ul列表 -->
    <ul>
    <li v-for="g in scopeData.games" :key="g">{{g}}</li>
    </ul>
    </template>
    </Category>
             
    <Category>
    <template slot-scope="scopeData">
    <!-- 生成的是h4标题 -->
    <h4 v-for="g in scopeData.games" :key="g">{{g}}</h4>
    </template>
    </Category>
    子组件中:  
      <template>
            <div>
               <slot :games="games"></slot>
            </div>
      </template>
      <script>
           export default {
               name:'Category',           
                props:['title'],          
                //数据在子组件自身            
                 data() {          
                    return {
                        games:['红色警戒
                        线','劲舞团','超级玛丽']
                
                    }
               },
              }
     </script>