3.Vue组件(2018.01.11)

1.Vue组件介绍:
Vue组件 (Component) 是 Vue.js 最强大的功能之一。项目都是由组件构建起来的,组件也可以重用。所有的 Vue 组件同时也都是 Vue 的实例。



2.Vue单文件组件包含三个部分
template
视图部分, 只能存在一个根元素。
script
逻辑 部分 ,其中  data  必须是函数。
style
样式 部分 ,建议使用  s coped 确保样式只在当前组件内生效。
模板如下:
<template>
   <div>
   </div>
</template>

<script>
</script>

<style scoped>
</style>



3.全局注册组件(不常用):
1). 在 main.js 里全局注册组件:
// 注册
Vue.component('my-component', {
     template: '<div>A custom component!</div>'
})
2). 在App.vue或者其它组件里面使用,例如在Hello.vue里使用:
<template>
   <div>
       <my-component></my-component>
  </div>
</template>
<script>
</script>
<style scoped>
</style>
结果:



4.局部注册组件(常用):
全局注册组件会让程序可读性降低,我们可以通过某个 Vue 组件的实例选项 components 注册仅在其作用域中可用的组件(对 Parent 组件而言 Child 组件是其局部组件,对 App 而言 Parent 组件也是其局部组件):
1). 在 Child.vue 里面:
<template>
    <div>
        <p>{message }}</p>
    </div>
</template>
<script>
    export default {
        name:"child",
        data(){
            return {
                message'Hello Vue!'
            }
        }
    }
</script>
<style scoped>
</style>
2). 在 Parent.vue 里面:
<template>
    <div>
        <Child/>
    </div>
</template>
<script>
    import Child from './Child'

    export default {
        name:"parent",
        data(){
            return {
            }
        },
        components: {
            Child
        }
    }
</script>
<style scoped>
</style>
3).在 App.vue 里面使用:
<template>
  <div id="app">
        <MyHelloWorld />
        <Parent/>
  </div>
</template>

<script>
import MyHelloWorld from './components/HelloWorld'
import Hello from './components/Hello'
import Parent from './components/Parent'

export default {
  name: 'App',
  components: {
    MyHelloWorld,
    Hello,
    Parent
  }
}
</script>
<style>
</style>
注:组件文件的名称不重要,在引入组件文件的时候其别名很重要,在components里面和在使用组件时都是使用该别名,参看上面的MyHelloWorld。不过建议组件名称跟组件文件名称相同。



5.组件里面的 data 必须是函数
如果data不是一个函数,那么所有使用该组件的实例将共享同一个data数据对象,如果data里面的数据改变,有可能引起其他组件数据也同时改变。所以为了消除这种影响,规定data必须是一个函数。
案例:
<template>
    <div>
        <p>{message }}</p>
    </div>
</template>
<script>
    export default {
        name:"child",
        data(){
            return {
                message: 'Hello Vue!'
            }
        }
    }
</script>
<style scoped>
</style>



6.子级组件之间的交互(通信):
组件实例的作用域是孤立的,为了解耦,组件之间不能直接进行数据的传递。在 Vue 中,父组件通过 props 给子组件下发数据,子组件通过事件给父组件发送消息。
1).父组件向子组件传递数据:props
①.案例:
a.在 Parent.vue 里面:
<template>
    <div>
        <!--前面是静态传递参数,后面是动态传递参数。参数名必须跟子组件props里面的名称一致-->
        <Child  staticMsg="I am static Message"  :myMessage="todo"></Child>
    </div>
</template>
<script>
    import Child from './Child'

    export default {
        name:"parent",
        data(){
            return {
                todo: {
                    text: 'Learn Vue',
                    isComplete: false
                }
            }
        },
        components: {
              Child
        }
    }
</script>
<style scoped>
</style>
b.在 Child.vue 里面:
<template>
    <div>
        <p>{staticMsg }}</p>
        <p>{myMessage }}</p>
    </div>
</template>
<script>
    export default {
        name:"child",
        props: ['staticMsg','myMessage'],  //父组件上面的参数必须跟这里一致
        data(){
            return {
            }
        }
    }
</script>
<style scoped>
</style>
注:Vue支持父组件将一个整体对象通过 props 传递给子组件。上面传递的 todo 即可说明。

②.不应该也不要在子组件内部直接改变 props 里面的值,如果想要改变,请使用下面方法:
a.方法一:定义一个局部变量,并用 props 的值初始化它:
<script>
    export default {
        name:"child",
        props: ['initialCounter'],
        data: function () {
              return { 
                  counter: this.initialCounter   //在子组件里面可以改变counter值,但是不要改变initialCounter的值
              }
        }
    }
</script>
b.方法二:定义一个计算属性,处理 props 的值并返回:
<script>
    export default {
        name:"child",
        props: ['size'],
        computed: {
          normalizedSize: function () {
                return this.size.trim().toLowerCase()  //只要size本身值不改变即可
          }
        }
    }
