Vue2Study(四)

13.深入了解组件

1.Prop

1.Prop大小写

  • 浏览器对大小写不敏感,统一转换成小写

  • camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:

    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 在 HTML 中是 kebab-case 的 -->
    <blog-post post-title="hello!"></blog-post>

    Vue.component('blog-post', {
    // 在 JavaScript 中是 camelCase 的
    props: ['postTitle'],
    template: '<h3>{{ postTitle }}</h3>'
    }
  • 使用字符串模板,那么这个限制就不存在

2.Prop类型

3.传入静态或动态Prop

4.单项数据流

5.Prop验证

6.非prop的attribute

  • 替换/合并已有的attribute
  • 禁用attribute inheritAttrs: false

2.自定义组件

推荐始终使用kebab-case的事件名

  • v-model

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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)"
    >
    `
    })

    <base-checkbox v-model="lovingVue"></base-checkbox>

    这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。

    注意你仍然需要在组件的 props 选项里声明 checked 这个 prop。

  • 将原生事件绑定到组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    Vue.component('base-input', {
    inheritAttrs: false,
    props: ['label', 'value'],
    computed: {
    inputListeners: function () {
    var vm = this
    // `Object.assign` 将所有的对象合并为一个新对象
    return Object.assign({},
    // 我们从父级添加所有的监听器
    this.$listeners,
    // 然后我们添加自定义监听器,
    // 或覆写一些监听器的行为
    {
    // 这里确保组件配合 `v-model` 的工作
    input: function (event) {
    vm.$emit('input', event.target.value)
    }
    }
    )
    }
    },
    template: `
    <label>
    {{ label }}
    <input
    v-bind="$attrs"
    v-bind:value="value"
    v-on="inputListeners"
    >
    </label>
    `
    })

    现在 <base-input> 组件是一个完全透明的包裹器了,也就是说它可以完全像一个普通的 <input> 元素一样使用了:所有跟它相同的 attribute 和监听器都可以工作,不必再使用 .native 监听器。

  • .sync修饰符

    • 使用emit出发事件更新数据

    • this.$emit('update:title', newTitle)
      <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>
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12

      - 注意带有 `.sync` 修饰符的 `v-bind` **不能**和表达式一起使用 (例如 `v-bind:title.sync=”doc.title + ‘!’”` 是无效的)。取而代之的是,你只能提供你想要绑定的 property 名,类似 `v-model`。
      ### 3.插槽 slot

      - **父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。**

      - 后备内容 (Submit 作为后备内容 可被默认渲染或替换)

      ```html
      <button type="submit">
      <slot>Submit</slot>
      </button>
  • 具名插槽

    • v-slot 只能添加在 <template>,当被提供的内容只有默认插槽时,组件的标签才可以被当作插槽的模板来使用。这样我们就可以把 v-slot 直接用在组件上
  • 作用域插槽

    • 为了让插槽中的内容可以访问父级内容,可以将父级内容作为slot元素的一个attibute绑定上去:

      1
      2
      3
      4
      5
      <span>
      <slot v-bind:user="user">
      {{ user.lastName }}
      </slot>
      </span>
    • 绑定在 <slot> 元素上的 attribute 被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 v-slot 来定义我们提供的插槽 prop 的名字:

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

      1
      2
      3
      <current-user v-slot="slotProps">
      {{ slotProps.user.firstName }}
      </current-user>
    • 注意默认插槽的缩写语法不能和具名插槽混用

    • 只要出现多个插槽,请始终为所有的插槽使用完整的基于 <template> 的语法

    • 解构插槽prop

      • 作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里

      • <current-user v-slot="{ user: person }">  {{ person.firstName }}</current-user>
        <current-user v-slot="{ user = { firstName: 'Guest' } }">
          {{ user.firstName }}
        </current-user>
        
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11

        - 动态插槽名

        ```html
        <base-layout>

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

        </base-layout>
  • 具名插槽缩写 v-slot: -> #

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

4.动态组件&异步组件

  • 在动态组件上使用keep-alive

    1
    2
    3
    4
    <!-- 失活的组件将会被缓存!-->
    <keep-alive>
    <component v-bind:is="currentTabComponent"></component>
    </keep-alive>
  • 异步组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    Vue.component('async-example', function (resolve, reject) {
    setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
    template: '<div>I am async!</div>'
    })
    }, 1000)
    })

    Vue.component('async-webpack-example', function (resolve) {
    // 这个特殊的 `require` 语法将会告诉 webpack
    // 自动将你的构建代码切割成多个包,这些包
    // 会通过 Ajax 请求加载
    require(['./my-async-component'], resolve)
    })

    Vue.component(
    'async-webpack-example',
    // 这个动态导入会返回一个 `Promise` 对象。
    () => import('./my-async-component')
    )

    new Vue({
    // ...
    components: {
    'my-component': () => import('./my-async-component')
    }
    })

    处理加载状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const AsyncComponent = () => ({
    // 需要加载的组件 (应该是一个 `Promise` 对象)
    component: import('./MyComponent.vue'),
    // 异步组件加载时使用的组件
    loading: LoadingComponent,
    // 加载失败时使用的组件
    error: ErrorComponent,
    // 展示加载时组件的延时时间。默认值是 200 (毫秒)
    delay: 200,
    // 如果提供了超时时间且组件加载也超时了,
    // 则使用加载失败时使用的组件。默认值是:`Infinity`
    timeout: 3000
    })

5.处理边界情况

  • 依赖注入 provide inject

    1
    2
    3
    4
    5
    6
    7
    8
    provide 选项允许我们指定我们想要提供给后代组件的数据/方法。在这个例子中,就是 <google-map> 内部的 getMap 方法:
    provide: function () {
    return {
    getMap: this.getMap
    }
    }
    然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的 property:
    inject: ['getMap']
  • 程序化的事件侦听器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    mounted: function () {
    this.attachDatepicker('startDateInput')
    this.attachDatepicker('endDateInput')
    },
    methods: {
    attachDatepicker: function (refName) {
    var picker = new Pikaday({
    field: this.$refs[refName],
    format: 'YYYY-MM-DD'
    })

    this.$once('hook:beforeDestroy', function () {
    picker.destroy()
    })
    }
    }
  • 控制更新

    • $forceUpdate
    • 通过v-once创建低开销的静态组件