Vue组件学习

TMaize 于 2018-01-10 发布

创建组件

所有的 vue 实例里面都可以使用

注意: 2.0 之后 template 最外层只有一个结点

Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead

注意: data 必须是函数

组件写法 1

Vue.component('my-component', {
  template: '<div @click="num++"></div>',
  data() {
    return {
      num: 0
    }
  }
});

组件写法 2

var myComponent = Vue.extend({
  template: '<div @click="num++"></div>',
  data() {
    return {
      num: 0
    }
  }
});

Vue.component('my-component', myComponent);

vue.extend, vue.component 区别

Vue.extend 是构造一个组件的语法器

Vue.component 是注册组件的,注册标签便于在模板中使用,如果不需要标签可以不注册,如一些插件直接使用代码创建实例即可

var apple = Vue.extend({ ... })
// 注册组件,在模板中使用apple标签
Vue.component('apple',apple)
// 直接在代码中获得组件实例
let a = new aple()
a.$mount()
console.log(a.$el)

组件使用模版

在组件里面用template: '<div @click="num++"></div>'是不利于书写的

方式 1

<script type="x-template" id="myComponent">
  <div @click="num++"></div>
</script>

template: '#myComponent'

方式 2

<template id="myComponent">
  <div @click="num++"></div>
</template>

template: '#myComponent'

动态组件

<div id="app">
  <input type="button" @click="cpName='my-component-1'" value="使用my-component-1渲染">
  <input type="button" @click="cpName='my-component-2'" value="使用my-component-2渲染">
  <component :is="cpName"></component>
</div>

<script>
  Vue.component('my-component-1', {
    template: '<div>我是组件1</div>'
  });
  Vue.component('my-component-2', {
    template: '<div>我是组件2</div>'
  });
  var vm = new Vue({
    el: '#app',
    data: {
      cpName: 'my-component-1'
    }
  });
</script>

注意,组件切换是是重新渲染,数据不会保留

如果希望保留数据,可以使用 keep-alive

<keep-alive>
  <component :is="currentView">
  <!-- 非活动组件将被缓存! -->
  </component>
</keep-alive>

父子组件

注意子组件必须在父组件的模板中使用而不是嵌套,嵌套是 slot

错误写法
<father><son></son></father>
正确写法

父组件的模板
<div>
...
<son></son>
</div>
<div id="app">
  <father></father>
</div>

<script>
  Vue.component('father', {
    template: '<div>我是父组件<son></son></div>',
      components: {
        'son': {
          template: '<div>我是子组件</div>'
        }
      }
  });

  var vm = new Vue({
    el: '#app'
  });
</script>

插槽

引用组件时候,在组件标签内写的内容是会被覆盖的,即无效

<div id="app">
  <comp>
    <span>World</span>
  </comp>
</div>

<script>
  Vue.component('comp', {
    template: '<div>Hello</div>'
  });
  var vm = new Vue({
    el: '#app'
  });
</script>

可以通过 slot 来将标签内的内容传递到组件模板中

<div id="app">
  <comp>
    <span>World</span>
  </comp>
</div>

<script>
  Vue.component('comp', {
    template: '<div>Hello<slot></slot></div>'
  });
  var vm = new Vue({
    el: '#app'
  });
</script>

当然,把标签内的作为一个整体作为一个 slot 还是不太自由,可以为 slot 起名字,根据名字引用

直接 slot 代表的是所有没有名字的 slot

<div id="app">
  <comp>
    <span slot='s1'>World</span>
    <span slot='s2'>!!!</span>
  </comp>
</div>

<script>
  Vue.component('comp', {
    data() {
      return {
        name: " Ming"
      }
    },
    template: '<div>Hello<slot name="s2"></slot><slot name="s1"></slot></div>'
  });
  var vm = new Vue({
    el: '#app'
  });
</script>

使用 prop 向组件传递数据

分为静态数据和动态数据,静态的就是一个字符串常量,动态的可以指定为父组件的 data 中数据

当让,动态的可以指定为一个数字,因为动态的实质就是一个表达式

