今天搞定了旅游搜索神器!从输入到建议,手把手带你实现Google级搜索体验

81 阅读6分钟

今天在旅游项目中完成了超酷的搜索功能,实现了类似Google Suggest的实时搜索建议效果,是用mockjs来模拟的!整个过程涉及了React状态管理、防抖优化、Zustand状态库等关键技术点,让本犬来详细记录下这个超实用的开发过程,当然还有待完善,大佬勿喷,以下时具体效果。

4.gif

一、搜索功能核心思路

整个搜索功能分为三大模块:

  1. 搜索框组件:负责用户输入和交互
  2. 搜索页面:整合搜索框和结果展示
  3. 状态管理:通过Zustand管理全局搜索状态
  4. mock模拟后端接口:通过mock来模拟后端的接口,实现自给自足

这种架构设计让我深刻体会到React的单向数据流思想 - 数据从父组件流向子组件,事件从子组件流向父组件。

二、搜索框组件的精妙设计

先看搜索框的核心代码:

const SearchBox = (props) => {
    const [query, setQuery] = useState('')
    const { handleQuery } = props
    const queryRef = useRef(null)
    
    const handleChange = (e) => {
        let val = e.currentTarget.value
        setQuery(val)
    }
    
    const clearQuery = () => {
        setQuery('')
        queryRef.current.value = ''
        queryRef.current.focus()
    }
    
    const handleQueryDebounce = useMemo(() => {
        return debounce(handleQuery, 700)
    }, [])
    
    useEffect(() => {
        handleQueryDebounce(query)
    }, [query])
    
    return (
        <div className={styles.wrapper}>
            <ArrowLeft onClick={() => history.go(-1)} />
            <input
                type="text"
                className={styles.ipt}
                placeholder='搜索旅游相关'
                ref={queryRef}
                onChange={handleChange}
            />
            <Close onClick={clearQuery} style={displayStyle} />
        </div>
    )
}

这个组件有几个关键设计点:

  1. 混合状态管理:同时使用useState和useRef

    • useState管理输入值,触发组件重新渲染
    • useRef非受控组件,直接操作DOM,用于清空输入框和聚焦操作
  2. 防抖优化:避免频繁触发搜索

    const handleQueryDebounce = useMemo(() => {
        return debounce(handleQuery, 700)
    }, [])
    

    这里使用useMemo缓存防抖函数,避免每次渲染都重新创建,handleQuery父组件传过来的函数,通过其来实现子传父的通信,handleQuery内部是调用了zustand创建的方法,来请求我们使用mockjs模拟的搜索建议地数据

  3. 智能清空按钮:只在有输入时显示

    const displayStyle = query ? { display: 'block' } : { display: 'none' }
    

    可以看到以下效果:

5.gif

防抖函数的实现也很有讲究:

export function debounce(fun, delay) {
    return function (args) {
        let that = this
        let _args = args
        clearTimeout(fun.id)
        fun.id = setTimeout(function () {
            fun.call(that, _args)
        }, delay)
    }
}

这个防抖函数通过fun.id保存定时器ID,确保同一函数多次调用时只执行最后一次。我们在之前文章中就提到过前端性能优化防抖的写法,大家感兴趣的可以看看本犬的这篇文章 闭包与防抖:代码里的“拖延症患者”和“隐私守护者”,防抖也是面试中的常客了,这次直接在实战项目中使用也是项目的亮点了

三、搜索页面的完整架构

搜索页面整合了所有功能模块:

const Search = () => {
    const [query, setQuery] = useState('')
    const { hotList, setHotList, suggestList, setSuggestList } = useSearchStore()

    useEffect(() => {
        setHotList()
    }, [])

    const handleQuery = (query) => {
        setQuery(query)
        if (!query.trim()) return
        setSuggestList(query)
    }

    const suggestListStyle = {
        display: query === '' ? 'none' : 'block'
    }
    
    return (
        <div className={styles.container}>
            <div className={styles.wrapper}>
                <SearchBox handleQuery={handleQuery} />
                <HotListItems hotList={hotList} />
                <div className={styles.list} style={suggestListStyle}>
                    {suggestList.map(item => (
                        <div key={item} className={styles.item}>
                            {item}
                        </div>
                    ))}
                </div>
            </div>
        </div>
    )
}

这里有几个关键设计:

  1. 条件渲染:搜索结果只在有查询时显示

    const suggestListStyle = {
        display: query === '' ? 'none' : 'block'
    }
    

