前言
在学习了解组件复用的时候查看资料,看到了mixins,extend
方法同时也查到了高级函数(Higher-Order Components)和jsx
等字眼。听上去hoc和jsx有种高级的感觉,那得好好研究下。
概念
高阶组件定义 : 高阶组件是一个方法,这个方法接收一个原始组件作为参数,并返回新的组件。
jsx定义 : JSX 是一种类似于 XML 的 JavaScript 语法扩展 JSX 不是由引擎或浏览器实现的。相反,我们将使用像 Babel 这样的转换器将 JSX 转换为常规 JavaScript。基本上,JSX 允许我们在 JavaScript 中使用类似 HTML 的语法。
template模板生成方式
Vue 推荐使用在绝大多数情况下使用 template 来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,这就是 render 函数,它比 template 更接近编译器。
其实我们传入template模板之后,在vue底层也会将其转换成render函数
vue源码 src/platform/web/entry-runtime-with-compiler.js文件中代码如下,我们只需要关注tempalte变量
,首先判断配置项中有无传入render,如果没有也会有很多种情况,无论哪种情况都会给template变量赋值一个DOM字符串
,最终通过compileToFunctions函数将其转换成render函数
,通过结构赋值的方式赋值给render变量
,然后添加到实例上 具体可以看这篇文章
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
}
}
复制代码
render语法
使用之前需要的配置和使用语法这里我们主要来看props 、class、on(事件)、slots
这四个属性,首先来看代码
// 子组件
复制代码
// 父组件
type="text"
v-model="number">
type="number">
"render__incoming"> 外来者
"render__incoming-p"
slot="tag">我是p标签
复制代码
render函数的功能创建虚拟节点
就是代替template所以 在.vue 文件中必须省略template标签
。
createElement
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节点
hoc高阶函数
同样来看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
})
}
}
}
复制代码
// 父组件
test="15"
v-on:customize-click="onCuClick">
插入信息
"test">1516545
复制代码
// componentA 组件
"handleClick">props: {{prop}}
"test">
============
复制代码
实现过程
在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 } 键名为字符串,值为函数多个用数组
"onCuClick">
复制代码
所以我们需要在hoc组件中设置componentA
的父组件事件
jsx
首先我们来看下用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 (
"date" label="日期" width="180" />
)
}
}
复制代码
实现jsx中v-model
复制代码
实现jsx中 v-for
复制代码
jsx 扩展符
扩展符将一个对象的属性智能的合并到组件optiion中
复制代码
使用扩展符能方便我们插入多个class时,案例中发现如下报错
提示我们这个对象以及observed查看源码如下,定义在src/core/vdom/create-element.js 中的_createElement函数中 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 ...
错误信息。
复制代码
jsx sync实现
复制代码
总结
本文主要是认识了高阶组件和jsx的定义和基本的使用方
式。再这之前梳理了template生成的过程,底层都是通过render函数
来实现的。接下来讲了render的相关语
法,以及书写render会带来维护的困难,不易读懂的困扰。最后使用jsx语法简化render书写
,并且举了几个vue常用的指令,除了v-show其他的实现都要通过自己来实现
。当然jsx的知识点还有很多,在有了以上知识的基础上,再去研究jsx的更多语法以及使用场景也会更加轻松些。
参考
hoc issues
babel-plugin-transform-vue-jsx
腾讯web
vue 官网
render 函数