Skip to content

列表项的增删改

学习目标

通过本章学习,你将全面掌握:

  • 列表项的添加操作(开头/末尾/指定位置)
  • 列表项的删除操作(单个/批量/条件删除)
  • 列表项的更新操作(单属性/多属性/批量更新)
  • 列表的排序和过滤
  • 列表的拖拽排序
  • 批量操作的实现
  • 性能优化策略
  • React 19中的最佳实践

第一部分:添加列表项

1.1 添加到末尾

jsx
function AddToEnd() {
  const [items, setItems] = useState([
    { id: 1, text: '项目1', createdAt: new Date() },
    { id: 2, text: '项目2', createdAt: new Date() }
  ]);
  
  const [inputValue, setInputValue] = useState('');
  
  const addItem = () => {
    if (inputValue.trim()) {
      const newItem = {
        id: Date.now(),
        text: inputValue,
        createdAt: new Date()
      };
      
      // 使用展开运算符添加到末尾
      setItems([...items, newItem]);
      setInputValue('');
    }
  };
  
  // 或使用concat
  const addItemWithConcat = () => {
    if (inputValue.trim()) {
      const newItem = {
        id: Date.now(),
        text: inputValue,
        createdAt: new Date()
      };
      
      setItems(items.concat(newItem));
      setInputValue('');
    }
  };
  
  return (
    <div>
      <input
        value={inputValue}
        onChange={e => setInputValue(e.target.value)}
        placeholder="输入内容"
      />
      <button onClick={addItem}>添加到末尾</button>
      
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.text} - {item.createdAt.toLocaleTimeString()}
          </li>
        ))}
      </ul>
    </div>
  );
}

1.2 添加到开头

jsx
function AddToStart() {
  const [items, setItems] = useState([
    { id: 1, text: 'A' },
    { id: 2, text: 'B' }
  ]);
  
  const addItem = () => {
    const newItem = {
      id: Date.now(),
      text: '新项目'
    };
    
    // 添加到开头
    setItems([newItem, ...items]);
  };
  
  // 或使用unshift的不可变版本
  const addItemAlternative = () => {
    const newItem = {
      id: Date.now(),
      text: '新项目'
    };
    
    setItems(prev => [newItem].concat(prev));
  };
  
  return (
    <div>
      <button onClick={addItem}>添加到开头</button>
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
    </div>
  );
}

1.3 插入到指定位置

