深入了解组件

2019-10-02  本文已影响0人  A郑家庆

传递静态或动态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

上一篇下一篇

猜你喜欢

热点阅读