当用户清空输入框内容就不会显示,极大地丰富了用户的体验,毕竟前端用户体验为王

  1. 热门搜索组件:展示常用搜索项

    const HotListItems = memo((props) => {
        const { hotList } = props
        return (
            <div className={styles.hot}>
                <h1>热门搜索</h1>
                {hotList.map(item => (
                    <div key={item.id} className={styles.item}>
                        {item.city}
                    </div>
                ))}
            </div>
        )
    })
    

image.png

  1. 性能优化:使用memo避免不必要的重新渲染

    export default memo(SearchBox)
    

这是我们在之前文章写到react性能优化的一个api,虽然简单,但是非常实用,只有当父组件所传的值改变时,我们的子组件才会实现热更新,减少了重绘重排带来的性能消耗

四、Zustand状态管理实战

使用Zustand管理全局搜索状态是项目的亮点:

const useSearchStore = create((set, get) => {
    const searchHistory = JSON.parse(localStorage.getItem('searchHistory')) || []

    return {
        searchHistory,
        suggestList: [],
        hotList: [],
        setSuggestList: async (keyword) => {
            const res = await getSuggestList(keyword)
            set({
                suggestList: res.data.data.data
            })
        },
        setHotList: async () => {
            const res = await getHotList()
            set({
                hotList: res.data.data
            })
        }
    }
})

Zustand的优势在于:

  1. 简洁的API设计
  2. 内置异步操作支持
  3. 轻松集成本地存储
  4. 自动处理状态更新

getSuggestListgetHotList()是我们封装的请求mock模拟的后端数据的AJAX函数,我们在经过Zustand,封装成集中管理的数据状态和方法

五、Mock数据模拟实战

后端API使用Mock.js模拟:

export default [{
    url: '/api/search',
    method: 'get',
    timeout: 1000,
    response: (req, res) => {
        const keyword = req.query.keyword;
        let num = Math.floor(Math.random() * 10)
        let list = []
        for (let i = 0; i < num; i++) {
            const randomData = Mock.mock({
                title: '@ctitle(3,6)'
            })
            list.push(`${randomData.title}${keyword}`)
        }
        return {
            code: 0,
            data: {
                data: list
            }
        }
    }
},
{
    url: '/api/hotlist',
    method: 'get',
    timeout: 1000,
    response: (req, res) => {
        return {
            code: 0,
            data: [
                { id: '101', city: '北京' },
                { id: '102', city: '上海' },
                { id: '103', city: '福州' }
            ]
        }
    }
}]

Mock.js的强大功能:

  1. 随机生成中文文本
  2. 动态响应查询参数
  3. 控制超时时间模拟网络延迟
  4. 随机数据量模拟真实场景

当我们写完之后,可以用apifox来测试,是否能成功地返回数据,边写边测试,方便我们debug,要不然一堆错误堆在一起属实是不好处理:

image.png

六、性能优化实战经验

项目中应用了多种性能优化技巧:

  1. 防抖技术:减少API请求次数

  2. 条件渲染:避免不必要的内容渲染

    <div className={styles.list} style={suggestListStyle}>
    
  3. 组件记忆:使用memo避免重复渲染

    export default memo(SearchBox)
    
  4. 函数缓存:useMemo缓存防抖函数

    const handleQueryDebounce = useMemo(() => {
        return debounce(handleQuery, 700)
    }, [])
    

性能优化是前端面试中的重中之重,在项目中实战用到会加深自己对性能优化的理解,同时在面试官眼中也是加分项,所以大家在项目中可以切实地用到一些关于性能优化的技术

七、项目收获与思考

通过这个搜索功能的实现,我有几点深刻体会:

  1. 状态管理选择:Zustand比Redux更轻量,适合中小项目

  2. 防抖的重要性:搜索功能必须防抖,否则API会被刷爆

  3. 组件设计哲学

    • 展示组件与容器组件分离
    • 单一职责原则
    • 受控与非受控组件的结合使用
  4. Mock数据的价值:前端独立开发,不阻塞后端进度

  5. 用户体验细节

    • 清空按钮的显隐逻辑
    • 输入框自动聚焦
    • 热门搜索的默认展示

总结

今天完成的旅游项目搜索功能,涵盖了React核心概念、状态管理、性能优化等关键技术点。从输入框的防抖处理到搜索结果的实时展示,从Zustand状态管理到Mock数据模拟,每个环节都让我对现代前端开发有了更深入的理解。

特别是当看到自己实现的搜索建议流畅地展现出来时,那种成就感真的爆棚!建议每个前端初学者都尝试实现这样的搜索功能,它能全面锻炼你的React技能。

最后项目的全部代码已上传至 Github