📅 今天我们继续 50 个小项目挑战!——LiveUserFilter组件

仓库地址:https://gitee.com/hhm-hhm/50days50projects.git

​​​​​​

创建一个功能强大且视觉美观的实时用户搜索过滤组件。这个组件能够从远程 API 获取用户数据,并根据用户输入的关键词实时过滤并展示结果,非常适合用于构建用户目录、联系人列表或任何需要搜索功能的界面。

让我们开始吧!🚀

🌀 组件目标

  • 实现基于姓名和/或位置的实时用户搜索过滤
  • 展示加载状态和无结果状态
  • 利用 Tailwind CSS 快速构建基础样式,并通过内联样式实现精确控制
  • 优化用户体验,提供流畅的搜索反馈

🔧 LiveUserFilter.tsx组件实现

import React, { useState, useEffect, useMemo } from 'react'

// 定义用户数据类型(基于 randomuser.me API)
interface User {
    login: { uuid: string }
    name: { first: string; last: string }
    location: { city: string; country: string }
    picture: { large: string }
}

const LiveUserFilter: React.FC = () => {
    const [users, setUsers] = useState<User[]>([])
    const [searchTerm, setSearchTerm] = useState<string>('')
    const [isLoading, setIsLoading] = useState<boolean>(true)

    // 获取用户数据
    const fetchUsers = async () => {
        try {
            setIsLoading(true)
            const response = await fetch('https://randomuser.me/api?results=50')
            if (!response.ok) throw new Error('Failed to fetch')
            const data = await response.json()
            setUsers(data.results)
        } catch (error) {
            console.error('Error fetching users:', error)
        } finally {
            setIsLoading(false)
        }
    }

    // 组件挂载时获取数据
    useEffect(() => {
        fetchUsers()
    }, [])

    // 过滤用户(等价于 Vue 的 computed)
    const filteredUsers = useMemo(() => {
        if (!searchTerm.trim()) return users
        const term = searchTerm.toLowerCase()
        return users.filter((user) => {
            const fullName = `${user.name.first} ${user.name.last}`.toLowerCase()
            const location = `${user.location.city} ${user.location.country}`.toLowerCase()
            return fullName.includes(term) || location.includes(term)
        })
    }, [users, searchTerm])

    return (
        <div className="flex min-h-screen items-center justify-center overflow-hidden bg-gray-900 font-sans">
            <div className="w-72 overflow-hidden rounded-lg bg-white shadow-md">
                {/* 头部搜索区域 */}
                <div className="bg-blue-600 p-6 text-white">
                    <h4 className="mb-1 text-xl font-bold">Live User Filter</h4>
                    <small className="opacity-80">Search by name and/or location</small>
                    <input
                        type="text"
                        value={searchTerm}
                        onChange={(e) => setSearchTerm(e.target.value)}
                        className="mt-3 w-full rounded-full bg-blue-800/50 px-4 py-2 text-sm text-white focus:outline-none"
                        placeholder="Search"
                        aria-label="Search users by name or location"
                    />
                </div>

                {/* 用户列表区域 */}
                <ul className="max-h-96 overflow-y-auto">
                    {/* 加载状态 */}
                    {isLoading && (
                        <li className="p-6 text-center">
                            <h3>Loading...</h3>
                        </li>
                    )}

                    {/* 过滤后的用户列表 */}
                    {!isLoading &&
                        filteredUsers.map((user) => (
                            <li
                                key={user.login.uuid}
                                className="flex items-center border-b border-gray-100 p-4">
                                <img
                                    src={user.picture.large}
                                    alt={`${user.name.first} ${user.name.last}`}
                                    className="h-12 w-12 rounded-full object-cover"
                                    loading="lazy"
                                />
                                <div className="ml-3">
                                    <h4 className="font-medium text-gray-900">
                                        {user.name.first} {user.name.last}
                                    </h4>
                                    <p className="text-xs text-gray-500">
                                        {user.location.city}, {user.location.country}
                                    </p>
                                </div>
                            </li>
                        ))}

                    {/* 无结果状态 */}
                    {!isLoading && filteredUsers.length === 0 && (
                        <li className="p-6 text-center text-gray-500">No users found</li>
                    )}
                </ul>
            </div>
            <div className="fixed right-20 bottom-5 z-100 text-2xl text-red-500">
                CSDN@Hao_Harrision
            </div>
        </div>
    )
}

export default LiveUserFilter

🔄 关键差异总结

功能 Vue 3 React + TS
响应式数据 ref() useState()
计算属性 computed() useMemo()
生命周期 onMounted useEffect(..., [])
双向绑定 v-model value + onChange
列表渲染 v-for .map()
条件渲染 v-if {condition && element}
类型安全 interface User

🔁 转换说明

1. 状态管理:ref → useState

Vue React
const users = ref([]) const [users, setUsers] = useState<User[]>([])
const searchTerm = ref('') const [searchTerm, setSearchTerm] = useState('')
const isLoading = ref(true) const [isLoading, setIsLoading] = useState(true)

✅ 使用 TypeScript 接口 User 提供完整类型安全。


2. 生命周期:onMounted → useEffect(..., [])

useEffect(() => {
  fetchUsers();
}, []);

✅ 确保只在组件挂载时请求一次数据。


3. 计算属性:computed → useMemo

  • Vue 的 filteredUsers 是响应式计算属性;
  • React 中使用 useMemo 缓存过滤结果,避免每次渲染都重新计算:
