Vue组件开发-高级玩法

在文章《Vue组件开发三板斧:prop、event、slot》中聊了常用的组件开发常用API和一些采坑心得,这里,再说说一些可能不太常用的高级玩法,可参考https://cn.vuejs.org/v2/api/

1. 组件挂载

方式一:components属性

我们常用的创建组件方式就是文件声明,例如,在一个假设的 headTop.js 或 headTop.vue 文件中定义组件。然后通过components 引入组件,将其挂载在DOM节点上。

// layout.vue文件
<template>
  <div class="fillcontain">
    <head-top></head-top>
    <div class="table_container container">
      <div class="container_wrap">
        <slot></slot>
      </div>
    </div>
  </div>
</template>

<script>
import headTop from '@/components/headTop'

export default {
  name: 'layout',
  components: {
    headTop // 引用组件
  }
}
</script>

<style lang="less">
@import '../style/mixin';
.table_container {
  min-height: calc(100% - 100px);
}
</style>

组件headTop是挂载在组件layout中某个DOM节点下。

方式二:$mount

还有两种方式可以创建组件:

  • new Vue()
  • Vue.extend()

new Vue()创建一个 Vue 实例时,都会有一个选项 el,可以用来指定实例的根节点。如果不写 el 选项,那组件就处于未挂载状态。看看最顶层的App.vue是如何挂载到根节点上的:

import App from './App'
......
new Vue({
  el: '#app',
  router,
  store,
  template: '<App/>',
  components: { App }
})

Vue.extend 是基于 Vue 构造器,创建一个“子类”,它的参数跟 new Vue 的基本一样,但是data写法和组件类似,需要返回一个函数。

import Vue from 'vue';

const AlertComponent = Vue.extend({
  template: '<div>{{ message }}</div>',
  data () {
    return {
      message: 'Hello world!'
    };
  },
});

Vue.extend是无法挂载组件的,此时需要:

  1. 使用$mount 渲染组件或者渲染并挂载组件
  2. 使用JS原生方法,挂载组件
// 方式一:仅仅渲染
const component = new AlertComponent().$mount();
// 通过JS方法组件添加到body节点上
document.body.appendChild(component.$el);

// 方式二:渲染挂载同时做
// 创建并挂载到 #app (会替换 #app)
new AlertComponent().$mount('#app')

应用场景:最常见的应该是自定义全局消息弹窗了。需要将组件挂载在body根节点上,此时,就可以通过$mount指定挂载节点。


同步歪歪一下React......

React 16 的portal也有异曲同工之妙。
portal可以帮助我们在JSX中跟普通组件一样直接使用dialog, 但是又可以让dialog内容层级不在父组件内,而是显示在独立于原来app在外的同层级组件。

HTML:

<div id="app-root"></div>
// 这里为我们定义Dialog想要放入的位置
<div id="modal-root"></div>

JS:

const modalRoot = document.getElementById('modal-root');

// Let's create a Modal component that is an abstraction around the portal API.
class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }

  componentDidMount() {
    // Append the element into the DOM on mount. We'll render
    // into the modal container element (see the HTML tab).
    modalRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    // Remove the element from the DOM when we unmount
    modalRoot.removeChild(this.el);
  }
  
  render() {
    // Use a portal to render the children into the element
    return ReactDOM.createPortal(
      this.props.children,
      this.el,
    );
  }
}

2. 渲染函数 render

Vue.js 2.0使用了 Virtual DOM(虚拟 DOM)来更新 DOM 节点,提升渲染性能。

一般我们写 Vue.js 组件,模板都是写在 <template> 内的,但它并不是最终呈现的内容,在 Vue.js 编译阶段,会解析为 Virtual DOM。与 DOM 操作相比,Virtual DOM 是基于 JavaScript 计算的,所以开销会小很多。下图演示了 Virtual DOM 运行的过程(来自网络):

vue.png

Vue.js 的 Render 函数就是将template 的内容改写成一个 JavaScript 对象。官网文档上有个极好的例子:https://cn.vuejs.org/v2/guide/render-function.html

Vue.component('my-component', {
  render: (h)=> {
    return h('div', {
            style: {
              color: 'red'
            }
          }, '自定义内容');
  }
})

应用场景:如果模板条件太多,用JS处理比HTML处理更加便利时,推荐使用render函数。

3. 递归组件

递归组件就是指组件在模板中调用自己,其核心是:在组件中设置一个 name 选项。如下:

<template>
  <div>
   这是一个组件,递归调用自己
   <my-component></my-component> 
  </div>
</template>
<script>
  export default {
    name: 'my-component'
  }
</script>

当然,上面的代码是有问题的。如果直接运行,会抛出 max stack size exceeded 的错误,因为没有终止条件,所以组件会无限的递归下去,循环至死。

所以,递归组件的第二个核心:设置终止条件
改造一下上面的代码:

<template>
  <div>
   这是一个组件,递归调用3次
   <my-component :count="count + 1" v-if="count <= 3"></my-component>
  </div>
</template>
<script>
  export default {
    name: 'my-component',
    props: {
      count: {
        type: Number,
        default: 1
      }
    }
  }
</script>

应用场景:树形组件

4. 组件通信:provide / inject

这对选项需要一起使用!( Vue.js 2.2.0 版本后新增的 API)
允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

是不是和React context很相似!!

// 父级组件提供 'foo'
var Provider = {
  provide: {
    foo: 'bar'
  },
  // ...
}

// 子组件注入 'foo'
var Child = {
  inject: ['foo'],
  created () {
    console.log(this.foo) // => "bar"
  }
  // ...
}

应用场景:某种意义上可以代替Vuex。如果你的项目只是需要全局共享一些公共状态信息,比如用户名,那么,用provide / inject足够了。

比如,在app.vue中注入根组件。

<script>
  export default {
    provide () {
      return {
        app: this
      }
    },
    data () {
      return {
        userInfo: null
      }
    },
    methods: {
      getUserInfo () {
        // 通过 ajax 获取用户信息后,赋值给 this.userInfo
        $.ajax('/user/info', (data) => {
          this.userInfo = data;
        });
      }
    },
    mounted () {
      this.getUserInfo();
    }
  }
</script>

然后,任何组件都可以使用到userInfo数据:

<template>
  <div>
    {{ app.userInfo }}
  </div>
</template>
<script>
  export default {
    inject: ['app']
  }
</script>

是不是比用Vuex简洁多了!

5. 数据更新:$set

之前提过,向响应式对象中添加一个属性,该新属性是非响应式的,视图也无法更新。所以为了保证新属性的响应性,可以用此API。

this.$set(data, 'checked', true);

小结

https://cn.vuejs.org/v2/api/是个好东西,多翻翻里面的api,可以发现很多有趣的功能。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,140评论 0 1
  • 回忆 首先,render函数中手写h=>h(app),new Vue()实例初始化init()和原来一样。$mou...
    LoveBugs_King阅读 2,265评论 1 2
  • 前几天想学学Vue中怎么编写可复用的组件,提到要对Vue的render函数有所了解。可仔细一想,对于Vue的ren...
    kangaroo_v阅读 116,003评论 13 171
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    小姜先森o0O阅读 9,389评论 0 72
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    流觞小菜鸟阅读 1,739评论 2 8