我们知道Vue的模板提供了一个简洁风格的DSL来描述响应式界面,而JSX却提供了更强的灵活性和抽象能力
在项目开发中有的时候会遇到一种需求,我们需要在同一个单文件组件中同时使用 template 和 jsx的能力,Vue框架本身好像并不允许这么干
通过简单的变通好像可以实现这个特性
jsx.vue
<script>
export default {
props: {
jsx: {
type: Function
}
},
render (createElement, context) {
return this.jsx(createElement, context)
},
name: 'jsx'
}
</script>
test.vue
<template>
<div>
<div>111</div>
<jsx :jsx="renderFn"></jsx>
<div>222</div>
</div>
</template>
<script>
import jsx from '../../components/utils/jsx'
export default {
name: 'test',
components: {
jsx
},
data () {
return {
count: 0
}
},
methods: {
onAdd () {
this.count++
},
renderFn (h, context) {
return <div>
<div>
<button onClick={this.onAdd}>add</button>
</div>
<div class="num">{this.count}</div>
</div>
}
}
}
</script>
<style scoped>
.num{
color: red;
}
</style>
原理也很简单,通过创建一个子组件 jsx.vue ,子组件使用render渲染,而子组件的render函数实际上是通过调用父组件传入的一个渲染函数,从而实现了好像在父组件可以混合template和jsx的能力,不过很快会发现一个问题,父组件如果定义了 scoped样式 ,将无定义渲染函数内部的样式
原因就是,虽然渲染函数是写在父组件的,但是vue却会认为这一部内容属于子组件(事实上好像也是如此)
所以我不得不对子组件的实现进行了hack,最后修改的组件如下:
<script>
import { keys, find } from 'ramda'
import { it } from 'partial-application.macro'
export default {
props: {
jsx: {
type: Function
}
},
render (h, context) {
let rt = this.jsx(h, context)
this.$nextTick(() => {
let ds = rt?.parent?.elm?.dataset
let vid = ds |> keys |> find(it.indexOf('v-') == 0)
if (!vid) {
return
}
function r (n) {
let chs = n.children
if (!chs) {
return
}
for (let c of chs) {
let ds = c?.elm?.dataset
if (ds) {
ds[vid] = ''
}
r(c)
}
}
r(rt)
})
return rt
},
name: 'jsx'
}
</script>