const filteredUsers = useMemo(() => {
  // ...过滤逻辑
}, [users, searchTerm]);

✅ 依赖项 [users, searchTerm] 确保仅在数据或搜索词变化时重新计算。


4. 事件处理:v-model + @input → value + onChange

<input
  value={searchTerm}
  onChange={(e) => setSearchTerm(e.target.value)}
/>

✅ 完全受控组件,实时同步输入值。


5. 条件渲染:v-if → {condition && (...)} 或三元

Vue React
<li v-if="isLoading"> {isLoading && <li>...}
<li v-for="user in filteredUsers"> {filteredUsers.map(...)}
<li v-if="!isLoading && filteredUsers.length === 0"> {!isLoading && filteredUsers.length === 0 && <li>...}

✅ 逻辑清晰,符合 React 渲染规范。


6. 类型定义:基于 API 响应结构

randomuser.me 返回的数据中提取关键字段,定义 User 接口:

interface User {
  login: { uuid: string };
  name: { first: string; last: string };
  location: { city: string; country: string };
  picture: { large: string };
}

✅ 避免 any,提升开发体验和安全性。


7. 无障碍与性能优化

  • 添加 aria-label 提升可访问性;
  • 图片添加 loading="lazy" 优化性能;
  • 输入框使用 type="text" 明确语义。

8. 错误处理

保留 try/catch,并在 finally 中关闭加载状态,确保 UI 状态正确。

✅ 注意事项

  1. 不要忘记依赖数组useMemo 和 useEffect 必须正确声明依赖;
  2. key 唯一性:使用 user.login.uuid 作为 key 是安全的(来自 API);
  3. 空搜索优化searchTerm.trim() 避免空格触发无效过滤;
  4. 网络错误处理:已包含基础错误日志,生产环境可扩展 toast 提示。

💡 可选增强

  • 添加“重新加载”按钮;
  • 支持防抖搜索(如 useDebounce);
  • 分页加载更多用户。

🎨 TailwindCSS 样式重点讲解

🎯 TailwindCSS 样式说明
类名 作用
flex 启用 Flexbox 布局
min-h-screen 设置最小高度为视口高度,确保内容居中
items-center / justify-center 在主轴和交叉轴上居中对齐
overflow-hidden 隐藏溢出内容
bg-gray-100 设置浅灰色背景
font-sans 使用无衬线字体
w-72 设置固定宽度(18rem)
rounded-lg 添加中等圆角
bg-white 白色背景
shadow-md 添加中等阴影,提升立体感
bg-blue-600 搜索区域深蓝色背景
p-6 内边距
text-white 白色文字
mb-1 下边距
text-xl / text-sm 字体大小
font-bold / font-medium 字体粗细
opacity-80 降低不透明度
mt-3 上边距
rounded-full 完全圆形(圆角)
bg-blue-800/50 深蓝色背景,50% 透明度(使用 Tailwind CSS 新的透明度语法)
px-4 py-2 水平/垂直内边距
focus:outline-none 移除聚焦轮廓
max-h-96 设置最大高度(24rem),创建滚动区域
overflow-y-auto Y轴自动出现滚动条
border-b border-gray-100 底部浅灰色边框,分隔列表项
p-4 列表项内边距
flex items-center 水平 Flex 布局,垂直居中
h-12 w-12 头像固定大小
rounded-full 头像圆形
object-cover 图片填充容器并保持比例
ml-3 左边距
text-gray-900 / text-gray-500 文字颜色
text-xs 小号文字

🦌 路由组件 + 常量定义

router/index.tsx 中 children数组中添加子路由

{
    path: '/',
    element: <App />,
    children: [
       ...
         {
                path: '/LiveUserFilter',
                lazy: () =>
                    import('@/projects/LiveUserFilter').then((mod) => ({
                        Component: mod.default,
                    })),
            },
    ],
 },
constants/index.tsx 添加组件预览常量
import demo43Img from '@/assets/pic-demo/demo-43.png'
省略部分....
export const projectList: ProjectItem[] = [
    省略部分....
      {
        id: 43,
        title: 'Live User Filter',
        image: demo43Img,
        link: 'LiveUserFilter',
    },
]

🚀 小结

通过这篇文章,我们使用 React19 和 TailwindCSS 构建了一个功能完整、界面美观的实时用户搜索过滤组件,来实现高效的过滤逻辑, 快速搭建了现代化的 UI。

想要让你的用户过滤器更加强大?试试这些扩展功能:

✅ 防抖搜索:如果数据量巨大或 API 有调用限制,可以为搜索输入添加防抖(debounce)功能,避免过于频繁的计算或 API 调用。
✅ 高级筛选:除了搜索框,添加下拉菜单或复选框进行更精确的筛选(如按国家、性别等)。
✅ 分页:当用户数据量非常大时,实现分页功能。
✅ 缓存机制:缓存已获取的数据,避免重复请求。
✅ 错误处理 UI:除了 console.error,在界面上显示友好的错误提示。
✅ 键盘导航:支持使用键盘上下键在用户列表中导航。

📅 明日预告: 我们将完成FeedbackUiDesign组件,实现了一个好玩儿的好评反馈组件。🚀🚀

感谢阅读,欢迎点赞、收藏和分享 😊


原文链接:https://blog.csdn.net/qq_44808710/article/details/149782464

每天造一个轮子,码力暴涨不是梦!🚀

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