Vue基础:组件--slot、异步组件、递归组件及其他

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>\
  '
})
奋飛 CSDN认证博客专家 技术管理 前端工程化
乐观、勇气、专注、果断、好奇、公正、慎思、真诚、追求极致追求完美、诚信!独立撰写了多个前端专题模块,访问量达百万级。多次负责组织大数据可视化前端架构平台开发工作。对前端新技术、新潮流具有很强的敏锐力和洞察力!
已标记关键词 清除标记
如下是我用emelent-ui组件写的一个导航组件。由于有多个子级菜单,这里使用了递归。 **首先,父组件是如下这样的** ``` <template> <el-menu background-color="#303133" text-color="#C0C4CC" active-text-color="#409EFF" class="el-menu" :collapse="true" > <navs :asideNav="asideNav"></navs> </el-menu> </template> <!-- :collapse="false" --> <script> import Navs from './components/Navs' export default { name: 'AsideNav', components: { Navs }, props: { asideNav: Array } } </script> <style lang="stylus" scoped> .el-menu--collapse>div>.el-menu-item span, .el-menu--collapse>div>.el-submenu>.el-submenu__title span height: 0 width: 0 overflow: hidden visibility: hidden display: inline-block .el-menu--collapse>div>.el-menu-item .el-submenu__icon-arrow, .el-menu--collapse>div>.el-submenu>.el-submenu__title .el-submenu__icon-arrow display: none; .el-menu border: none .el-menu:not(.el-menu--collapse) { width: 200px } ul color: #fff </style> ``` **子组件是如下这样的** ``` <template> <div> <el-submenu v-for="item of asideNav" :key="item.url" :index="item.url" v-if="item.son"> <template slot="title"> <i class="iconfont">{{item.icon}}</i> <span slot="title">{{item.name}}</span> </template> <navs :asideNav="item.son"></navs> // 递归自调用 </el-submenu> <el-menu-item v-else :index="item.url"> <i class="iconfont">{{item.icon}}</i> <span slot="title">{{item.name}}</span> </el-menu-item> </div> </template> <script> import Navs from './Navs' export default { name: 'Navs', components: { Navs }, props: { asideNav: Array // 父组件传递过来的json数据 } } </script> <style lang="stylus" scoped> </style> ``` 以上代码实际上已经解决了我要实现的无限级导航的要求,但有一个小bug。我这样写完以后,由于子组件模版内的根元素必须有一个div,所以导致emelent-ui导航模版在收缩的时候会出现一点点小问题,以至于我必须得修改css才能修正bug。后来想到可以使用render函数来重写一个模版组件、但由于我技术不娴熟,练了两天,只懂个皮毛。写完以后各种报错。 **如下是我用我勤劳的小双手写的带错误的render函数模版** ``` export default { props: { myData: Array }, render(createElement) { let data = this.myData let ret = [] return data.map(item => { if (item.son) { // console.log(item.son) return createElement('el-submenu', { attrs: {index: item.url} }, [ createElement('template', { slot: 'title' }, [ createElement('i', { attrs: {'class': 'iconfont'} }, [item.icon]), createElement('span', { attrs: {slot: 'title'} }, [item.name]) ]) ]) } else { return createElement('el-menu-item', { attrs: {index: item.url} }, [ createElement('i', { attrs: {'class': 'iconfont'} }, [item.icon]), createElement('span', { attrs: {slot: 'title'} }, [item.name]) ]) } }) } } ``` 写完这个控制台抛出了个错误,提示我 ![图片说明](https://img-ask.csdn.net/upload/201908/04/1564919266_485602.png) 归根究底,技术有限,实在是搞的头大,希望大佬根据我写的组件模版,帮我用render函数写一个模版出来。供我学习,参考,使用。感激不尽!!!
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页