在文章《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
是无法挂载组件的,此时需要:
- 使用
$mount
渲染组件或者渲染并挂载组件 - 使用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.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,可以发现很多有趣的功能。