如何按姓名首字母排序并分组渲染联系人列表
按姓名首字母分组实现通讯录效果
直接按以下两步处理即可,代码可直接复用:
- 第一步:先对原始联系人数据做排序、分组处理,输出结构化的分组数据
- 第二步:遍历分组数据,依次渲染字母标题和对应分组下的联系人条目
数据处理逻辑
以下工具函数可以直接把你提供的原始数组转换为按首字母排序、分组的结构,已经兼容字段为空的边界场景:
// 原始联系人数据 export const rows = [ { id: 1, name: 'Snow', email: 'Jon', contact: 35, avatar: '' }, { id: 2, name: 'Lannister', email: 'Cersei', contact: 42, avatar: '' }, { id: 3, name: 'Lannister', email: 'Jaime', contact: 45, avatar: '' }, { id: 4, name: 'Stark', email: 'Arya', contact: 16, avatar: '' }, { id: 5, name: 'Targaryen', email: 'Daenerys', contact: null, avatar: '' }, { id: 6, name: 'Melisandre', email: null, contact: 150, avatar: '' }, { id: 7, name: 'Clifford', email: 'Ferrara', contact: 44, avatar: '' }, { id: 8, name: 'Frances', email: 'Rossini', contact: 36, avatar: '' }, { id: 9, name: 'Roxie', email: 'Harvey', contact: 65, avatar: '' }, { id: 11, name: 'Snow', email: 'Jon', contact: 35 , avatar: ''}, { id: 12, name: 'Lannister', email: 'Cersei', contact: 42, avatar: '' }, ]; /** * 联系人按姓名首字母排序分组 * @param {Array} contactList 原始联系人列表 * @returns {Array} 分组后结构:[{ letter: '首字母', list: [对应联系人数组] }] */ function groupContactsByFirstLetter(contactList) { // 过滤name为空的无效数据,剩余数据按姓名字母顺序升序排列 const sortedList = contactList .filter(item => item?.name) .sort((a, b) => a.name.localeCompare(b.name)); const groupMap = new Map(); // 遍历排序后的数据,按首字母归组 sortedList.forEach(contact => { const firstLetter = contact.name.charAt(0).toUpperCase(); if (!groupMap.has(firstLetter)) { groupMap.set(firstLetter, []); } groupMap.get(firstLetter).push(contact); }); // 分组本身也按字母顺序排序,转换为方便渲染的数组结构 return Array.from(groupMap.entries()) .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) .map(([letter, list]) => ({ letter, list })); } // 调用即可得到最终用于渲染的分组数据 const groupedContacts = groupContactsByFirstLetter(rows);
处理后你会得到如下结构的数据,和通讯录需要的渲染结构完全匹配:
[ { letter: 'C', list: [/* Clifford 联系人项 */] }, { letter: 'F', list: [/* Frances 联系人项 */] }, { letter: 'L', list: [/* 3个Lannister 联系人项 */] }, { letter: 'M', list: [/* Melisandre 联系人项 */] }, { letter: 'R', list: [/* Roxie 联系人项 */] }, { letter: 'S', list: [/* Snow、Stark 共3个联系人项 */] }, { letter: 'T', list: [/* Targaryen 联系人项 */] }, ]
页面渲染参考
不管使用原生JS、Vue还是React,只要双层遍历上述分组数据即可,以React为例的简单渲染代码如下:
function ContactBook() { return ( <div className="contact-container"> {groupedContacts.map(group => ( <div key={group.letter} className="contact-group"> {/* 字母分类标题,可加样式做成常见的吸顶效果 */} <div className="group-letter-title">{group.letter}</div> {/* 渲染当前分组下所有联系人 */} {group.list.map(contact => ( <div key={contact.id} className="contact-item"> <div className="contact-avatar"> {contact.avatar ? <img src={contact.avatar} alt="" /> : contact.name.charAt(0)} </div> <div className="contact-meta"> <div className="contact-name">{contact.name}</div> <div className="contact-subtext">{contact.email ?? '未填写邮箱'}</div> </div> </div> ))} </div> ))} </div> ) }
补充说明
- 排序使用
localeCompare实现,相比直接对比字符编码,可正确兼容带特殊字符、非英文姓名的排序场景 - 已经默认处理了
name、email、contact字段为null的边界情况,不会出现运行报错 - 如果需要实现侧边字母快速导航,直接提取所有分组的
letter字段生成导航项即可,点击后滚动到对应分组位置就能实现完整的通讯录交互
内容的提问来源于stack exchange,提问作者Viet Nguyen
相关产品推荐
相关产品推荐