jsx
function InsertAt() {
  const [items, setItems] = useState([
    { id: 1, text: 'A' },
    { id: 2, text: 'B' },
    { id: 3, text: 'C' },
    { id: 4, text: 'D' }
  ]);
  
  const insertAt = (index, text) => {
    const newItem = {
      id: Date.now(),
      text
    };
    
    // 在指定位置插入
    setItems([
      ...items.slice(0, index),
      newItem,
      ...items.slice(index)
    ]);
  };
  
  // 在某个项目后面插入
  const insertAfter = (id, text) => {
    const index = items.findIndex(item => item.id === id);
    if (index !== -1) {
      insertAt(index + 1, text);
    }
  };
  
  // 在某个项目前面插入
  const insertBefore = (id, text) => {
    const index = items.findIndex(item => item.id === id);
    if (index !== -1) {
      insertAt(index, text);
    }
  };
  
  return (
    <div>
      <button onClick={() => insertAt(0, '开头')}>在开头插入</button>
      <button onClick={() => insertAt(2, '中间')}>在位置2插入</button>
      <button onClick={() => insertAt(items.length, '末尾')}>在末尾插入</button>
      
      <ul>
        {items.map((item, index) => (
          <li key={item.id}>
            {item.text}
            <button onClick={() => insertBefore(item.id, 'Before')}>
              在前面插入
            </button>
            <button onClick={() => insertAfter(item.id, 'After')}>
              在后面插入
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

1.4 批量添加

jsx
function BatchAdd() {
  const [items, setItems] = useState([]);
  const [inputValue, setInputValue] = useState('');
  
  // 批量添加(从文本解析)
  const batchAdd = () => {
    const lines = inputValue.split('\n').filter(line => line.trim());
    
    const newItems = lines.map((line, index) => ({
      id: Date.now() + index,
      text: line.trim(),
      createdAt: new Date()
    }));
    
    setItems(prev => [...prev, ...newItems]);
    setInputValue('');
  };
  
  // 从JSON导入
  const importFromJSON = () => {
    try {
      const data = JSON.parse(inputValue);
      if (Array.isArray(data)) {
        setItems(prev => [...prev, ...data]);
        setInputValue('');
      }
    } catch (error) {
      alert('JSON格式错误');
    }
  };
  
  return (
    <div>
      <textarea
        value={inputValue}
        onChange={e => setInputValue(e.target.value)}
        placeholder="每行一个项目,或粘贴JSON数组"
        rows={5}
      />
      
      <button onClick={batchAdd}>批量添加</button>
      <button onClick={importFromJSON}>从JSON导入</button>
      
      <p>当前项目数: {items.length}</p>
      
      <ul>
        {items.map(item => (
          <li key={item.id}>{item.text}</li>
        ))}
      </ul>
    </div>
  );
}

第二部分:删除列表项

2.1 按ID删除

jsx
function DeleteById() {
  const [items, setItems] = useState([
    { id: 1, text: 'A' },
    { id: 2, text: 'B' },
    { id: 3, text: 'C' }
  ]);
  
  const deleteItem = (id) => {
    setItems(items.filter(item => item.id !== id));
  };
  
  // 带确认的删除
  const deleteWithConfirm = (id) => {
    const item = items.find(i => i.id === id);
    if (confirm(`确定要删除 "${item.text}" 吗?`)) {
      deleteItem(id);
    }
  };
  
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.text}
          <button onClick={() => deleteItem(item.id)}>
            删除
          </button>
          <button onClick={() => deleteWithConfirm(item.id)}>
            删除(确认)
          </button>
        </li>
      ))}
    </ul>
  );
}

2.2 按索引删除

jsx
function DeleteByIndex() {
  const [items, setItems] = useState(['A', 'B', 'C', 'D', 'E']);
  
  // 删除指定索引
  const deleteAt = (index) => {
    setItems(items.filter((_, i) => i !== index));
  };
  
  // 删除第一个
  const deleteFirst = () => {
    setItems(items.slice(1));
  };
  
  // 删除最后一个
  const deleteLast = () => {
    setItems(items.slice(0, -1));
  };
  
  // 删除多个(从index开始删除count个)
  const deleteMultiple = (index, count) => {
    setItems([
      ...items.slice(0, index),
      ...items.slice(index + count)
    ]);
  };
  
  return (
    <div>
      <button onClick={deleteFirst}>删除第一个</button>
      <button onClick={deleteLast}>删除最后一个</button>
      <button onClick={() => deleteMultiple(1, 2)}>删除第2、3个</button>
      
      <ul>
        {items.map((item, index) => (
          <li key={index}>
            {item}
            <button onClick={() => deleteAt(index)}>删除</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

2.3 条件删除

jsx
function ConditionalDelete() {
  const [items, setItems] = useState([
    { id: 1, text: 'A', completed: false, important: true },
    { id: 2, text: 'B', completed: true, important: false },
    { id: 3, text: 'C', completed: false, important: false },
    { id: 4, text: 'D', completed: true, important: true }
  ]);
  
  // 删除已完成的项目
  const deleteCompleted = () => {
    setItems(items.filter(item => !item.completed));
  };
  
  // 删除不重要的项目
  const deleteUnimportant = () => {
    setItems(items.filter(item => item.important));
  };
  
  // 删除满足条件的项目
  const deleteWhere = (predicate) => {
    setItems(items.filter(item => !predicate(item)));
  };
  
  return (
    <div>
      <button onClick={deleteCompleted}>删除已完成</button>
      <button onClick={deleteUnimportant}>删除不重要</button>
      <button onClick={() => deleteWhere(item => item.completed && !item.important)}>
        删除已完成且不重要的
      </button>
      
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.text}
            {item.completed && ' (已完成)'}
            {item.important && ' (重要)'}
          </li>
        ))}
      </ul>
    </div>
  );
}

2.4 批量删除

jsx
function BatchDelete() {
  const [items, setItems] = useState([
    { id: 1, text: 'A', selected: false },
    { id: 2, text: 'B', selected: false },
    { id: 3, text: 'C', selected: false },
    { id: 4, text: 'D', selected: false }
  ]);
  
  // 切换选中状态
  const toggleSelect = (id) => {
    setItems(items.map(item =>
      item.id === id ? { ...item, selected: !item.selected } : item
    ));
  };
  
  // 全选/取消全选
  const toggleAll = () => {
    const allSelected = items.every(item => item.selected);
    setItems(items.map(item => ({
      ...item,
      selected: !allSelected
    })));
  };
  
  // 删除选中项
  const deleteSelected = () => {
    const selectedCount = items.filter(item => item.selected).length;
    
    if (selectedCount === 0) {
      alert('请先选择项目');
      return;
    }
    
    if (confirm(`确定要删除 ${selectedCount} 个项目吗?`)) {
      setItems(items.filter(item => !item.selected));
    }
  };
  
  const selectedCount = items.filter(item => item.selected).length;
  const allSelected = items.length > 0 && items.every(item => item.selected);
  
  return (
    <div>
      <div className="batch-actions">
        <label>
          <input
            type="checkbox"
            checked={allSelected}
            onChange={toggleAll}
          />
          全选
        </label>
        
        <button
          onClick={deleteSelected}
          disabled={selectedCount === 0}
        >
          删除选中项 ({selectedCount})
        </button>
      </div>
      
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <input
              type="checkbox"
              checked={item.selected}
              onChange={() => toggleSelect(item.id)}
            />
            {item.text}
          </li>
        ))}
      </ul>
    </div>
  );
}

