Skip to content

渲染列表

🌟 数组到UI的转换

🔄 基本思路:数组转组件

  • 🧩 核心理念:将数据和UI分离,通过JavaScript数组方法动态生成组件
  • 📊 常见场景:显示用户列表、商品卡片、评论、菜单项等
  • 🛠️ 主要工具:JavaScript的map()filter()方法

🗂️ 使用map()方法渲染列表

🔍 基本步骤

  1. 📦 存储数据:将数据放入数组中
  2. 🔄 转换数据:使用map()方法将数据项转换为JSX元素
  3. 📄 返回结果:将生成的JSX元素数组包含在父元素中返回
jsx
// 基本示例:渲染人名列表
const people = [
  '凯瑟琳·约翰逊: 数学家',
  '马里奥·莫利纳: 化学家',
  '穆罕默德·阿卜杜勒·萨拉姆: 物理学家',
];

function PeopleList() {
  // 使用map()转换数据为JSX元素
  const listItems = people.map(person => 
    <li>{person}</li>
  );
  
  // 返回包含所有列表项的ul元素
  return <ul>{listItems}</ul>;
}

🎯 注意事项

  • 📝 每个列表项需要唯一key:避免"Each child in a list should have a unique key"警告
  • 🔄 JSX表达式直接使用:生成的JSX数组可以直接在其他JSX中使用
  • 🧠 两步法或内联法:可以先生成数组再使用,也可以直接在JSX内联使用map()

🔍 使用filter()方法筛选列表项

🧹 数据过滤技巧

  • 🔎 选择性渲染:只显示符合特定条件的列表项
  • 🔄 链式调用:可以先filter()map(),组合使用多个数组方法
  • 🧮 高效处理:减少渲染的项数,提高性能
jsx
// 筛选并渲染化学家列表
const people = [
  { name: '凯瑟琳·约翰逊', profession: '数学家' },
  { name: '马里奥·莫利纳', profession: '化学家' },
  { name: '穆罕默德·阿卜杜勒·萨拉姆', profession: '物理学家' },
  { name: '珀西·莱温·朱利亚', profession: '化学家' },
];

function ChemistsList() {
  // 1. 先过滤出化学家
  const chemists = people.filter(person => 
    person.profession === '化学家'
  );
  
  // 2. 将过滤后的数组转换为JSX元素
  const listItems = chemists.map(person =>
    <li key={person.name}>{person.name}</li>
  );
  
  return <ul>{listItems}</ul>;
}

🔑 理解和使用key

🏷️ 为什么需要key?

  • 🔄 元素身份识别:让React能够识别哪些项被添加、删除或重新排序
  • 🎯 精确更新:避免整个列表重新渲染,只更新变化的元素
  • 🛡️ 维持状态:保持组件状态,防止在重新渲染时丢失(如表单输入)

📌 key的正确使用

  • 🆔 唯一性:在兄弟元素之间必须唯一,不同列表间可以重复
  • 🔒 稳定性:key不应该随着时间或重新渲染而改变
  • 🧩 数据ID优先:尽量使用来自数据的唯一标识符作为key
jsx
// ✅ 正确使用key的例子
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map(todo => (
        <li key={todo.id}>
          {todo.text}
        </li>
      ))}
    </ul>
  );
}

⚠️ 避免的错误做法

  • 🚫 不要使用索引作为key:当列表项可能重新排序时,会导致意外问题
    jsx
    // 🔴 不好的做法
    {todos.map((todo, index) => (
      <li key={index}>...</li>
    ))}
  • 🚫 不要动态生成key:不要使用key={Math.random()}等方式随机生成key
    jsx
    // 🔴 错误做法
    <li key={Math.random()}>...</li>
  • 🚫 不要将key当作props传递:key仅供React内部使用,组件不会接收到key作为prop

🛠️ 正确设置key的方法

  • 💽 数据库ID:使用数据库中的主键或唯一标识符
  • 📝 本地生成ID:使用crypto.randomUUID()或类似库生成的ID
  • 📊 复合键:如果没有单一ID,可以组合多个字段创建唯一标识符
jsx
// 不同来源数据的key设置
// 1. 来自数据库的数据
<li key={user.id}>{user.name}</li>

// 2. 本地生成的数据
const [todos, setTodos] = useState([]);
function addTodo(text) {
  setTodos([...todos, {
    id: crypto.randomUUID(),
    text
  }]);
}

🧩 处理嵌套列表

📦 列表中的列表

  • 🔄 扁平化结构:尽可能将嵌套数据扁平化,便于渲染和管理
  • 🧠 分治思想:将复杂的嵌套列表拆分为单独的组件处理
  • 🔑 复合key:在嵌套结构中确保key在各层级都是唯一的
jsx
// 处理二维列表:类别和类别内的项目
function NestedLists({ categories }) {
  return (
    <div>
      {categories.map(category => (
        <section key={category.id}>
          <h2>{category.name}</h2>
          <ul>
            {category.items.map(item => (
              <li key={`${category.id}-${item.id}`}>
                {item.name}
              </li>
            ))}
          </ul>
        </section>
      ))}
    </div>
  );
}

🧱 组件化列表项

  • 📦 抽离组件:将复杂的列表项逻辑提取为独立组件
  • 🔄 提高可维护性:独立组件便于测试和修改
  • 🧠 关注点分离:将数据处理与UI渲染分开
jsx
// 将列表项抽离为独立组件
function PersonItem({ person }) {
  return (
    <li>
      <img
        src={getImageUrl(person)}
        alt={person.name}
      />
      <p>
        <b>{person.name}</b>
        {' - '}
        {person.profession}
      </p>
    </li>
  );
}

function PeopleList({ people }) {
  return (
    <ul>
      {people.map(person => (
        <PersonItem 
          key={person.id} 
          person={person} 
        />
      ))}
    </ul>
  );
}

💡 高级技巧与模式

🛠️ 高效渲染大列表

  • 🔄 虚拟列表:只渲染视口内可见的部分(使用react-windowreact-virtualized
  • 🧩 分页加载:一次只渲染有限数量的项目
  • 🔍 懒加载:结合IntersectionObserver实现滚动加载

🎨 灵活的列表展示

  • 📊 网格vs列表:根据同一数据源动态切换不同展示方式
  • 🔄 排序和过滤:提供用户交互控制列表顺序和筛选条件
  • 🔍 搜索功能:结合列表渲染实现实时搜索结果
jsx
// 结合排序和过滤的列表
function SortableList({ items, filter, sortBy }) {
  // 1. 过滤
  const filteredItems = items.filter(item => 
    item.category === filter || filter === 'all'
  );
  
  // 2. 排序
  const sortedItems = [...filteredItems].sort((a, b) => 
    sortBy === 'name' ? a.name.localeCompare(b.name) : a.id - b.id
  );
  
  // 3. 转换为JSX
  return (
    <ul>
      {sortedItems.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
}

📝 总结

  • 📋 列表渲染核心:使用map()将数组转换为JSX元素集合
  • 🔍 选择性渲染:使用filter()筛选需要显示的数据
  • 🔑 key的重要性
    • 确保列表元素的稳定身份识别
    • 必须在兄弟元素间唯一
    • 应基于稳定的数据ID,而非索引或随机值
  • 🧩 组件化思想:将复杂列表项抽离为独立组件,提高代码可维护性
  • 🔄 数据处理流程:数据准备(filter/sort) → 转换为JSX(map) → 渲染
  • ⚠️ 常见陷阱:避免索引作为key、不要动态生成key、不将key误认为普通prop

参考:React官方文档-渲染列表