在学习了解组件复用的时候查看资料,看到了mixins,extend
方法同时也查到了高级函数(Higher-Order Components)和jsx
等字眼。听上去hoc和jsx有种高级的感受,那得好好研究下。css
高阶组件定义 : 高阶组件是一个方法,这个方法接收一个原始组件做为参数,并返回新的组件。html
jsx定义 : JSX 是一种相似于 XML 的 JavaScript 语法扩展 JSX 不是由引擎或浏览器实现的。相反,咱们将使用像 Babel 这样的转换器将 JSX 转换为常规 JavaScript。基本上,JSX 容许咱们在 JavaScript 中使用相似 HTML 的语法。vue
Vue 推荐使用在绝大多数状况下使用 template 来建立你的 HTML。然而在一些场景中,你真的须要 JavaScript 的彻底编程的能力,这就是 render 函数,它比 template 更接近编译器。node
其实咱们传入template模板以后,在vue底层也会将其转换成render函数
vue源码 src/platform/web/entry-runtime-with-compiler.js文件中代码以下,咱们只须要关注tempalte变量
,首先判断配置项中有无传入render,若是没有也会有不少种状况,不管哪一种状况都会给template变量赋值一个DOM字符串
,最终经过compileToFunctions函数将其转换成render函数
,经过结构赋值的方式赋值给render变量
,而后添加到实例上 具体能够看这篇文章git
if (!options.render) {
let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) === '#') {
template = idToTemplate(template)
}
} else if (template.nodeType) {
template = template.innerHTML
}
} else if (el) {
template = getOuterHTML(el)
}
if (template) {
const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
}
}
复制代码
使用以前须要的配置和使用语法这里咱们主要来看props 、class、on(事件)、slots
这四个属性,首先来看代码github
// 子组件
<script>
export default {
name: 'judge',
props: {
type: {
type: Number | String,
default: 1
}
},
render(h) {
return h(
'div',
{
class: {
default: true,
first: this.type == 1,
second: this.type == 2,
third: this.type == 3
},
on: {
click: this.onShowTypeClick
}
},
[this.$slots.default, this.$slots.tag]
)
},
methods: {
onShowTypeClick() {
console.log(this.type)
}
}
}
</script>
<style lang='scss' scoped>
.default {
text-align: center;
width: 200px;
line-height: 50px;
background-color: #eee;
}
.first {
background-color: #f66;
}
.second {
background-color: #c66;
}
.third {
background-color: #804;
}
</style>
复制代码
// 父组件
<input type="text"
v-model="number">
<judge :type="number">
<span class="render__incoming"> 外来者</span>
<p class="render__incoming-p"
slot="tag">我是p标签</p>
</judge>
复制代码
render函数的功能建立虚拟节点
就是代替template因此 在.vue 文件中必须省略template标签
。web
render函数默认接受createElement 函数
createElement默认接受三个参数编程
createElement(
// {String | Object | Function}
// 一个 HTML 标签字符串,组件选项对象,或者
// 解析上述任何一种的一个 async 异步函数。必需参数。
'div',
// {Object}
// 一个包含模板相关属性的数据对象
// {String | Array}
// 子虚拟节点 (VNodes),由 `createElement()` 构建而成,
[
'先写一些文字',
createElement('h1', '一则头条'),
this.$slots.default
]
)
复制代码
除了第一个参数,后面两个都为可选参数
,第二个参数是配置选项
如:style、attrs、props等,注意的是使用了domProps属性下的innerHTML后,会覆盖子节点和slot插入的元素
,第三个参数是关于子节点
如:再用createElement函数建立一个虚拟子节点,或者是接受slot传递过来的DOM节点element-ui
一样来看props 、class、on(事件)、slots
这四个属性的实现。数组
// hoc 组件
export default componentA => {
return {
// hoc 组件自己没有设置props 须要设置传入的组件相同的props
props: componentA.props,
render(h) {
let slots = {}
Object.keys(this.$slots).map(item => {
slots[item] = () => this.$slots[item]
return slots
})
return h(componentA, {
attrs: this.$attrs,
props: this.$props,
scopedSlots: slots,
on:this.$listeners
})
}
}
}
复制代码
// 父组件
<packer :test="15"
v-on:customize-click="onCuClick">
插入信息
<p slot="test">1516545</p>
</packer>
复制代码
// componentA 组件
<template>
<div>
<span @click="handleClick">props: {{prop}}</span>
<slot name="test"></slot>
============
<slot></slot>
</div>
</template>
复制代码
在vue中组件能够看做是一个对象
,里面包括一些方法属性如:data、props、methods、钩子函数等
,那hoc函数最终也是要返回这样一个对象。咱们从新来温习下hoc高阶组件的定义,一个函数接收一个组件,而且返回一个新的组件
。能够这样理解,接受一个组件,而且对整个组件进行包装而后返回出去
。固然hoc函数返回出去的对象应该也具备组件应该有的options如:data、props、methods、钩子函数等
。
来解释下本例中的实现过程,给hoc函数设置props和componentA
相同的props属性,这样咱们也能够经过packer向hoc组件传入props。在hoc接受到外部传入的值后经过this.$props将hoc实例中的props值都传递给componentA
,固然咱们也能够给hoc设置多于componentA
的props值。
attrs 指的是那些没有被声明为 props 的属性
经过scopedSlots实现hoc组件传入slot插槽
scopedSlots: {
// 实现默认插槽和具名插槽
default: ()=>this.$slots.default,
test: ()=>this.$slots.test
}
复制代码
listeners: (2.3.0+) 一个包含了全部在父组件上注册的事件侦听器的对象。这只是一个指向 data.on 的别名。
listeners指的就是在父组件上绑定的全部事件(是一个对象)格式为 { [key: string]: Function | Array } 键名为字符串,值为函数多个用数组
<packer v-on:customize-click="onCuClick"></packer>
复制代码
因此咱们须要在hoc组件中设置componentA
的父组件事件
首先咱们来看下用render函数实现使用element-ui table组件
export default {
render(h) {
return h(
'el-table',
{
props: {
data: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}
]
}
},
[
h('el-table-column', {
attrs: {
prop: 'date',
label: '日期',
width: '180'
}
})
]
)
}
复制代码
能够看到只是两次嵌套关系的render函数写起来的代码很难维护,这里就要用到jsx语法。它可让咱们回到更接近于模板的语法上。
export default {
data() {
return {
tableData: [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}
]
}
},
render(h) {
return (
<el-table data={this.tableData}>
<el-table-column prop="date" label="日期" width="180" />
</el-table>
)
}
}
复制代码
<script>
export default {
data() {
return {
initValue: ''
}
},
render() {
return (
<div class="input__wrap">
<div>initValue的值:{this.initValue}</div>
<el-input
value={this.initValue}
on-input={val => {
this.initValue = val
}}
/>
</div>
)
}
}
</script>
<style lang='scss' scoped>
.input__wrap {
width: 200px;
}
</style>
复制代码
<script>
export default {
data() {
return {
tagList: ['标签1', '标签2', '标签3']
}
},
render() {
return (
<div class="tag__wrap">
{this.tagList.map((item, index) => {
return <el-tag key={index}>{item}</el-tag>
})}
</div>
)
}
}
</script>
<style>
.tag__wrap {
width: 200px;
display: flex;
justify-content: space-between;
}
</style>
复制代码
扩展符将一个对象的属性智能的合并到组件optiion中
<script>
export default {
name: 'sync',
created() {
console.log(this)
},
data() {
return {
obj: {
class: ['sync__class1', 'sync__class2']
}
}
},
render() {
return <div {...this.obj} />
}
}
</script>
复制代码
使用扩展符能方便咱们插入多个class时,案例中发现以下报错
if (isDef(data) && isDef((data: any).__ob__)) {
process.env.NODE_ENV !== 'production' && warn(
`Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
'Always create fresh vnode data objects in each render!',
context
)
return createEmptyVNode()
}
复制代码
这段代码判断data中中定义的对象以及data的__ob__,若是已经定义(表明已经被observed,上面绑定了Oberver对象)则提示Avoid using observed data object ...
错误信息。
<script>
export default {
name: 'sync',
render() {
const obj = {
class: ['sync__class1', 'sync__class2']
}
return <div {...obj} />
}
}
</script>
复制代码
<script>
export default {
name: 'sync',
render() {
return (
<div class="sync__click" onClick={this.onClick}>
<el-pagination
total={10}
current-page={this.index}
page-size={5}
{...{
on: {
'update:currentPage': value => {
this.index = value
console.log('页数改变了')
}
}
}}
/>
</div>
)
},
data() {
return {
index: 1
}
},
methods: {
onClick() {
console.log('点击事件触发了')
}
}
}
</script>
<style>
.sync__click {
width: 400px;
height: 500px;
background-color: #efefef;
}
</style>
复制代码
本文主要是认识了高阶组件和jsx的定义和基本的使用方
式。再这以前梳理了template生成的过程,底层都是经过render函数
来实现的。接下来说了render的相关语
法,以及书写render会带来维护的困难,不易读懂的困扰。最后使用jsx语法简化render书写
,而且举了几个vue经常使用的指令,除了v-show其余的实现都要经过本身来实现
。固然jsx的知识点还有不少,在有了以上知识的基础上,再去研究jsx的更多语法以及使用场景也会更加轻松些。