列表渲染
在开发中,我们经常需要根据数组数据渲染一组相似的元素。Vitarx 提供了 For 组件来高效渲染动态列表。
基本用法
For 组件接收 each 属性作为数据源,children 作为渲染函数:
tsx
import { For, createApp } from 'vitarx'
function App() {
const fruits = ['苹果', '香蕉', '橘子']
return (
<ul>
<For each={fruits}>{(fruit) => <li>{fruit}</li>}</For>
</ul>
)
}
createApp(App).mount('#app')渲染函数接收两个参数:
- item:当前项的数据
- index:当前项的索引,是一个
Ref<number>(响应式引用)
tsx
<For each={fruits}>
{(fruit, index) => (
<li>
第 {index} 项: {fruit}
</li>
)}
</For>INFO
index 是 Ref<number> 类型,在模板中会自动解包显示数值,在 JavaScript 代码中需要通过 index.value 访问。
key 属性
key 用于标识列表项的唯一性,帮助框架高效地更新列表。强烈建议始终提供 key:
字符串形式
当数据是对象数组时,传入唯一标识的属性名:
tsx
import { For, ref, createApp } from 'vitarx'
function App() {
const users = ref([
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' }
])
return (
<ul>
<For each={users.value} key="id">
{(user) => <li>{user.name}</li>}
</For>
</ul>
)
}
createApp(App).mount('#app')函数形式
当你需要更灵活地生成 key 时,可以传入函数:
tsx
<For each={users.value} key={(user) => user.id}>
{(user) => <li>{user.name}</li>}
</For>为什么需要 key
key 的作用是帮助框架识别哪些项发生了变化(添加、删除、移动)。没有 key 时,框架无法高效地复用已有 DOM,可能导致不必要的重建和状态丢失。
- 有 key:框架通过 key 匹配新旧列表项,只更新变化的项,保持未变化项的 DOM 和状态
- 没有 key:框架无法精确匹配,可能重建本可以复用的 DOM,性能较差
WARNING
key 的值必须在列表中唯一。重复的 key 会导致渲染异常。不要使用数组索引作为 key,因为当列表项增删时索引会变化,失去 key 的意义。
响应式列表
当列表数据是响应式的(如 ref 包装的数组),数据变化时列表会自动更新:
tsx
import { For, ref, createApp } from 'vitarx'
function App() {
const items = ref([
{ id: 1, text: '学习 Vitarx' },
{ id: 2, text: '编写组件' },
{ id: 3, text: '构建应用' }
])
let nextId = 4
const addItem = () => {
items.value = [...items.value, { id: nextId++, text: `新任务 #123;nextId - 1}` }]
}
const removeItem = (id: number) => {
items.value = items.value.filter((item) => item.id !== id)
}
return (
<div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
<h2>待办列表</h2>
<button onClick={addItem} style={{ marginBottom: '12px' }}>
添加任务
</button>
<ul style={{ paddingLeft: '20px' }}>
<For each={items.value} key="id">
{(item) => (
<li style={{ margin: '8px 0' }}>
{item.text}
<button onClick={() => removeItem(item.id)} style={{ marginLeft: '8px' }}>
删除
</button>
</li>
)}
</For>
</ul>
</div>
)
}
createApp(App).mount('#app')完整示例
下面是一个功能完整的待办事项列表:
tsx
import { For, ref, createApp } from 'vitarx'
interface Todo {
id: number
text: string
done: boolean
}
function App() {
const todos = ref<Todo[]>([
{ id: 1, text: '学习 Vitarx', done: false },
{ id: 2, text: '编写组件', done: false },
{ id: 3, text: '构建应用', done: true }
])
const newText = ref('')
let nextId = 4
const addTodo = () => {
const text = newText.value.trim()
if (!text) return
todos.value = [...todos.value, { id: nextId++, text, done: false }]
newText.value = ''
}
const toggleTodo = (id: number) => {
todos.value = todos.value.map((todo) => (todo.id === id ? { ...todo, done: !todo.done } : todo))
}
const removeTodo = (id: number) => {
todos.value = todos.value.filter((todo) => todo.id !== id)
}
return (
<div style={{ padding: '20px', maxWidth: '400px', margin: '0 auto' }}>
<h2>待办事项</h2>
<div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
<input
value={newText}
onInput={(e) => {
newText.value = (e.target as HTMLInputElement).value
}}
onKeyDown={(e) => {
if (e.key === 'Enter') addTodo()
}}
placeholder="输入新任务"
style={{ flex: 1, padding: '8px' }}
/>
<button onClick={addTodo} style={{ padding: '8px 16px' }}>
添加
</button>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
<For each={todos.value} key="id">
{(todo) => (
<li
style={{
display: 'flex',
alignItems: 'center',
padding: '8px',
marginBottom: '4px',
background: '#f5f5f5',
borderRadius: '4px'
}}
>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
style={{ marginRight: '8px' }}
/>
<span
style={{
flex: 1,
textDecoration: todo.done ? 'line-through' : 'none',
color: todo.done ? '#999' : '#333'
}}
>
{todo.text}
</span>
<button
onClick={() => removeTodo(todo.id)}
style={{ color: '#e74c3c', cursor: 'pointer' }}
>
删除
</button>
</li>
)}
</For>
</ul>
<p style={{ color: '#999', fontSize: '14px', marginTop: '12px' }}>
共 {todos.value.length} 项,已完成 {todos.value.filter((t) => t.done).length} 项
</p>
</div>
)
}
createApp(App).mount('#app')API 参考
tsx
interface ForProps<T> {
/** 列表数据源 */
each: readonly T[]
/** 列表项渲染函数 */
children: (item: T, index: Ref<number>) => RenderChild
/** 唯一标识——属性名或函数 */
key?: keyof T | ((item: T) => any)
}
// 使用方式
;<For each={list} key="id">
{(item, index) => <div>{item.name}</div>}
</For>下一步
- 组件基础 — 深入学习组件的生命周期和高级用法