</script>
注:对象或数组是引用类型,如果 props 是一个对象或数组,在子组件内部改变它会影响父组件的状态。这就是我们不应该在子组件里面改变父组件传过来值的原因。

③. 数据传递类型限制与验证(重要):
可以为组件的 props 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。要指定验证规则,需要用对象的形式来定义 props,而不能用字符串数组
props: {
    // 必须是数字 (`null` 指允许任何类型)
    propA: Number,

    // 可能是字符串或者是数字
    propB: [String, Number],

    // 必传且是字符串
    propC: {
      type: String,
      required: true
    },

    // 数值且有默认值
    propD: {
      type: Number,
      default: 100
    },

    // 数组/对象的默认值应当由一个工厂函数返回
    propE: {
      type: Object,
      default: function () {
         return { message: 'hello' }
      }
    },

    // 自定义验证函数
    propF: {
      validator: function (value) {
         return value > 10
      }
    }
  }
type 可以是下面原生构造器:
type验证类型
说明
String
字符串
Number
数字
Boolean
布尔
Function
函数
Object
对象
Array
数组
Symbol
符号
自定义构造器函数
使用 instanceof 检测


2).子组件向父组件传递数据:emit事件
①.案例:
a.在 Child.vue 里面:
<template>
    <div>
         <button v-on:click="incrementCounter">{counter }}</button>
    </div>
</template>
<script>
    export default {
        name:"child",
        data(){
            return {
                counter: 0
            }
        },
        methods: {
            incrementCounter: function () {
                 this.counter += 1
                 this.$emit('increment')  //父组件在使用子组件时必须监听 increment 事件
            }
       }
    }
</script>
<style scoped>
</style>
b.在 Parent.vue 里面:
<template>
    <div>
        <p>{{ total }}</p>
        <!--  其中 increment 名称必须与子组件this.$emit('increment')里面的名称一致-->
        <Child v-on:increment="incrementTotal"></Child> 
        <Child v-on:increment="incrementTotal"></Child>
    </div>
</template>
<script>
    import Child from './Child'

    export default {
        name:"parent",
        data(){
            return {
                total: 0
            }
        },
        methods: {
            incrementTotal: function () {
                this.total += 1
            }
        },
        components: {
            Child
        }
    }
</script>
<style scoped>
</style>

②.给组件绑定原生事件:
<Child v-on:click.native="doTheThing"></Child >

③.使用自定义事件的表单输入组件:
a.在 Child.vue 里面:
<template>
    <div>
        <input ref="input"  v-bind:value="value"  v-on:input="updateValue($event.target.value)">
    </div>
</template>
<script>
    export default {
        name:"child",
        props: ['value'],
        methods: {
            updateValue: function (value) {
              if(value.indexOf('$') === -1){
                 value = "$" + value;
              }
              this.$emit('input', value)
            }
          }
    }
</script>
<style scoped>
</style>
b.在 Parent.vue 里面:
<template>
    <div>
      <p>{price }}</p>
      <Child v-model="price"></Child> 
    </div>
</template>
<script>
    import Child from './Child'

    export default {
        name:"parent",
        data(){
            return {
                price: 0
            }
        },
        components: {
            Child
        }
    }
</script>
<style scoped>
</style>


3).非父子组件的通信:
有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:
①.新建一个空的 Vue 实例作为事件总线,在bus.js 文件中,内容如下:
import Vue from 'vue'
export default new Vue;
②.在组件 A.vue 里面触发事件:
<template>
    <div>
        <button v-on:click="incrementCounter">{{ counter }}</button>
    </div>
</template>
<script>
    import bus from './bus'

    export default {
        name:"component_a",
        data(){
            return {
                counter: 0
            }
        },
        methods: {
            incrementCounter: function () {
                this.counter += 1
                bus.$emit('busincrement', this.counter) //其他组件如果想跟A通信,必须监听busincrement事件
            }
        }
    }
</script>
<style scoped>
</style>
③.在组件 B.vue 中监听事件:
<template>
    <div>
        <p>{counter }}</p>
    </div>
</template>
<script>
    import bus from './bus'

    export default {
        name:"component_b",
        data(){
            return {
                counter: 0
            }
        },
        mounted() {
            bus.$on('busincrement',function (value) {
                this.counter = value
            }.bind(this))
        }
    }
</script>
<style scoped>
</style>
注:B组件主动监听A组件,需要将事件写到mounted()里面,这样当A组件里面的事件触发,B组件就能知道。
④.在 App.vue 里面调用两个组件:
<template>
  <div id="app">
        <B/>
        <A/>
  </div>
</template>

<script>
import A from './components/A'
import B from './components/B'

export default {
  name: 'App',
  components: {
    A,
    B
  }
}
</script>
<style>
</style>



