组件
组件命名
Vue JSX 遵循标准 JSX 的标签命名约定:
import UserCard from './UserCard'
export function App() {
return (
<div>
<UserCard />
<UserCard.Header />
</div>
)
}小写字母开头的标签始终按原生元素处理,包括未知标签和 kebab-case 标签,不会被解析为 Vue 组件:
<button /> // 原生 HTML 元素
<my-widget /> // 元素标签,不是组件使用大写标识符或成员表达式表示 Vue 组件。这样可以让组件解析行为保持确定,避免未来 HTML 新增原生标签时改变已有组件的含义。
定义组件
defineComponent 用来定义 Virtual DOM 组件,defineVaporComponent 用来定义 Vapor 组件。两者都可以接收一个 setup 函数,或者一个带 setup 的选项对象:
import { defineComponent, defineVaporComponent } from 'vue-jsx'
// Virtual DOM:setup 返回渲染函数。
const Counter = defineComponent((props: { count: number }) => {
return () => <button>{props.count}</button>
})
// Vapor:setup 直接返回块。
const VaporCounter = defineVaporComponent((props: { count: number }) => (
<button>{props.count}</button>
))// 选项对象形式也可以,两个入口都支持。
const Counter = defineComponent({
props: { count: Number },
setup(props) {
return () => <button>{props.count}</button>
},
})导入来源
vue-jsx 也提供了这两个函数,与 vue 不同的是 inheritAttrs 默认为 false 并使用 attrs 作为 props,意味着不用提前定义 props 了:
import { defineComponent, defineVaporComponent } from 'vue-jsx'inheritAttrs 默认为 false
属性不会自动落到根元素上,而是留在 attrs 里,由组件决定展开到哪个元素:
import { defineVaporComponent } from 'vue-jsx'
const Button = defineVaporComponent(() => <button>Click</button>)
// 渲染结果是 `<button>Click</button>`,`data-test` 不会应用到根元素上。
export default () => <Button data-test="submit" />这样透传就是显式的:渲染多个元素的组件不再依赖 Vue 去猜测哪个是根元素,包装组件也无法把属性泄漏到它并不希望的元素上。
用属性充当 props
当组件没有声明 props 选项时,属性会作为第一个参数传给 setup。它与 useAttrs() 返回的是同一个对象,因此即使没有任何 props 选项声明 title,也可以读取 props.title:
import { defineVaporComponent } from 'vue-jsx'
const Card = defineVaporComponent((props: { title: string }) => (
<section>
<h2>{props.title}</h2>
</section>
))
export default () => <Card title="Hello" />For
Vue JSX 为 Virtual DOM 提供了 For,为 Vapor 模式提供了 VaporFor。两个组件都能直接从 in 推断 item 和 index 类型,不依赖指令专用的语言工具。
Virtual DOM
从 vue-jsx 导入 For,并在默认插槽中返回带 key 的节点:
import { defineComponent, ref } from 'vue'
import { For } from 'vue-jsx'
export default defineComponent(() => {
const users = ref([
{ id: 1, name: 'Ada' },
{ id: 2, name: 'Grace' },
])
return () => (
<ul>
<For in={users.value}>
{(user, index) => (
<li key={user.id}>
{index}: {user.name}
</li>
)}
</For>
</ul>
)
})For 使用 Vue 的 keyed Fragment 列表渲染。请为每一项返回的根节点设置稳定的 key,以便 Vue 正确复用和移动已有节点。
Vapor 模式
在 Vapor 模式编译的组件中使用 VaporFor:
import { ref } from 'vue'
import { VaporFor } from 'vue-jsx'
export default () => {
const users = ref([
{ id: 1, name: 'Ada' },
{ id: 2, name: 'Grace' },
])
return (
<ul>
<VaporFor in={users.value}>
{(user, index) => (
<li>
{user.name},位置:{index.value}
</li>
)}
</VaporFor>
</ul>
)
}Vapor 插槽接收到的 index 是 ShallowRef<number>。在 JavaScript 中通过 index.value 读取;插入、删除或移动列表项后,JSX 中使用的 index 会保持响应式更新。
也可以从 Vapor 专用入口导入更短的别名:
import { For } from 'vue-jsx/vapor'这里的 For 与 VaporFor 是同一个组件。
Vapor 模式的稳定 key
VaporFor 默认使用 item 本身作为 key,适合对象引用保持不变的列表。当新对象仍表示同一条数据时,可以通过 getKey 提供稳定 key:
<VaporFor in={users.value} getKey={(user) => user.id}>
{(user, index) => (
<li>
{user.value.name},位置:{index.value}
</li>
)}
</VaporFor>提供 getKey 后,插槽中的 item 也会变成 ShallowRef。这样 Vapor 可以根据稳定 key 复用已有 block,同时把 user.value 更新为最新的 item 对象。
支持的数据源
两个组件都支持数组、字符串、数字、普通对象、Set 和 Map。
数组和其他 iterable 的插槽参数为 (item, index);普通对象的插槽参数为 (value, key, index)。在 VaporFor 中,对象的 key 和 index 同样是 shallow ref。
需要原生 TypeScript 推断和高效列表更新时,推荐使用这两个组件。对于不关心 keyed 更新的小型或静态列表,也可以继续使用 Array.prototype.map()。