第三部分:更新列表项

3.1 更新单个属性

jsx
function UpdateProperty() {
  const [items, setItems] = useState([
    { id: 1, text: 'A', completed: false, priority: 'low' },
    { id: 2, text: 'B', completed: false, priority: 'medium' },
    { id: 3, text: 'C', completed: false, priority: 'high' }
  ]);
  
  // 切换完成状态
  const toggleComplete = (id) => {
    setItems(items.map(item =>
      item.id === id
        ? { ...item, completed: !item.completed }
        : item
    ));
  };
  
  // 更新优先级
  const updatePriority = (id, priority) => {
    setItems(items.map(item =>
      item.id === id
        ? { ...item, priority }
        : item
    ));
  };
  
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          <input
            type="checkbox"
            checked={item.completed}
            onChange={() => toggleComplete(item.id)}
          />
          <span style={{
            textDecoration: item.completed ? 'line-through' : 'none'
          }}>
            {item.text}
          </span>
          
          <select
            value={item.priority}
            onChange={e => updatePriority(item.id, e.target.value)}
          >
            <option value="low">低</option>
            <option value="medium">中</option>
            <option value="high">高</option>
          </select>
        </li>
      ))}
    </ul>
  );
}

3.2 更新文本内容

jsx
function UpdateText() {
  const [items, setItems] = useState([
    { id: 1, text: 'A', editing: false },
    { id: 2, text: 'B', editing: false }
  ]);
  
  const [editValue, setEditValue] = useState('');
  
  // 开始编辑
  const startEdit = (id, text) => {
    setItems(items.map(item =>
      item.id === id ? { ...item, editing: true } : item
    ));
    setEditValue(text);
  };
  
  // 保存编辑
  const saveEdit = (id) => {
    setItems(items.map(item =>
      item.id === id
        ? { ...item, text: editValue, editing: false }
        : item
    ));
    setEditValue('');
  };
  
  // 取消编辑
  const cancelEdit = (id) => {
    setItems(items.map(item =>
      item.id === id ? { ...item, editing: false } : item
    ));
    setEditValue('');
  };
  
  // 直接更新文本
  const updateText = (id, newText) => {
    setItems(items.map(item =>
      item.id === id ? { ...item, text: newText } : item
    ));
  };
  
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.editing ? (
            <div>
              <input
                value={editValue}
                onChange={e => setEditValue(e.target.value)}
                autoFocus
              />
              <button onClick={() => saveEdit(item.id)}>保存</button>
              <button onClick={() => cancelEdit(item.id)}>取消</button>
            </div>
          ) : (
            <div>
              <span>{item.text}</span>
              <button onClick={() => startEdit(item.id, item.text)}>
                编辑
              </button>
              <input
                type="text"
                onChange={e => updateText(item.id, e.target.value)}
                placeholder="直接输入新值"
              />
            </div>
          )}
        </li>
      ))}
    </ul>
  );
}

