Vue学习笔记[09]-组件化进阶

2019-11-05  本文已影响0人  神楽花菜

tag:父子组件通信,data为什么为函数事件总线

组件的可选属性

组件不能直接访问顶层Vue实例中的数据:

<script>//✖!
  Vue.component('cpn', {
    template: "<div>{{number}}</div>"
  })
  const vm = new Vue({
    el: "#app",
    data: {
      message:"some text"
},
  })

组件中也可以有data属性和methods等属性:

 Vue.component('cpn', {
    template: "#cpn-t",
    data() {
      return {
        message: "some text"
      }
    },
  })
  const vm = new Vue({
    el: "#app",
  })

可以发现,子组件中的data是一个函数并且返回一个对象,并且如果不写成函数的形式而是写成对象的形式时会报错(“data”属性应该是在组件定义中返回一个对象实例的函数。)

错误信息
在每一次使用组件时,vue每次都会对调用data函数并且返回一个对象

解析:
函数返回内部对象时相当于会创建一个新的实例对象并返回该对象(的地址),而在返回外部对象时并不会创建新的对象而是返回外部对象的地址,这就会造成组件中数据污染到外部对象或是其他组件的数据

  function f1() {

    return{
      name: 'Sam',
      age: 20
    };
  }
let a = f1();//创建新的对象并返回
let b = f1();//创建新的对象并返回
let c = f1();//创建新的对象并返回
//-----------------------------------------
const person2 = {
  name: "Alex",
  age: 21
}
function f2(){
  return person2;//这里return的是外部对象的引用(地址)
}
let d = f2();//外部对象的引用
let e = f2();//外部对象的引用
let f = f2();//外部对象的引用
//d,e,f指向同一个对象,修改任意一个其他的对象属性也会变.(当对象为const时属性仍然可修改)

data为函数:

