我们知道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?.datasetlet vid = ds |> keys |> find(it.indexOf('v-') == 0)if (!vid) {return}function r (n) {let chs = n.childrenif (!chs) {return}for (let c of chs) {let ds = c?.elm?.datasetif (ds) {ds[vid] = ''}r(c)}}r(rt)})return rt},name: 'jsx'}</script>