当子组件需要一个对象传入且不想修改这个对象而影响到父组件,可以在自己的 data 返回 prop 中的一些属性

<son :sNum="fNum" sNum2="fNum"></son>
props: ['sNum',sNum2]

prop 的数据流向

父子组件之间通过动态 prop 传递数据是单向数据流,即父组件的数据改变会影响到子组件,子组件改变 prop 中的数据而不会影响到父组件

这句话是不对严谨的,对于一个数组或对象类型的 prop 来说,子组件改变 prop 也会影响到父组件,因为 JavaScript 中对象和数组是通过引用传入的

在父组件改变数据时候,子组件跟着改变,但是子组件改变 data1 会报错(单向数据流),改变 data2 父组件也会收到影响

01.png

Vue.component('father', {
  data: function() {
    return {
      data1: -10,
      data2: {
        number: 0
      }
    }
  },
  template:`<div>
    <span @click="data1++"></span>
    <span @click="data2.number++"></span>
    <son :data1="data1" :data2="data2"></son>
  </div>`,
  components: {
    son: {
      props: ['data1','data2'],
      template: `<div>
        <span @click="data1++"></span>
        <span @click="data2.number++"></span>
      </div>`
    }
  }
})

父子组件通讯的正确方式

综上,当 prop 为数组和对象的时候会影响到父组件破坏了单项数据流

同时,按照道理来讲,子组件不应该直接修改这些状态,因为会影响到父组件

所以正确的方式是子组件触发父组件的事件,然后在父组件中改变各个数据,然后会自动传递到子组件

Vue.component('father', {
  data: function() {
    return {
      fNum: 1
    }
  },
  methods: {
    fFunc() {
      this.fNum++
    }
  },
  template:
    '<div><div @click="fNum++">我是父组件</div><son @noticeF="fFunc" :fNum="fNum"></son></div>',
  components: {
    son: {
      props: ['fNum'],
      template: '<div @click="sClick">我是子组件</div>',
      methods: {
        sClick() {
          this.$emit('noticeF')
        }
      }
    }
  }
})

上面的 emit 可以通过使用 sync 语法糖来简化

Vue.component('father', {
  data: function() {
    return {
      fNum: 1
    }
  },
  template:
    '<div><div @click="fNum++">我是父组件</div><son :fNum.sync="fNum"></son></div>',
  components: {
    son: {
      props: ['fNum'],
      template: '<div @click="sClick">我是子组件</div>',
      methods: {
        sClick() {
          this.$emit('update:fNum', this.fNum + 1)
        }
      }
    }
  }
})

可以看出 Vue 替你在父组件内声明和了一个update:fNum函数,该函数只有有一个参数用于更新父组件的值

不同组件之间通信

在一些大项目中会有大量的组件,不同组件都有自己的状态且之间能够互相通信

为了方便之间的数据通信,Vue 官方推荐的是使用 Vuex 对组件的状态数据进行统一管理

如果项目较小我们也可以自己实现不同组件之间的通信,首先为了便于管理,我们应该把不同组件的数据集中定义(Vuex 就是这样)

一般是于最外层的父组件中统一定义,然后层层向上 emit,为了简化这种操作,Vue 也提供了 hub 的方式,类似于中间人的角色一样

<div id="app">
  <comp1></comp1>
  <comp2></comp2>
</div>

<script>
//创建事件中心
var hub = new Vue()
hub.$on('event1', data => {
  alert(data)
})

// 组件1
Vue.component('comp1', {
  data() {
    return {
      num: 1
    }
  },
  methods: {
    click() {
      // 触发hub中的的事件
      hub.$emit('event1', 666)
      hub.$emit('comp2Func', 666)
    }
  },
  template: '<div @click="click"></div>'
})
// 组件2
Vue.component('comp2', {
  data() {
    return {
      num: 1
    }
  },
  mounted: function() {
    let _this = this
    // 在hub中定义事件,之所以在组件2里面定义就是为了this
    hub.$on(
      'comp2Func',
      function(data) {
        this.num = data
      }.bind(_this)
    )
  },
  template: '<div></div>'
})
var vm = new Vue({
  el: '#app'
})
</script>