7.插槽:
所谓插槽就是在父组件里面先占一个位置,如果子组件有插口,则将子组件插口里面的内容放到父组件的插槽里面;如果子组件没有插口,则使用父组件备用(默认)的内容。 最初在 <slot> 标签中的任何内容都被视为备用(默认)内容。备用(默认)内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用(默认)内容。
1).单个插槽:
①. 在子组件 Child.vue 模板里面:
 
 
<div>
<h2>我是子组件的标题</h2>
<slot>
只有在没有要分发的内容时才会显示。
</slot>
</div>
②. 在父组件 Parent.vue 模板里面:
 
 
<div>
<h1>我是父组件的标题</h1>
<Child>
<p>这是一些初始内容</p>
<p>这是更多的初始内容</p>

</Child>
</div>
③. 渲染结果:
<div>
<h1>我是父组件的标题</h1>
<div>
<h2>我是子组件的标题</h2>
<p>这是一些初始内容</p>
<p>这是更多的初始内容</p>
</div>
</div>

2).具名插槽:
<slot> 元素可以用一个特殊的特性 name 来进一步配置如何分发内容。多个插槽可以有不同的名字。具名插槽将匹配内容片段中有对应 slot 特性的元素。仍然可以有一个匿名插槽,它是默认插槽,作为找不到匹配的内容片段的备用插槽。如果没有默认插槽,这些找不到匹配的内容片段将被抛弃。
①. 在子组件 app-layout 模板中:
<div class="container">
  <header>
      <slot name="header"></slot>
  </header>
  <main>
      <slot></slot>
  </main>
  <footer>
      <slot name="footer"></slot>
  </footer>
</div>
②.父组件模板:
<app-layout>
  <h1 slot="header">这里可能是一个页面标题</h1>

  <p>主要内容的一个段落。</p>
  <p>另一个主要段落。</p>

  <p slot="footer">这里有一些联系信息</p>
</app-layout>
③.渲染结果为:
<div class="container">
  <header>
    <h1>这里可能是一个页面标题</h1>
  </header>
  <main>
    <p>主要内容的一个段落。</p>
    <p>另一个主要段落。</p>
  </main>
  <footer>
    <p>这里有一些联系信息</p>
  </footer>
</div>

3).作用域插槽:数据是子组件传给父组件
①. 在子组件 child 中,只需将数据传递到插槽,就像你将 props 传递给组件一样:
<div>
     <slot text="hello from child"></slot>
</div>
②. 在父级中,具有特殊特性 slot-scope 的 <template> 元素必须存在,表示它是作用域插槽的模板。slot-scope 的值将被用作一个临时变量名,此变量接收从子组件传递过来的 props 对象:
<div class="parent">
  <child>
    <template slot-scope="props">
        <span>hello from parent</span>
        <span>{{ props.text }}</span>
    </template>
  </child>
</div>
③. 如果我们渲染上述模板,得到的输出会是:
<div class="parent">
  <div class="child">
    <span>hello from parent</span>
    <span>hello from child</span>
  </div>
</div>
注意:在2.5.0之前,必须使用到template身上。



8.动态组件:
1).动态组件案例:
①.分别创建 Home.vuePosts.vueArchive.vue 内容如下(适当修改 msg 内容和 name 名称):
<template>
    <div>
        {msg }}
    </div>
</template>
<script>
    export default {
        name:"home",
        data(){
            return {
                msg: "我是home组件"
            }
        }
    }
</script>
<style scoped>
</style>
②.在  App.vue  里面使用三个组件:
<template>
  <div id="app">
       <button v-on:click="changeView">改变组件</button>
       <component v-bind:is="currentView">
          <!-- 组件在 vm.currentview 变化时改变! -->
      </component>
  </div>
</template>

<script>
import Home from './components/Home'
import Posts from './components/Posts'
import Archive from './components/Archive'

export default {
  name: 'App',
  data(){
        return {
            currentView: 'Home'
        }
  },
  methods:{
    changeView : function(){
        if(this.currentView == 'Home'){
            this.currentView ='Posts'
        }else if(this.currentView == 'Posts'){
           this.currentView ='Archive'
        }else{
           this.currentView ='Home'
        }
    }
  },
  components: {
    Home,
    Posts,
    Archive
  }
}
</script>

<style>
</style>

2).组件缓存: keep-alive
如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:
<keep-alive>
    <component :is="currentView">
       <!-- 非活动组件将被缓存! -->
    </component>
</keep-alive>



9.Vue 组件的 API 来自三部分:

API
作用
props
允许外部环境传递数据给组件,即父组件传递给子组件
事件
允许从组件内触发外部环境的副作用,即子组件传递给父组件
插槽
允许外部环境将额外的内容组合在组件中,即父组件传递给子组件




10.对低开销的静态组件使用 v-once:

尽管在 Vue 中渲染 HTML 很快,不过当组件中包含大量静态内容时,可以考虑使用 v-once 将渲染结果缓存起来,如下:

<template>
    <div v-once>
        <h1>因为我都是静态内容,而且不易变化,所有缓存起来!</h1>
    </div>
</template>
<script>
    export default {
        name:"child",
        data(){
            return {
            }
        }
    }
</script>
<style scoped>
</style>







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值