For
For 是 Vitarx 的列表渲染组件,用于根据数组数据动态渲染列表。它支持 key 优化、列表动画钩子,以及高效的差量更新。
基本用法
tsx
import { For, reactive } from 'vitarx'
function App() {
const items = reactive(['苹果', '香蕉', '橘子'])
return (
<ul>
<For each={items}>
{(item, index) => (
<li>
{index.value + 1}. {item}
</li>
)}
</For>
</ul>
)
}children 是一个渲染函数,接收两个参数:
item— 当前列表项的数据index— 当前项的索引(Ref<number>类型,响应式)
注意:
index是响应式引用,需要通过.value访问实际值。
属性
| 属性 | 类型 | 必填 | 说明 |
|---|---|---|---|
each | readonly T[] | 是 | 数据源数组 |
children | (item: T, index: Ref<number>) => RenderChild | 是 | 列表项渲染函数 |
key | keyof T | ((item: T) => any) | 否 | 列表项唯一标识,强烈建议提供 |
onLeave | (view: View, done: () => void) => void | 否 | 列表项离开动画钩子 |
onEnter | (view: View) => void | 否 | 列表项进入动画钩子 |
onBeforeUpdate | (children: IterableIterator<View>) => void | 否 | 列表更新前回调 |
onAfterUpdate | (children: IterableIterator<View>) => void | 否 | 列表更新后回调 |
key 属性
key 用于标识列表项的唯一性,帮助框架高效地复用和移动 DOM 节点。虽然 key 不是必填的,但强烈建议提供,否则框架会发出警告。
key 支持两种形式:
字符串属性名
当列表项是对象,且某个属性可以作为唯一标识时,直接传属性名:
tsx
import { For, reactive } from 'vitarx'
interface User {
id: number
name: string
}
function UserList() {
const users = reactive<User[]>([
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
])
return (
<For each={users} key="id">
{(user) => <div>{user.name}</div>}
</For>
)
}函数形式
当需要更灵活的 key 生成逻辑时,可以传函数:
tsx
<For each={users} key={(user) => user.id}>
{(user) => <div>{user.name}</div>}
</For>
key重复检测:框架会自动检测同一列表中是否存在重复的 key,并在开发模式下发出警告。
列表动画钩子
For 提供了四个动画钩子,让你可以在列表项增删时添加动画效果。
onLeave — 离开动画
当列表项被移除时触发。你需要手动调用 done() 来通知框架动画完成,框架才会真正移除 DOM 元素。
tsx
<For
each={items}
key="id"
onLeave={(view, done) => {
// 添加淡出动画
const el = view.node as HTMLElement
el.style.transition = 'opacity 0.3s'
el.style.opacity = '0'
// 300ms 后通知框架移除元素
setTimeout(done, 300)
}}
>
{(item) => <div>{item.name}</div>}
</For>onEnter — 进入动画
当新列表项被添加到 DOM 后触发。
tsx
<For
each={items}
key="id"
onEnter={(view) => {
// 添加淡入动画
const el = view.node as HTMLElement
el.style.opacity = '0'
requestAnimationFrame(() => {
el.style.transition = 'opacity 0.3s'
el.style.opacity = '1'
})
}}
>
{(item) => <div>{item.name}</div>}
</For>onBeforeUpdate / onAfterUpdate
在列表更新前后触发,可以用来记录位置、执行布局计算等。
tsx
<For
each={items}
key="id"
onBeforeUpdate={(children) => {
// 更新前:记录当前元素位置
console.log('列表即将更新')
}}
onAfterUpdate={(children) => {
// 更新后:重新计算布局
console.log('列表更新完成')
}}
>
{(item) => <div>{item.name}</div>}
</For>完整示例
下面是一个包含增删操作和动画效果的完整示例:
tsx
import { For, reactive, ref } from 'vitarx'
interface TodoItem {
id: number
text: string
}
function TodoApp() {
let nextId = 4
const todos = reactive<TodoItem[]>([
{ id: 1, text: '学习 Vitarx' },
{ id: 2, text: '写一个项目' },
{ id: 3, text: '部署上线' }
])
const inputText = ref('')
/** 添加待办 */
const addTodo = () => {
const text = inputText.value.trim()
if (!text) return
todos.push({ id: nextId++, text })
inputText.value = ''
}
/** 删除待办 */
const removeTodo = (id: number) => {
const index = todos.findIndex((item) => item.id === id)
if (index !== -1) todos.splice(index, 1)
}
return (
<div>
<h2>待办事项</h2>
<div>
<input
value={inputText}
onInput={(e) => {
inputText.value = (e.target as HTMLInputElement).value
}}
onKeyDown={(e) => {
if (e.key === 'Enter') addTodo()
}}
/>
<button onClick={addTodo}>添加</button>
</div>
<ul>
<For
each={todos}
key="id"
onEnter={(view) => {
const el = view.node as HTMLElement
el.style.opacity = '0'
el.style.transform = 'translateX(-20px)'
requestAnimationFrame(() => {
el.style.transition = 'all 0.3s ease'
el.style.opacity = '1'
el.style.transform = 'translateX(0)'
})
}}
onLeave={(view, done) => {
const el = view.node as HTMLElement
el.style.transition = 'all 0.3s ease'
el.style.opacity = '0'
el.style.transform = 'translateX(20px)'
setTimeout(done, 300)
}}
>
{(todo) => (
<li>
<span>{todo.text}</span>
<button onClick={() => removeTodo(todo.id)}>删除</button>
</li>
)}
</For>
</ul>
</div>
)
}下一步
- Transition — 使用 CSS 过渡动画替代手动动画
- Suspense — 异步内容占位
- Freeze — 组件缓存