本文会介绍多种场景下的 jsx 和 template 相互使用,主要内容是插槽,包含常规 slot、slot 传值 等场景,不涉及非常基础的 jsx 语法使用(类似 v-for
是 jsx 的 map
函数等诸如此类的不介绍),因此可能无法覆盖全面,还请多多包涵。
长期写 react 深知其痛,这也是我司也在向 vue 方向靠拢,对 vue 的接受度也还可以(主要是相对完备的 jsx 支持,实在不行就写 jsx)。作为 jsx 的拥趸,也不忘在 vue 里灌输 jsx 思想(滑稽),这也算是一个学习记录吧。
没有说 vue 的模板语法不好的意思,vue 里 jsx 出现就是为了解决某些痛点,互相补足。所以经常看到有文章阔谈 jsx 与 template 优劣,一堆人在下边吵,觉得属实没必要,取长补短而已。
tsx 写法使用
这里以 vant 为例
注意: jsx 无法使用 unplugin-vue-components
插件自动导入样式,且组件本身也需要手动导入。
子组件接收 props
JSX 写法目前还是比较繁琐的,即声明 TS 类型之后,还要再声明运行时的 props 声明。不过官方文档有说明未来会对该部分优化。相关说明
- 常规 template 写法
<script setup lang="ts">
type MyProps = {
list: number[]
}
const { list = [] } = defineProps<MyProps>()
</script>
<template>
<div v-for="item in list">{{ item }}</div>
</template>
- JSX 第一种写法(vue 3.3+ 支持,其实第二种更为常见)
export default defineComponent(
(props: MyProps, ctx) => {
const { list = [] } = props
return () => (
<>
{list.map((item) => (
<div>{item}</div>
))}
</>
)
},
{
props: {
list: {
type: Array as PropType<number[]>,
default: () => [],
},
},
},
)
- JSX 写法二
export default defineComponent<MyProps>({
props: {
list: {
type: Array as PropType<number[]>,
default: () => [],
},
},
setup(props) {
return () => <>{props.list?.map((item) => <div>{item}</div>)}</>
},
})
slot 插槽
default slot(两种写法一致)
默认插槽的写法 jsx 和 template 都差不多,闭合标签内的内容默认插槽
- template 默认插槽写法
<template>
<van-button type="primary">
<div>this is default slot</div>
</van-button>
</template>
- jsx 默认插槽写法
return () => (
<>
<Button type="primary">
<div>this is default slot</div>
</Button>
</>
)
自定义插槽
比如 vant 的 cell
组件,给出了很多插槽,两种写法概括如下:
template
可以使用#xxx
的格式写- jsx 需要在
v-slots
对象中定义,对象的 key 就是插槽名,value 必须是一个函数返回 VNode。
- template 写法
<template>
<van-cell title="标题">
<template #icon>
<van-icon name="setting-o" />
</template>
<template #right-icon>
<div style="color: red;">right-icon 插槽</div>
</template>
</van-cell>
</template>
- jsx 写法
return () => (
<Cell
title="标题"
v-slots={{
icon: () => <Icon name="setting-o" />,
'right-icon': () => <div style={{ color: 'red' }}>right-icon 插槽</div>,
}}
/>
)
jsx 写法中,插槽使用 v-slots
且对象的 value 必须是一个函数返回
插槽传值
这个例子实际上是一个虚拟列表的封装,但是内容有点多,就简化代码了,方便读
子组件的插槽传值写法
- template 写法
<script setup lang="ts">
import { computed } from 'vue'
import type { MyProps } from './interface'
const { list } = defineProps<MyProps>()
const innerList = computed(() => {
// 组件内部对传入的数据进行统一处理,回传给父组件自定义渲染
return list.map((item) => item * 3)
})
</script>
<template>
<header>header</header>
<slot name="list" :list="innerList"></slot>
<footer>footer</footer>
</template>
- jsx 写法
import { computed, defineComponent, type SetupContext, type SlotsType, type VNode } from 'vue'
import type { MyProps } from './interface'
interface MySlot {
list?: (props: number[]) => VNode[]
}
// jsx 中,props 的运行时校验仍需手动声明,vue 官方未来计划会对此部分优化,类似于 defineProps,
// https://cn.vuejs.org/api/general.html#function-signature
const myProps = {
list: { type: Array, default: () => [] },
}
export default defineComponent(
(props: MyProps, ctx: SetupContext<object, SlotsType<MySlot>>) => {
const { list } = props
const innerList = computed(() => {
// 组件内部对传入的数据进行统一处理,回传给父组件自定义渲染
return list.map((item) => item * 3)
})
return () => (
<>
<header>header</header>
{ctx.slots.list?.(innerList.value)}
<footer>footer</footer>
</>
)
},
{
//声明 props 运行参数校验规则
props: myProps,
},
)
defineComponent
函数的第一个参数是回调函数,主要 jsx 代码就在这个回调函数里,第二个参数是一个对象,接收props
emits
等运行时参数
jsx 的插槽是通过 context 拿到 slots
,然后调用函数的,这也刚好对应了为什么我们使用 jsx 的 v-slots
时,对象的 value 必须是一个函数了,因为在 jsx 中,组件接收插槽就是一个函数并调用它。
父组件使用插槽传递的值
子组件就是上边的例子,我们分别使用 template 和 jsx 使用这个组件(Child 组件哪个写法都可以用)
- 父组件使用 template 写法
<template>
<Child :list="[1, 2, 3, 4, 5]">
<template #list="{ list }">
<div v-for="item in list" :key="item">{{ item }}</div>
</template>
</Child>
</template>
- 父组件 jsx 写法
export default defineComponent(() => {
return () => (
<>
<Child
list={[1, 2, 3, 4, 5]}
v-slots={{
list: (list: number[]) => list.map((item) => <div key={item}>{item}</div>),
}}
/>
{/* 如果是默认插槽,则不使用 v-slots 也可以 */}
<Child list={[1, 2, 3, 4, 5]}>
{/* default slot */}
{(list: number[]) => list.map((item) => <div key={item}>{item}</div>)}
</Child>
</>
)
})
标签:tsx,插槽,props,list,写法,item,template,Vue3,jsx
From: https://www.cnblogs.com/jsonq/p/18597739