3.3 更新多个属性

jsx
function UpdateMultipleProperties() {
  const [items, setItems] = useState([
    {
      id: 1,
      title: '任务1',
      description: '描述1',
      status: 'pending',
      priority: 'low',
      assignee: '',
      dueDate: ''
    }
  ]);
  
  // 更新单个属性
  const updateProperty = (id, property, value) => {
    setItems(items.map(item =>
      item.id === id
        ? { ...item, [property]: value }
        : item
    ));
  };
  
  // 更新多个属性
  const updateMultiple = (id, updates) => {
    setItems(items.map(item =>
      item.id === id
        ? { ...item, ...updates }
        : item
    ));
  };
  
  // 标记为完成(更新多个属性)
  const markAsComplete = (id) => {
    updateMultiple(id, {
      status: 'completed',
      completedAt: new Date(),
      completedBy: 'currentUser'
    });
  };
  
  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          <input
            value={item.title}
            onChange={e => updateProperty(item.id, 'title', e.target.value)}
          />
          
          <textarea
            value={item.description}
            onChange={e => updateProperty(item.id, 'description', e.target.value)}
          />
          
          <select
            value={item.status}
            onChange={e => updateProperty(item.id, 'status', e.target.value)}
          >
            <option value="pending">待处理</option>
            <option value="in-progress">进行中</option>
            <option value="completed">已完成</option>
          </select>
          
          <button onClick={() => markAsComplete(item.id)}>
            标记为完成
          </button>
        </li>
      ))}
    </ul>
  );
}

3.4 批量更新

jsx
function BatchUpdate() {
  const [items, setItems] = useState([
    { id: 1, text: 'A', completed: false, archived: false },
    { id: 2, text: 'B', completed: false, archived: false },
    { id: 3, text: 'C', completed: false, archived: false }
  ]);
  
  // 全部标记为完成
  const completeAll = () => {
    setItems(items.map(item => ({
      ...item,
      completed: true,
      completedAt: new Date()
    })));
  };
  
  // 全部标记为未完成
  const uncompleteAll = () => {
    setItems(items.map(item => ({
      ...item,
      completed: false,
      completedAt: null
    })));
  };
  
  // 归档已完成的
  const archiveCompleted = () => {
    setItems(items.map(item =>
      item.completed
        ? { ...item, archived: true }
        : item
    ));
  };
  
  // 批量更新满足条件的项目
  const updateWhere = (predicate, updates) => {
    setItems(items.map(item =>
      predicate(item)
        ? { ...item, ...updates }
        : item
    ));
  };
  
  return (
    <div>
      <div className="batch-actions">
        <button onClick={completeAll}>全部完成</button>
        <button onClick={uncompleteAll}>全部未完成</button>
        <button onClick={archiveCompleted}>归档已完成</button>
        <button onClick={() => updateWhere(
          item => item.text.includes('重要'),
          { priority: 'high' }
        )}>
          标记"重要"为高优先级
        </button>
      </div>
      
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <input
              type="checkbox"
              checked={item.completed}
              readOnly
            />
            {item.text}
            {item.archived && ' (已归档)'}
          </li>
        ))}
      </ul>
    </div>
  );
}

第四部分:复杂操作

4.1 拖拽排序