Vue.component('button-counter2', {
  data: function () {
    return {
      count: 0;
      }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
new Vue({ el: '#components-demo3' })
三个组件拥有独立的data
var buttonCounter2Data = {
  count: 0
}
Vue.component('button-counter2', {
  data: function () {
    return buttonCounter2Data
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
new Vue({ el: '#components-demo3' })
三个组件共享data

组件通信

由于Vue父子组件中的数据是互相不可见的,因此有时需要传递数据时就需要组件通信.

<div id="app">
   <cpn></cpn> 
 </div>
<template id="cpn-t">
  <div>
    <h4>title</h4>
    <cpn-children :message="message"></cpn-children>
  </div>
</template>

在父组件中 使用子组件时,需要绑定prop数组中的值到父组件的数据.由于html标签中不支持大小写,所以props中的变量名应为小写,若使用驼峰标识,在html标签内要转化为短横线命名.

<script>

  Vue.component('cpn', {
    template: "#cpn-t",
    data() {
      return {
        message: "some text"
      }
    },
    components: {
      cpnChildren: {
        template: '<div>{{message}}</div>',
        //数组形式:props: ['message',/*...*/]  ,在Vue风格指南中作为反例
        //对象形式:
        props: {
          message: String,
          name: {
            type: String, //类型
            default: "Smith", //默认值  
            required:false
          }
        }
      }
    }
  })

如果props中的数据是对象类型或数组类型,在默认值处应用工厂方法返回一个数组或对象:

props:{
  fruit:{
      type:Array,
      default:function(){
          return [];
      }
   }
}
<body>
  <div id="app">
    <cpn></cpn>
  </div>
</body>

<template id="cpn-t">
  <div>
    <cpn-children @emit-btn-click="consoleLog"></cpn-children> //监听子组件发射自定义事件并处理事件!该方法不需要参数,默认传入.
  </div>
</template>

<script>
  Vue.component('cpn', { //定义父组件
    template: "#cpn-t",
    methods: {
      consoleLog(item) { //默认传入自定义事件中的传递的数据,由父组件处理发射事件
        console.log("Clicked", item.name);
      }
    },

    components: { //定义子组件
      cpnChildren: {
        template: `<div>
        <button v-for="item in fruit" @click="btnclicked(item)">{{item.name}}</button>
        </div>
        `,
        data() {
          return {
            fruit: [{
                id: 200,
                name: "apple",
                price: 2
              },

              {
                id: 201,
                name: "banana",
                price: 2.5
              },

              {
                id: 202,
                name: "orange",
                price: 0.5
              }
            ]
          }
        },
        methods: {
          btnclicked(item) {
            this.$emit("emit-btn-click",item) // 子组件向父组件发射自定义事件
          },
        }

      }
    }
  })
  const vm = new Vue({
    el: "#app",
    methods: {

    }
  })
</script>

Vue<ROOT>
|-cpn =>监听发射事件,,接收数据,处理发射,
 |-cpnChildren =>发射事件,数据

补充:通过事件总线发送消息

当某些组件层次过深或传递复杂时,一方面我们可以用Vuex来进行管理事件,另一方法是在vue原型上添加$bus属性,通过另一个vue实例来对事件进行发送和监听。

//main.js
import Vue from 'vue'
import App from './App.vue'
//vue原型添加事件总线
Vue.prototype.$bus = new Vue()

Vue.config.productionTip = false

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

//CpnSendMsg.vue
//发送消息(事件)的组件
<template>
  <div>
    <button @click="sendMsg">
      send
    </button>
  </div>
</template>

<script>
export default {pn
  name:"Cpn",
  methods: {
    sendMsg(){
        //利用新建的vue实例来发送自定义事件
      this.$bus.$emit("cpnSendMsg")
    }
  }
}
</script>
//CpnReseveMsg.vue
//接受自定义事件的组件
<template>
  <div>
。。。
  </div>
</template>

<script>
export default {
  name:"CpnReseveMsg",
  created(){
    this.$bus.$on("cpnSendMsg",()=>{
      //事件处理
    })
  }
}
</script>

补充:使用vue原型包装全局组件

使用场景:封装Toast(弹窗)组件,由于该组件需要在不同场景下使用,,十分频繁,因此将该组件挂载到vue原型上。

//toast/Toast.vue
<template>
  <div class="toast" v-show="isShow">
    {{message}}
  </div>
</template>

<script>
export default {
  name: 'Toast',
 data() {
   return {
     message:'',
     isShow:false
   }
 },
  methods: {
    show(message,time){
      this.message = message
      this.isShow = true
      setTimeout(() => {
        this.isShow = false
        this.message = ''
      }, time);
    }
  },

};
</script>

<style lang='scss' scoped>
.toast{
  position: fixed;
  top:50%;
  left: 50%;
  transform: translate(-50%,-50%);
  z-index: 1000;
  padding: 20px 40px;
  color: rgb(110, 110, 110);
  background-color: rgba(236, 236, 236, 0.85);
  border-radius: 15px;
}
</style>
//toast/index.js
import Toast from "./Toast"
const obj = {
}
obj.install = function (Vue) {
  //1.创建组件构造器
  const constructor = Vue.extend(Toast)
  //2.使用new方法创建组件
  const toast = new constructor()
  //3.手动挂载组件对象
  toast.$mount(document.createElement('div'))
  //4.已经挂载的对象具有了el属性
  document.body.appendChild(toast.$el)
  Vue.prototype.$toast = toast
/********************or***************************/
 //1.创建组件
  const toast = new Vue(Toast)
  //2.手动挂载组件对象
  toast.$mount(document.createElement('div'))
  //3.已经挂载的对象具有了el属性
  document.body.appendChild(toast.$el)
  Vue.prototype.$toast = toast
}
export default obj
//main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
//导入toast
import toast from "components/common/toast/index"
//vue原型添加事件总线
Vue.prototype.$bus = new Vue()
Vue.config.productionTip = false
//安装toast
Vue.use(toast)
new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

//use
//...
methods:{
  someEventAcitve(){
  this.$toast.show("MESSAGE",2000)
  }
}
上一篇下一篇

猜你喜欢

热点阅读