slot分发内容

为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为内容分发。Vue中使用特殊的 <slot> 元素作为原始内容的插槽。

问题(编译作用域)

message 应该绑定到父组件的数据,还是绑定到子组件的数据?

<child-component>
  {{ message }}
</child-component>

答案是父组件。父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。

单个slot

除非子组件模板包含至少一个 <slot> 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性的 slot 时,父组件整个内容片段将插入到 slot 所在的 DOM 位置,并替换掉 slot 标签本身。在 <slot> 标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。

具名slot

<slot> 元素可以用一个特殊的属性 name 来配置如何分发内容。多个 slot 可以有不同的名字。具名 slot 将匹配内容片段中有对应 slot 特性的元素。仍然可以有一个匿名 slot,它是默认 slot,作为找不到匹配的内容片段的备用插槽。如果没有默认的 slot,这些找不到匹配的内容片段将被抛弃。

<div id="app">
  <my-component>
    <template>
      <p>父组件分发内容</p>
    </template>
  </my-component>
  <my-component2>
    <b>内容a</b>
    <template slot="title">
      父组件分发的title
    </template>
    <b>内容b</b>
  </my-component2>
</div>

<script>
const app = new Vue({
  el: '#app',
  components: {
    'my-component': {
      template: `<div><slot><p>备选内容</p></slot></div>`
    },
    'my-component2': {
        template: `<div><slot name="title">备选title</slot><slot><b>备选内容</b></slot></div>`
    }
  }
});
</script>

渲染结果:

<div>
  <p>父组件分发内容</p>
</div> 
<div>
  父组件分发的title
  <b>内容a</b>  
  <b>内容b</b>
</div>

完整示例参考地址:https://jsfiddle.net/381510688/tugxd14s/

作用域插槽

在子组件插槽中可以通过slot插槽标签的属性将数据传递到父组件要分发的内容当中,父组件要通过<template scope=""></template>模板来接收子组件插槽传递上来的数据。

<my-component>
  <template scope="props">
    <p>父组件分发内容</p>
    <p>{{props.message}}</p>
  </template>
</my-component>

<script>
const app = new Vue({
    el: '#app',
    components: {
    'my-component': {
        template: `<div><slot message="哈哈"></slot></div>`
    }
  }
});
</script>

渲染结果:

<div>
  <p>父组件分发内容</p> 
  <p>哈哈</p>
</div>

具代表性的列表组:

<my-list :list="['a.com', 'b.com']">
  <template slot="item" scope="props">
    <li class="link"><a>{{props.link}}</a></li>
  </template>
</my-list>

<script>
// ...
'my-list': {
  props: ['list'],
  template: `<ul><slot name="item" v-for="item in list" :link="item"></slot></ul>`
}
</script>

渲染结果:

<ul>
  <li class="link"><a>a.com</a></li>
  <li class="link"><a>b.com</a></li>
</ul>

完整示例参考地址:https://jsfiddle.net/381510688/j8qkbwbo/

动态组件

动态地绑定到它的 is 特性,让多个组件可以使用同一个挂载点,并动态切换:

<component v-bind:is="currentView">
  <!-- 组件在 vm.currentview 变化时改变! -->
</component>
var vm = new Vue({
  el: '#example',
  data: {
    currentView: 'home'
  },
  components: {
    home: { /* ... */ },
    posts: { /* ... */ },
    archive: { /* ... */ }
  }
})

keep-alive可以把切换出去的组件保留在内存中,保留它的状态,避免重新渲染。

<keep-alive>
  <component :is="currentView">
    <!-- 非活动组件将被缓存! -->
  </component>
</keep-alive>

杂项

编写可复用组件

可复用组件应当定义一个清晰的公开接口,同时也不要对其使用的外层数据作出任何假设。

  • Prop 允许外部环境传递数据给组件;
  • 事件允许从组件内触发外部环境的副作用;
  • 插槽允许外部环境将额外的内容组合在组件中。
<my-component
  :foo="baz"
  :bar="qux"
  @event-a="doThis"
  @event-b="doThat"
>
  <img slot="icon" src="...">
  <p slot="main-text">Hello!</p>
</my-component>
子组件引用

尽管有 prop 和事件,但是有时仍然需要在 JavaScript 中直接访问子组件。为此可以使用 ref 为子组件指定一个引用 ID。

<div id="parent">
  <user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({ el: '#parent' })
// 访问子组件实例
var child = parent.$refs.profile

注意:$refs 只在组件渲染完成后才填充,并且它是非响应式的。它仅仅是一个直接操作子组件的应急方案——应当避免在模板或计算属性中使用 $refs
ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过$el,获取DOM元素。

异步组件

Vue.js 允许将组件定义为一个工厂函数,异步地解析组件的定义。

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)
})

webpack 2 + ES2015 的语法时可以这样:

Vue.component(
  'async-webpack-example',
  () => import('./my-async-component')
)
组件命名约定

当注册组件 (或者 prop) 时,可以使用 kebab-case (短横线分隔命名)、camelCase (驼峰式命名) 或 PascalCase (单词首字母大写命名);在 HTML 模板中,请使用 kebab-case。

components: {
  myComponent: { /* ... */ }
}
<my-component/>
递归组件

一定要确保递归调用有终止条件,可以通过v-if进行控制。

递归组件示例:https://jsfiddle.net/381510688/nhhfL1bd/

组件间的循环引用

参考官网地址:https://cn.vuejs.org/v2/guide/components.html#组件间的循环引用

内联模板

如果子组件有 inline-template 特性,组件将把它的内容当作它的模板,而不是把它当作分发内容。这让模板编写起来更灵活。

<my-component inline-template>
  <div>
    <p>这些将作为组件自身的模板。</p>
    <p>而非父组件透传进来的内容。</p>
  </div>
</my-component>

但是 inline-template 让模板的作用域难以理解。使用 template 选项在组件内定义模板或者在 .vue 文件中使用 template 元素才是最佳实践。

X-Template

另一种定义模板的方式是在 JavaScript 标签里使用 text/x-template 类型,并且指定一个 id。例如:

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
  template: '#hello-world-template'
})

注意:在有很多大模板的演示应用或者特别小的应用中可能有用,其它场合应该避免使用,因为这将模板和组件的其它定义分离了。

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

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

Vue.component('terms-of-service', {
  template: '\
    <div v-once>\
      <h1>Terms of Service</h1>\
      ...很多静态内容...\
    </div>\
  '
})
Logo

前往低代码交流专区

更多推荐