jsx
function DragSortList() {
  const [items, setItems] = useState([
    { id: 1, text: 'A' },
    { id: 2, text: 'B' },
    { id: 3, text: 'C' },
    { id: 4, text: 'D' }
  ]);
  
  const [draggedId, setDraggedId] = useState(null);
  const [overIndex, setOverIndex] = useState(null);
  
  const handleDragStart = (id) => (e) => {
    setDraggedId(id);
    e.dataTransfer.effectAllowed = 'move';
  };
  
  const handleDragOver = (index) => (e) => {
    e.preventDefault();
    setOverIndex(index);
  };
  
  const handleDrop = (dropIndex) => (e) => {
    e.preventDefault();
    
    if (draggedId === null) return;
    
    const dragIndex = items.findIndex(item => item.id === draggedId);
    if (dragIndex === -1 || dragIndex === dropIndex) return;
    
    const newItems = [...items];
    const [draggedItem] = newItems.splice(dragIndex, 1);
    newItems.splice(dropIndex, 0, draggedItem);
    
    setItems(newItems);
    setDraggedId(null);
    setOverIndex(null);
  };
  
  const handleDragEnd = () => {
    setDraggedId(null);
    setOverIndex(null);
  };
  
  // 使用按钮移动
  const moveUp = (id) => {
    const index = items.findIndex(item => item.id === id);
    if (index > 0) {
      const newItems = [...items];
      [newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
      setItems(newItems);
    }
  };
  
  const moveDown = (id) => {
    const index = items.findIndex(item => item.id === id);
    if (index < items.length - 1) {
      const newItems = [...items];
      [newItems[index], newItems[index + 1]] = [newItems[index + 1], newItems[index]];
      setItems(newItems);
    }
  };
  
  return (
    <ul>
      {items.map((item, index) => (
        <li
          key={item.id}
          draggable
          onDragStart={handleDragStart(item.id)}
          onDragOver={handleDragOver(index)}
          onDrop={handleDrop(index)}
          onDragEnd={handleDragEnd}
          style={{
            padding: '10px',
            margin: '5px 0',
            background: draggedId === item.id ? '#e0e0e0' : 
                       overIndex === index ? '#f0f0f0' : 'white',
            border: '1px solid #ccc',
            cursor: 'move'
          }}
        >
          <span>{item.text}</span>
          
          <div className="move-buttons">
            <button
              onClick={() => moveUp(item.id)}
              disabled={index === 0}
            >

            </button>
            <button
              onClick={() => moveDown(item.id)}
              disabled={index === items.length - 1}
            >

            </button>
          </div>
        </li>
      ))}
    </ul>
  );
}

4.2 撤销/重做功能

jsx
function UndoRedoList() {
  const [items, setItems] = useState([
    { id: 1, text: 'A' },
    { id: 2, text: 'B' }
  ]);
  
  const [history, setHistory] = useState([[
    { id: 1, text: 'A' },
    { id: 2, text: 'B' }
  ]]);
  
  const [historyIndex, setHistoryIndex] = useState(0);
  
  // 添加到历史记录
  const addToHistory = (newItems) => {
    const newHistory = history.slice(0, historyIndex + 1);
    newHistory.push(newItems);
    setHistory(newHistory);
    setHistoryIndex(newHistory.length - 1);
    setItems(newItems);
  };
  
  // 撤销
  const undo = () => {
    if (historyIndex > 0) {
      const newIndex = historyIndex - 1;
      setHistoryIndex(newIndex);
      setItems(history[newIndex]);
    }
  };
  
  // 重做
  const redo = () => {
    if (historyIndex < history.length - 1) {
      const newIndex = historyIndex + 1;
      setHistoryIndex(newIndex);
      setItems(history[newIndex]);
    }
  };
  
  // 操作函数
  const addItem = (text) => {
    const newItems = [
      ...items,
      { id: Date.now(), text }
    ];
    addToHistory(newItems);
  };
  
  const deleteItem = (id) => {
    const newItems = items.filter(item => item.id !== id);
    addToHistory(newItems);
  };
  
  const updateItem = (id, text) => {
    const newItems = items.map(item =>
      item.id === id ? { ...item, text } : item
    );
    addToHistory(newItems);
  };
  
  return (
    <div>
      <div className="history-controls">
        <button onClick={undo} disabled={historyIndex === 0}>
          ↶ 撤销
        </button>
        <span>{historyIndex + 1} / {history.length}</span>
        <button onClick={redo} disabled={historyIndex === history.length - 1}>
          ↷ 重做
        </button>
      </div>
      
      <button onClick={() => addItem('New Item')}>
        添加项目
      </button>
      
      <ul>
        {items.map(item => (
          <li key={item.id}>
            <input
              value={item.text}
              onChange={e => updateItem(item.id, e.target.value)}
            />
            <button onClick={() => deleteItem(item.id)}>
              删除
            </button>
          </li>
        ))}
      </ul>
    </div>
  );
}

4.3 列表排序

jsx
function SortableList() {
  const [items, setItems] = useState([
    { id: 1, name: 'Charlie', age: 25, score: 85 },
    { id: 2, name: 'Alice', age: 30, score: 95 },
    { id: 3, name: 'Bob', age: 28, score: 90 }
  ]);
  
  const [sortBy, setSortBy] = useState('name');
  const [sortOrder, setSortOrder] = useState('asc');
  
  // 排序函数
  const sortedItems = useMemo(() => {
    return [...items].sort((a, b) => {
      let compareValue = 0;
      
      if (sortBy === 'name') {
        compareValue = a.name.localeCompare(b.name);
      } else if (sortBy === 'age') {
        compareValue = a.age - b.age;
      } else if (sortBy === 'score') {
        compareValue = a.score - b.score;
      }
      
      return sortOrder === 'asc' ? compareValue : -compareValue;
    });
  }, [items, sortBy, sortOrder]);
  
  // 切换排序顺序
  const toggleSortOrder = () => {
    setSortOrder(order => order === 'asc' ? 'desc' : 'asc');
  };
  
  // 应用排序(修改原数组)
  const applySort = () => {
    setItems(sortedItems);
  };
  
  return (
    <div>
      <div className="sort-controls">
        <select value={sortBy} onChange={e => setSortBy(e.target.value)}>
          <option value="name">按名称</option>
          <option value="age">按年龄</option>
          <option value="score">按分数</option>
        </select>
        
        <button onClick={toggleSortOrder}>
          {sortOrder === 'asc' ? '升序 ↑' : '降序 ↓'}
        </button>
        
        <button onClick={applySort}>
          应用排序
        </button>
      </div>
      
      <table>
        <thead>
          <tr>
            <th onClick={() => setSortBy('name')} style={{ cursor: 'pointer' }}>
              名称 {sortBy === 'name' && (sortOrder === 'asc' ? '↑' : '↓')}
            </th>
            <th onClick={() => setSortBy('age')} style={{ cursor: 'pointer' }}>
              年龄 {sortBy === 'age' && (sortOrder === 'asc' ? '↑' : '↓')}
            </th>
            <th onClick={() => setSortBy('score')} style={{ cursor: 'pointer' }}>
              分数 {sortBy === 'score' && (sortOrder === 'asc' ? '↑' : '↓')}
            </th>
          </tr>
        </thead>
        <tbody>
          {sortedItems.map(item => (
            <tr key={item.id}>
              <td>{item.name}</td>
              <td>{item.age}</td>
              <td>{item.score}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

第五部分:性能优化

5.1 使用useCallback优化

jsx
function OptimizedList() {
  const [items, setItems] = useState([]);
  
  // 缓存操作函数
  const addItem = useCallback((text) => {
    setItems(prev => [
      ...prev,
      { id: Date.now(), text }
    ]);
  }, []);
  
  const deleteItem = useCallback((id) => {
    setItems(prev => prev.filter(item => item.id !== id));
  }, []);
  
  const updateItem = useCallback((id, text) => {
    setItems(prev => prev.map(item =>
      item.id === id ? { ...item, text } : item
    ));
  }, []);
  
  const toggleItem = useCallback((id) => {
    setItems(prev => prev.map(item =>
      item.id === id ? { ...item, completed: !item.completed } : item
    ));
  }, []);
  
  return (
    <div>
      <button onClick={() => addItem('New')}>添加</button>
      
      <ul>
        {items.map(item => (
          <MemoListItem
            key={item.id}
            item={item}
            onDelete={deleteItem}
            onUpdate={updateItem}
            onToggle={toggleItem}
          />
        ))}
      </ul>
    </div>
  );
}

const MemoListItem = React.memo(function ListItem({ 
  item, 
  onDelete, 
  onUpdate, 
  onToggle 
}) {
  console.log('ListItem渲染:', item.id);
  
  return (
    <li>
      <input
        type="checkbox"
        checked={item.completed}
        onChange={() => onToggle(item.id)}
      />
      <input
        value={item.text}
        onChange={e => onUpdate(item.id, e.target.value)}
      />
      <button onClick={() => onDelete(item.id)}>删除</button>
    </li>
  );
});

5.2 虚拟化大列表

jsx
import { FixedSizeList } from 'react-window';

function VirtualizedEditableList() {
  const [items, setItems] = useState(
    Array.from({ length: 10000 }, (_, i) => ({
      id: i,
      text: `Item ${i}`,
      value: Math.random()
    }))
  );
  
  const deleteItem = useCallback((id) => {
    setItems(prev => prev.filter(item => item.id !== id));
  }, []);
  
  const updateItem = useCallback((id, updates) => {
    setItems(prev => prev.map(item =>
      item.id === id ? { ...item, ...updates } : item
    ));
  }, []);
  
  const Row = useCallback(({ index, style }) => {
    const item = items[index];
    
    return (
      <div style={style}>
        <EditableRow
          item={item}
          onDelete={deleteItem}
          onUpdate={updateItem}
        />
      </div>
    );
  }, [items, deleteItem, updateItem]);
  
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}

const EditableRow = React.memo(function EditableRow({ item, onDelete, onUpdate }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', padding: '0 10px' }}>
      <input
        value={item.text}
        onChange={e => onUpdate(item.id, { text: e.target.value })}
        style={{ flex: 1 }}
      />
      <button onClick={() => onDelete(item.id)}>删除</button>
    </div>
  );
});

第六部分:React 19增强

6.1 使用useOptimistic

jsx
'use client';

import { useOptimistic } from 'react';

function OptimisticList({ initialItems }) {
  const [items, setItems] = useState(initialItems);
  const [optimisticItems, addOptimisticUpdate] = useOptimistic(
    items,
    (state, { action, id, data }) => {
      switch (action) {
        case 'add':
          return [...state, data];
        case 'delete':
          return state.filter(item => item.id !== id);
        case 'update':
          return state.map(item =>
            item.id === id ? { ...item, ...data } : item
          );
        default:
          return state;
      }
    }
  );
  
  const handleAdd = async (text) => {
    const newItem = { id: Date.now(), text };
    addOptimisticUpdate({ action: 'add', data: newItem });
    
    await saveItem(newItem);
    setItems(prev => [...prev, newItem]);
  };
  
  const handleDelete = async (id) => {
    addOptimisticUpdate({ action: 'delete', id });
    
    await deleteItem(id);
    setItems(prev => prev.filter(item => item.id !== id));
  };
  
  return (
    <ul>
      {optimisticItems.map(item => (
        <li key={item.id}>
          {item.text}
          <button onClick={() => handleDelete(item.id)}>删除</button>
        </li>
      ))}
    </ul>
  );
}

练习题

基础练习

  1. 实现列表项的添加、删除功能
  2. 创建可编辑的列表项
  3. 实现批量选择和删除

进阶练习

  1. 实现列表的排序功能
  2. 创建可拖拽排序的列表
  3. 实现列表的搜索和过滤
  4. 添加撤销/重做功能

高级练习

  1. 优化大列表的增删改性能
  2. 实现虚拟化的可编辑列表
  3. 创建复杂的数据表格编辑器
  4. 使用React 19的useOptimistic实现乐观更新

通过本章学习,你已经掌握了列表项的增删改全部操作。这些是构建动态列表应用的基础技能。继续学习,探索更多列表操作技巧!