前言

本篇主要介绍组件的使用以及父子组件的传值和事件互相调用。

定义组件

无论那种组件,需要注意的是组件内的data必须为函数,通过函数内return data来实现数据使用;组件的el也必须使用函数

组件命名规范

  • 参考官网文档
  • 项目实践描述
    1.有意义的,如果是业务组件要用业务的名称;如果是ui组件要用ui的名称
    2.名称简短
    3.具有可读性,便于沟通
    4.符合自定义元素规范,避免使用保留字,使用统一的连接符中划线
    5.app-作为统一的命名空间,想到其他项目可以共用

页面内定义的组件

  • 页面内可以定义全局或者页面组件
    1. // 注册全局组件
    2. Vue.component('my-component', {
    3. template: '<div>A custom component!</div>'
    4. props:[]
    5. })
    6. //局部组件
    7. var app=new Vue({
    8. el:"#app",
    9. components:{
    10. [ 'my-component': {
    11. template: '#myComponent',
    12. data: function() {
    13. return {
    14. msg: 'This is a Component!' //Vue中component的data必须通过function() return
    15. }
    16. },
    17. methods: {
    18. showMsg: function() {
    19. alert(this.msg);
    20. }
    21. }
    22. ]
    23. })
    24. //使用template标签实现组件
    25. <template id="myComponent">
    26. <div>This is a component!</div>
    27. </template>
    28. Vue.component('my-component', {
    29. template: '#myComponent'
    30. props:[]
    31. })

单文件组件

  • 单独的一个组件作为一个文件,包括该组件需要的样式以及脚本数据等,如果你想组件也使用共用资源或者同类资源管理的方式也是可以的。其他:参考官网介绍
    1. <!-- 单文件组件定义-->
    2. <template>
    3. <div >
    4. <h2>comp的组件</h2>
    5. data
    6. </ul>
    7. </div>
    8. </template>
    9. <script>
    10. export default {
    11. name: 'comp',
    12. data () {
    13. return {
    14. data:'data'
    15. }
    16. }
    17. }
    18. </script>
    19. <!-- Add "scoped" attribute to limit CSS to this component only -->
    20. <style scoped less>
    21. </style>

引入组件

  • 引入文件组件,然后在使用的组件内注册该组件即可

    1. <!--> 其他页面使用该组件<-->
    2. <comp></comp>
    3. <script>
    4. import comp from './comp'
    5. export default {
    6. name: 'app',
    7. components:{comp},
    8. data () {
    9. return {
    10. }
    11. }}
    12. </script>
  • 在全局中注册文件组件,就可以直接使用,在components中将所有组件注册一遍,页面中可以直接使用

  1. import Vue from 'vue'
  2. import booklist from './booklist'
  3. import comp from './comp'
  4. const components = [booklist, comp
  5. ]
  6. components.map(component => {
  7. Vue.component(component.name, component)
  8. })
  9. export default {
  10. booklist, comp
  11. }
  • 组件传值以及绑定方法
    组件实例的作用域是孤立的。这意味着不能并且不应该在子组件的模板内直接引用父组件的数据。父组件可以使用 props 把数据传给子组件。注意:在子组件中定义prop时,使用了camelCase命名法。由于HTML特性不区分大小写,camelCase的prop用于特性时,需要转为 kebab-case(短横线隔开)。例如,在prop中定义的myName,在用作特性时需要转换为my-name。
  1. //组件中声明可以直接使用,组件中直接使用属性名称即可,与data中定义的具有相同的效果,可以直接使用,
  2. 但不是同一个数据
  3. props:['attr']
  4. props:['mySize']
  5. //使用的时候 属性显性声明注入,直接冒号绑定数据就可以,如果你不是动态数据的内容,
  6. 那么去掉冒号即可(另外需要注意的是如果加了冒号,引号内支持的是js表达式,否则只是字符串)
  7. <comp :message="msg" :my-size='size'></comp>
  8. data () {
  9. return {
  10. msg:'示例的子组件'
  11. }
  12. }
  1. //父组件
  2. <father>
  3. <child :love="msg"></child>
  4. </father>
  5. //子组件
  6. export default {
  7. ...
  8. props: ['love']
  9. }
  • 子组件向父组件传递数值,通过event传递
    如在par中使用子组件sub,可以使用自定义事件(myClick)来完成:如果你 传递的是多个值,封装到一个对象中。
    <sub @myClick="handlerClick" />
    在sub中使用this.$emit('myClick')去触发。 par中写好handlerClick(常规的method方法)对应去处理即可。

  • 同级组件(非父子组件)数据传递 (另外一种方法是使用vuex)
    兄弟组件或者完全不相关的组件通信就需要使用一个你说的 中央事件总线去处理, 这个new Vue()写在一个单独的文件中作为公用的util方法
    如 a,b通信, 在a组件的生命周期函数里面去写对应的监听事件如在mounted处理:

  1. //一般定义在公共的脚本中,如果你是spa项目的话,写在main.js文件中,定义一个全局的,获取全局Vue对象
  2. window.eventBus = new Vue();
  3. //a组件, 监听my-event事件,进行对应的处理,组件创建前添加钩子函数
  4. mounted(){
  5. eventBus.$on('my-event', (data) => {
  6. //do something...
  7. })
  8. }
  9. //b组件 比如在某个元素点击后触发 my-event
  10. methods: {
  11. click: function(){
  12. eventBus.$emit('my-event', data); // data是要传递的数据
  13. }
  14. }
  • 注意事项
    组件中传入的值如果后续发生改变,那么原始注入的值就会发生改变,建议将值使用赋值到另一个变量,不要直接改变注入属性的值。虽然这不会影响原来的。如果使用了v-model的模型,会实时改变到使用这个值的组件。

组件事件触发

单向数据流

在vue2.x之后,组件中是只建议使用单向数据流,也就是props可以父组件向子组件传值之后,通过data的数据项来读取,不建议直接使用prop的值,

  • 如果你传值是简单数据类型,那么可以在子组件的data属性中,将data值赋值为传递的属性值。(否则会报错)
  1. <child :status='status'></child>
  2. Vue.component("child",{
  3. template:"<span>{{value}}</span>",
  4. props:{
  5. status
  6. },
  7. data(){
  8. return {
  9. value:this.status
  10. }
  11. }
  12. })
  • 如果传值是引用数据类型,那么需要
    子组件
    props:{[“initCount”}
    由于js中对象以及数组是引用类型,所以当prop为对象或者数组时,会有双向绑定的效果,这点有利有弊

数据验证

  • 属性传入支持常规的数据格式验证,也支持自定义验证。
    常规的类型有String,Number,Boolean,Object,Array,Function ,可以设置多个类型,用数组实现.在其内部是通过insanceof来实现数据类型的判断。
  1. props:{
  2. len :{
  3. type:[String,Number] // 支持数组,存储多类型参数
  4. }
  5. }
  • 支持required是否必须字段,支持default设置默认值,dafault为一个值或者返回默认值的方法。
  • 支持validator ,对数据的内容进行严格的校验,可能是长度,格式,具体内容等,返回是否符合结果的布尔型结果即可。

动态组件

有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
上述内容可以通过 Vue 的 元素加一个特殊的 is 特性来实现:如果你希望组件不会每次都渲染、销毁,那么你可以设置v-once .其中两个组件的属性注入都可以写入。

  1. <!-- 组件会在 `currentTabComponent` 改变时改变 -->
  2. <component v-bind:is="currentTabComponent"></component>

在上述示例中,currentTabComponent 可以包括已注册组件的名字,或一个组件的选项对象。

  1. <template>
  2. <component :is="type" v-bind="linkProps(to)">
  3. <slot />
  4. </component>
  5. </template>
  6. <script>
  7. import { isExternal } from '@/utils/validate'
  8. export default {
  9. props: {
  10. to: {
  11. type: String,
  12. required: true
  13. }
  14. },
  15. computed: {
  16. isExternal() {
  17. return isExternal(this.to)
  18. },
  19. type() {
  20. if (this.isExternal) {
  21. return 'a'
  22. }
  23. return 'router-link'
  24. }
  25. },
  26. methods: {
  27. linkProps(to) {
  28. if (this.isExternal) {
  29. return {
  30. href: to,
  31. target: '_blank',
  32. rel: 'noopener'
  33. }
  34. }
  35. return {
  36. to: to
  37. }
  38. }
  39. }
  40. }
  41. </script>

异步加载组件

image.png

如何缓存组件

使用keep-alive抽象组件,并不会真正渲染到dom中
应用场景:多数在tab切换中
image.png

Vue组件抽离公共逻辑

查看Mixins

插槽分析内容

  • 插槽分发内容
  • 插槽详解
    和 HTML 元素一样,我们经常需要向一个组件传递内容,像这样:它可以是内容也可以是html代码。
  1. <alert-box>
  2. Something bad happened.
  3. </alert-box>

基本使用

首先需要在组件定义的时候,定义好插槽的位置。那么引用时

  1. Vue.component("link",{
  2. template:"<a v-bind:href="url" class="nav-link"> <slot></slot></a>"
  3. })

具名插槽 ,允许你将多个插槽通过name区分

  1. //模板
  2. <div class="container">
  3. <header>
  4. <slot name="header"></slot>
  5. </header>
  6. <main>
  7. <slot></slot>
  8. </main>
  9. <footer>
  10. <slot name="footer"></slot>
  11. </footer>
  12. </div>
  13. //使用时
  14. <base-layout>
  15. <template slot="header">
  16. <h1>Here might be a page title</h1>
  17. </template>
  18. <p>A paragraph for the main content.</p>
  19. <p>And another one.</p>
  20. <template slot="footer">
  21. <p>Here's some contact info</p>
  22. </template>
  23. </base-layout>

默认内容

可以在插槽内写好内置内容,支持传入新内容时覆盖。

  1. <button type="submit">
  2. <slot>Submit</slot>
  3. </button>

编译作用域

该插槽可以访问跟这个模板的其它地方相同的实例属性 (也就是说“作用域”是相同的)。但这个插槽不能访问 的作用域。例如尝试访问 url 是不会工作的。牢记一条准则:
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译

  1. <navigation-link url="/profile">
  2. Logged in as {{ user.name }}
  3. </navigation-link>

想要插槽内容提供数据的时候,可以通过常规的属性绑定实现:

  1. <ul>
  2. <li
  3. v-for="todo in todos"
  4. v-bind:key="todo.id"
  5. >
  6. <!-- 我们为每个 todo 准备了一个插槽,-->
  7. <!-- 将 `todo` 对象作为一个插槽的 prop 传入。-->
  8. <slot v-bind:todo="todo">
  9. <!-- 回退的内容 -->
  10. {{ todo.text }}
  11. </slot>
  12. </li>
  13. </ul>

那么进一步,如果我们想实现自定义的模板字符串,可以通过template,以及slot-scope实现插槽内容。(在 2.5.0+,slot-scope 不再限制在 元素上使用,而可以用在插槽内的任何元素或组件上)

  1. <todo-list v-bind:todos="todos">
  2. <!-- 将 `slotProps` 定义为插槽作用域的名字 -->
  3. <template slot-scope="slotProps">
  4. <!-- 为待办项自定义一个模板,-->
  5. <!-- 通过 `slotProps` 定制每个待办项。-->
  6. <span v-if="slotProps.todo.isComplete"></span>
  7. {{ slotProps.todo.text }}
  8. </template>
  9. </todo-list>

更进一步,在支持es2015的浏览器可以用结构拿到某些对应的参数。这样可以使得插槽内容更纯粹,只保留有用的数据。

  1. <todo-list v-bind:todos="todos">
  2. <template slot-scope="{ todo }">
  3. <span v-if="todo.isComplete"></span>
  4. {{ todo.text }}
  5. </template>
  6. </todo-list>

相关文档