美文网首页
深入了解组件

深入了解组件

作者: A郑家庆 | 来源:发表于2019-10-02 11:29 被阅读0次

    传递静态或动态Prop

    传入静态的值:

    <blog-post title="My journey with Vue"></blog-post>
    

    这时候值是一个字符串
    你也可以通过v-bind动态赋值:

    <blog-post v-bind:title="post.title"></blog-post>
    

    这时候值是一个js表达式

    传入一个布尔值

    // 包含该prop没有值的情况在内,都意味着true
    <blog-post is-pushlished></blog-post>
    <blog-post :is-pushlished="true"></blog-post>
    

    传入一个对象的所有属性

    如果你想要将一个对象的所有属性都作为prop传入,你可以使用不带参数的v-bind。例如,对于一个给定的对象post:

    post: {
       id: 1,
       title: 'My Journey with Vue'
    }
    // 下面的模版
    <blog-post v-bind="post"></blog-post>
    // 等价于
    <blog-post
      v-bind:id="post.id"
      v-bind:title="post.title"
    ></blog-post>
    

    单向数据流

    所有prop都使得其父子prop之间形成一个单向下行绑定:父级prop的更新会向下流动到子组件中,但是反过来不行。这样会防止从子组件意外改变父组件的状态,从而导致你的应用数据流向难以理解。额外的,每次父级组件发生更新时,子组件中所有prop都将会刷新为最新的值。
    总结:prop会在子组件创建之前传递,父组件通过prop向子组件传递基本数据类型和引用数据类型,如果是基本数据类型,这时候改变props的数据就会报错,如果是引用数据类型如果改变原始数据不会报错,但是重新赋值或改变某个属性值就会报错

    prop验证

    Vuecomponent('my-component', {
        props: {
           propA: Number,
           // 多个可能的类型
           propB: [String, Number],
           // 是否必传,如果为true,则父组件必须要传这个值,否则报错
           propC: {
               type: String,
               required: true
           },
           // 带有默认值的对象
           propD: {
               type: Object,
               default: () => { message: 'hello' }
           },
           // 自定义验证函数,如果不符合条件就会报错
           propE: {
               validator: (value) => {
                    return ['success', 'warning', 'danger'].indexOf(value) !== -1
               }
           }
        }
    })
    

    注意:prop会在一个组件实例创建之前进行验证,所以实例的属性(如data、computed等)在default或validator函数中是不可用的

    非prop的特性

    一个非prop特性是指传向一个组件,但是该组件并没有相应prop定义的特性。

    <bootstrap data-date-picker="activated"></bootstrap>
    

    data-date-picker="activated"这个特性就会自动添加到<bootstrap>的根元素上。

    替换/合并已有的特性

    假如子组件<bootstrap-date-input>的模板是这样的:

    <input type="date" class="form-control">
    

    这时候我们在子组件上添加一个type和一个class:

    <bootstrap-date-input  type="text" class="date-picker"></bootstrap-date-input>
    

    子组件的模版上已经有了这两个属性了,这时候我们发现子组件内部设置的type被外部的type替换成text,但是class合并成form-control和date-picker。
    总结:除了class和style特性外部和内部的根元素会合并起来,其他的值会被外部替换掉

    禁用特性继承

    如果你不希望组件的根元素继承特性,你可以在组件的选项中设置inheritAttrs:false。例如:

    Vue.component('my-component', {
        inheritAttrs:false,
        ....
    })
    

    这尤其适合配合实例的$attrs属性使用,该属性包含了传递给一个组件的特性名和特性值。

    自定义事件

    model属性

    一个组件上的v-model默认会利用名为value的prop和名为input的事件,但是像单选框、复选框等类型的输入控件可能会将value特性用于不同的目的。model选项可以用来避免这样的冲突:

    <base-checkbox v-model="lovingVue"></base-checkbox>
    
    Vue.component('base-checkbox', {
      model: {
        prop: 'checked',
        event: 'change'
      },
      props: {
        checked: Boolean
      },
      template: `
        <input
          type="checkbox"
          v-bind:checked="checked"
          v-on:change="$emit('change', $event.target.checked)"
        >
      `
    })
    

    上面我们可以看到model将从父组件传过来的属性不管是vlaue还是checked统一名称为checked,监听的事件不管是@input还是@change统一为change,这样子就不需要根据不同的情况写不同的属性和方法。
    这里的lovingVue的值将会传入这个名为checked的prop。同时当<base-checkbox>触发一个change事件并附带一个新的值的时候,这个logingVue的属性将会被更新。
    注意你仍然需要在组件的props选项里声明checked这个prop。

    .sync修饰符

    在有些情况下,我们可能需要对一个prop进行双向绑定。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,造成理解困难。
    这时候我们可以使用.sync修饰符,它的本质和v-model类似,只是一种缩写。

    <text-document
      v-bind:title="doc.title"
      v-on:update:title="doc.title = $event"
    ></text-document>
    

    上面的代码使用.sync就可以写成

    <text-document v-bind:title.sync="doc.title"></text-document>
    

    这样在子组件中,就可以通过下面的代码来实现对这个prop重新赋值的意图了。

    this.$emit('update:title', newTitle)
    

    v-model和.sync背景

    父子组件传递数据通过props,props传递的数据是单向数据流,但如果传递的是引用数据类型,那么改变子组件传递过来的值父组件的值也会发生改变,这样子虽然可以实现父子组件的双向绑定,但会牺牲数据流向的简洁性,使得数据难以理解,最好不要这样去做,在这个背景下v-model和.sync就出现了,这两种方式都实现父子组件数据的双向绑定。

    v-mode和.sync对比

    .sync从功能上看和v-model十分类似,都是为了实现数据的双向绑定,本质上也都不是真正的双向绑定,而是语法糖。
    相比较之下,.sync更加灵活,它可以给多个prop使用,而v-model在一个组件中只能有一个。
    从语义上来看,v-model绑定的值是指这个组件的绑定值,比如input组件,select组件,各种表单元素,这些组件所绑定的值使用v-model比较合适,因为v-model是一个语法糖,传递的各种类型的值比如vlaue、checked等等,vue已经处理了各种情况,不需要自己去分别定义传递的属性。其他情况没有这种语义,.sync会更合适。
    从写法上来看,.sync更加简洁,不需要在 子组件中定义props就可以直接实现数据双向绑定。
    这两种方式子组件都没有直接改变prop的值,只是通过事件监听的方式直接改变父组件的值。
    注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的属性名,类似 v-model。
    当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用:

    <text-document v-bind.sync="doc"></text-document>
    

    这样会把 doc 对象中的每一个属性 (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。
    将 v-bind.sync 用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”,是无法正常工作的,因为在解析一个像这样的复杂表达式的时候,有很多边缘情况需要考虑。

    参考文章:https://juejin.im/post/5d0489dff265da1ba84a8e41

    插槽

    我们为具名插槽和作用域插槽引入了一个新的统一的语法,即v-slot指令。它取代了slot和slot-scope这两个目前已被废弃但未被移除且仍在文档中的特性。

    插槽内容

    Vue实现了一套内容分发的API,将slot元素作为承载分发内容的出口,也被称为插槽,插槽的内容是写在自定义组件中间,插槽内容可以是包含任何模版代码或其他组件,这些内容会将slot元素替换,如果自定义组件内没有插槽,那么这个组件中间写的任何内容都会被抛弃。

    编译作用域

    当你想在一个插槽内容中使用数据时,例如:

    <template>
     <div id="demo">
      <navigation-link url="/profile">
        name: {{name}}
        url: {{url}}
      </navigation-link>
    </div>
    </template>
    <script>
    import navigationLink from './navigationLink'
    export default {
      components: {navigationLink},
      data () {
        return {
          name: 'jack'
        }
      }
    }
    </script>
    

    你会发现插槽内容跟模版其他地方一样可以访问相同的实例属性(也就是相同的作用域),而不能访问navigation-link子组件的作用域。
    作为一条规则,请记住:父级模版里的所有内容都是在父级作用域中编译的,子模版里的所有内容都是在子作用域中编译的。

    后备内容(备用内容)

    如果插槽内没有内容,我们希望能显示默认的内容,这时候我们可以在slot元素中写,如果没有插槽内容就会显示slot元素中内容,如果有内容就会替换slot元素中的内容。

    具名插槽

    有时我们需要多个插槽,例如对于一个带有如下模版的<base-layout>组件:

    <div class="container">
      <header>
        <slot name="header"></slot>
      </header>
      <main>
        <slot></slot>
      </main>
      <footer>
        <slot name="footer"></slot>
      </footer>
    </div>
    

    一个不带name的slot出口会带有隐含的名字default。
    在向具名插槽提供内容的时候,我们可以在一个template元素上使用v-slot指令,并以v-slot的参数的形式提供其名称:

    <base-layout>
      <template v-slot:header>
        <h1>Here might be a page title</h1>
      </template>
    
      <p>A paragraph for the main content.</p>
      <p>And another one.</p>
    
      <template v-slot:footer>
        <p>Here's some contact info</p>
      </template>
    </base-layout>
    

    现在 <template> 元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有 v-slot 的 <template> 中的内容都会被视为默认插槽的内容。
    注意:v-slot只能添加在一个template元素上(只有一种例外情况就是独占默认插槽的缩写语法,下面会讲到)

    作用域插槽

    如果我们想让插槽内容访问组件内的数据,这时候我们可以使用作用域插槽,将数据作为slot元素的一个特性绑定上去:

    <span>
      <slot v-bind:user="user">
        {{ user.lastName }}
      </slot>
    </span>
    

    绑定在slot元素上的特性被称为插槽porp。在父级作用域中,我们可以给v-slot带一个值来定义我们提供的插槽prop的名字:

    <current-user>
      <template v-slot:default="slotProps">
        {{ slotProps.user.firstName }}
      </template>
    </current-user>
    
    独占默认插槽的缩写语法

    当组件内只有默认插槽时,组件的标签就可以被当作插槽的模版来使用。这样我们就可以把v-slot直接用在组件上:

    <current-user v-slot:default="slotProps">
      {{slotProps.user.firstName}}
    </current-user>
    // 不带参数的v-slot被假定对应默认插槽,可以简写为
    <current-user v-slot="slotProps">
      {{slotProps.user.firstName}}
    </current-user>
    

    注意默认插槽的缩写语法不能和具名插槽混用,因为它会导致作用域不明确,只要出现多个插槽,请始终为所有的插槽使用完整的基于<template>的语法。

    解构插槽prop
    <current-user v-slot="{ user }">
      {{ user.firstName }}
    </current-user>
    // prop重命名
    <current-user v-slot="{ user: person }">
      {{ person.firstName }}
    </current-user>
    // 插槽prop是undefined
    <current-user v-slot="{ user = { firstName: 'Guest' } }">
      {{ user.firstName }}
    </current-user>
    

    动态插槽名

    动态指令参数也可以用在v-slot上,来定义动态的插槽名:

    <base-layout>
      <template v-slot:[dynamicSlotName]>
        ...
      </template>
    </base-layout>
    

    具名插槽的缩写

    跟v-on和v-bind一样,v-slot也有缩写,即把参数之前的所有内容(v-slot:)替换为字符#。例如v-slot:header可以重写为#header:

    <base-layout>
      <template #header>
        <h1>Here might be a page title</h1>
      </template>
    </base-layout>
    

    如果你希望使用缩写的话,你必须始终以明确插槽名取而代之:

    <current-user #default="{ user }">
      {{ user.firstName }}
    </current-user>
    

    总结:插槽有点像一个单独的组件,可以定义它的名称,也可以传值给插槽内容,插槽在设计基础组件的时候非常有用,比如我之前写的弹框组件,element-ui中的很多组件都有用到插槽,你可以在组件里面写你自定义的其他组件。

    处理边界的情况

    在大多数情况下,我们最好不要触达另一个组件实例内部或手动操作DOM元素。比如使用$refs、$parent、$children、$root等等,最好使用Vuex来管理应用的状态。

    混入

    混入提供了一种非常灵活的方式,来分发Vue组件中的可复用功能。

    选项合并

    当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行合并。

    data数据合并

    data中数据有同名选项时,如果是基本类型就会以组件数据为准,如果是引用类型就会合并。

    生命周期合并

    生命周期里的代码都会执行,并且混入对象的代码会先执行。

    methods、components、directives

    这三个对象将被合并为同一个对象,如果发生同名选项,以组件的为准。
    注意:Vue.extend()也使用同样的策略进行合并。

    自定义指令

    基本用法

    除了核心功能默认内置的指令比如v-model、v-show,Vue也允许注册自定义指令。一般用在需要对普通DOM元素进行底层操作,这时候就会用到自定义指令。比如业务需求是input输入框初始是聚焦的状态:

    // 注册一个全局自定义指令v-focus
    Vue.directive('focus', {
      // 当被绑定的元素插入到DOM中时
      inserted (el, binding) {
         el.focus()
      }
    })
    // 注册一个局部的指令
    directives: {
      focus: {
        inserted (el, binding) {
          el.focus()
        }
      }
    }
    
    动态指令参数

    指令的参数可以是动态的。例如,在v-myditective:[argument]="value"中,argument参数可以根据组件实例数据进行更新!这使得自定义指令可以在应用中被灵活使用。

    <div id="dynamicexample">
      <h3>Scroll down inside this section ↓</h3>
      <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
    </div>
    Vue.directive('pin', {
      bind: function (el, binding, vnode) {
        el.style.position = 'fixed'
        var s = (binding.arg == 'left' ? 'left' : 'top')
        el.style[s] = binding.value + 'px'
      }
    })
    
    new Vue({
      el: '#dynamicexample',
      data: function () {
        return {
          direction: 'left'
        }
      }
    })
    

    过滤器

    Vue允许你自定义过滤器,可被用于一些常见的文本格式化,或者是一些数据转换等等,但是过滤器在实践当中可以被其他方式取代,比如写一些公共方法去处理或者直接直接在methods去调用某个方法等等,所以现在用到的越来越少,这个功能就显得有点鸡肋。不过它有一个优势是别的方法实现比较麻烦的,链式调用,后面会讲到。

    基本用法

    过滤器可以用在两个地方:双花括号插值和v-bind表达式。

    // 在双花括号中
    {{ message | capitalize }}
    // 在v-bind中
    <div v-bind="rawId | formatId"></div>
    

    你可以在一个组件的选项中定义本地的过滤器:

    filters: {
      capitalize: function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
      }
    }
    

    或者在创建Vue实例之前全局 定义过滤器:

    Vue.filter('capitalize', function (value) {
      if (!value) return ''
      value = value.toString()
      return value.charAt(0).toUpperCase() + value.slice(1)
    })
    
    new Vue({
      // ...
    })
    

    过滤器可以串联:

    {{ message | filterA('arg1', 'arg2') | filterB }}
    

    在这个例子中,message表达式将作为filterA过滤器函数的第一个参数,'arg1'和'arg2'将作为第二个、第三个参数,filterA函数返回的结果又会作为filterB函数的第一个参数。

    key

    key的特殊属性主要用在Vue虚拟DOM算法,在新旧nodes对比时辨识vnodes。如果不实用key,Vue会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用key,它会基于key的变化重新排列元素顺序,并且会移除key不存在的元素。
    有相同父元素的子元素必须有特殊的key。重复的key会造成渲染错误。它可以用于强制替换元素/组件而不是重复使用它。

    Vue.extend()

    使用基础Vue构造器,创建一个'子类'。参数是一个包含组件选项的对象,data选项必须是函数。

    <div id="mount-point"></div>
    
    // 创建构造器
    var Profile = Vue.extend({
      template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
      data: function () {
        return {
          firstName: 'Walter',
          lastName: 'White',
          alias: 'Heisenberg'
        }
      }
    })
    // 创建 Profile 实例,并挂载到一个元素上。
    new Profile().$mount('#mount-point')
    

    结果如下:

    <p>Walter White aka Heisenberg</p>
    

    Vue.observable()

    让一个对象可响应。Vue内部会用它来处理data函数返回的对象。
    返回的对象可以直接用于渲染函数和计算属性内,并且会在发生改变时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景:

    const state = Vue.observable({ count: 0 })
    
    const Demo = {
      render(h) {
        return h('button', {
          on: { click: () => { state.count++ }}
        }, `count is: ${state.count}`)
      }
    }
    

    参考文章:https://segmentfault.com/a/1190000019292569

    相关文章

      网友评论

          本文标题:深入了解组件

          本文链接:https://www.haomeiwen.com/subject/cvpfpctx.html