跳到主要内容

Vue3 和 JSX -slot 的一些问题

JSX 和 slot 插槽

  • slot 是 Vue 发明的概念,为了完善 template 的能力
  • slot 是初学者的“噩梦”,特别是:作用域 slot
  • 使用 JSX 就容易理解,JSX 本质是 js

使用 JSX 和 slot 实现 tabs 功能

列子,有这样一个 tabs 组件 ./tabs-jsx/Index.vue

<template>
<tabs default-active-key="1" @change="onTabsChange">
<tab-panel key="1" title="title1">
<div>tab panel content 1</div>
</tab-panel>
<tab-panel key="2" title="title1">
<div>tab panel content 2</div>
</tab-panel>
<tab-panel key="3" title="title1">
<div>tab panel content 3</div>
</tab-panel>
</tabs>
</template>

<script>
import TabPanel from "./TabPanel";
import Tabs from "./Tabs";

export default {
name: "index",
components: {TabPanel, Tabs},
methods: {
onTabsChange(key) {
console.log('tab changed', key)
}
},
}
</script>

然后 ./tabs-jsx/TabPanel.vue 有一个 slot

<template>
<slot></slot>
</template>

<script>
export default {
name: "TabPanel",
props: ['key', 'title']
}
</script>

关键的在 ./tabs-jsx/Tabs.jsx 中,直接使用 filter 等 js 语法

import {ref} from "vue";

export default {
name: "Tabs",
props: ['defaultActiveKey'],
emits: ['change'],
setup(props, context) {
const children = context.slots.default()
const titles = children.map(panel => {
const {title, key} = panel.props || {}
return {
title,
key
}
})

// 当前 actKey
const actKey = ref(props.defaultActiveKey)

function changeActKey(key) {
actKey.value = key
context.emit('change', key)
}

// jsx
const render = () => <>
<div>
{/* 渲染 buttons */}
{titles.map(titleInfo => {
const {title, key} = titleInfo
return <button
key={key}
style={{color: actKey.value === key ? 'blue' : '#333'}}
onClick={() => changeActKey(key)}
>{title}</button>
})}
</div>

<div>
{children.filter(panel => {
const {key} = panel.props || {}
// 匹配上,则显示,否则隐藏
return actKey.value === key ? true : false
})}
</div>
</>

return render
}
}

使用 JSX 实现作用域 slot 插槽

首先回顾下作用域 slot 的使用

在 template 中使用作用域 slot 插槽

子文件 ./scoped-slot-template/Child.vue

<template>  
<slot :msg="msg"></slot>
</template>

<script>
export default {
name: "Child",
data() {
return {
msg: '作用域插槽 Child'
}
}
}
</script>

在父文件 ./scoped-slot-template/Index.vue 通过作用域 slot 拿到子文件的 msg

<template>
<child>
<template v-slot:default="msgProp">
<p>scoped slot template {{ msgProp.msg }}</p>
</template>
</child>
</template>

<script>
import Child from "./Child";

export default {
name: "Index",
components: {Child}
}
</script>

在 JSX 实现作用域 slot 插槽

父文件 ./scoped-slot-jsx/Index.jsx,传递 render 函数

import {defineComponent} from "vue"
import Child from './Child'

export default defineComponent(() => {
function render(msg) {
return <>{msg}</>
}

return () => {
return <>
<p>scoped slot Jsx <Child render={render}></Child></p>
</>
}
})

在子文件 ./scoped-slot-jsx/Child.jsx 执行 render 函数交给父组件显示

import {defineComponent, ref} from "vue"

export default defineComponent({
props: ['render'],
setup(props) {
const msgRef = ref('作用域插槽 Child')

return () => {
return <>
{props.render(msgRef.value)}
</>
}
}
})