今天在旅游项目中完成了超酷的搜索功能,实现了类似Google Suggest的实时搜索建议效果,是用mockjs来模拟的!整个过程涉及了React状态管理、防抖优化、Zustand状态库等关键技术点,让本犬来详细记录下这个超实用的开发过程,当然还有待完善,大佬勿喷,以下时具体效果。
一、搜索功能核心思路
整个搜索功能分为三大模块:
- 搜索框组件:负责用户输入和交互
- 搜索页面:整合搜索框和结果展示
- 状态管理:通过Zustand管理全局搜索状态
- 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>
)
}
这个组件有几个关键设计点:
-
混合状态管理:同时使用useState和useRef
useState管理输入值,触发组件重新渲染useRef非受控组件,直接操作DOM,用于清空输入框和聚焦操作
-
防抖优化:避免频繁触发搜索
const handleQueryDebounce = useMemo(() => { return debounce(handleQuery, 700) }, [])这里使用useMemo缓存防抖函数,避免每次渲染都重新创建,
handleQuery父组件传过来的函数,通过其来实现子传父的通信,handleQuery内部是调用了zustand创建的方法,来请求我们使用mockjs模拟的搜索建议地数据 -
智能清空按钮:只在有输入时显示
const displayStyle = query ? { display: 'block' } : { display: 'none' }可以看到以下效果:
防抖函数的实现也很有讲究:
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>
)
}
这里有几个关键设计:
-
条件渲染:搜索结果只在有查询时显示
const suggestListStyle = { display: query === '' ? 'none' : 'block' }
当用户清空输入框内容就不会显示,极大地丰富了用户的体验,毕竟前端用户体验为王
-
热门搜索组件:展示常用搜索项
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> ) })
-
性能优化:使用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的优势在于:
- 简洁的API设计
- 内置异步操作支持
- 轻松集成本地存储
- 自动处理状态更新
getSuggestList和getHotList()是我们封装的请求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的强大功能:
- 随机生成中文文本
- 动态响应查询参数
- 控制超时时间模拟网络延迟
- 随机数据量模拟真实场景
当我们写完之后,可以用apifox来测试,是否能成功地返回数据,边写边测试,方便我们debug,要不然一堆错误堆在一起属实是不好处理:
六、性能优化实战经验
项目中应用了多种性能优化技巧:
-
防抖技术:减少API请求次数
-
条件渲染:避免不必要的内容渲染
<div className={styles.list} style={suggestListStyle}> -
组件记忆:使用memo避免重复渲染
export default memo(SearchBox) -
函数缓存:useMemo缓存防抖函数
const handleQueryDebounce = useMemo(() => { return debounce(handleQuery, 700) }, [])
性能优化是前端面试中的重中之重,在项目中实战用到会加深自己对性能优化的理解,同时在面试官眼中也是加分项,所以大家在项目中可以切实地用到一些关于性能优化的技术
七、项目收获与思考
通过这个搜索功能的实现,我有几点深刻体会:
-
状态管理选择:Zustand比Redux更轻量,适合中小项目
-
防抖的重要性:搜索功能必须防抖,否则API会被刷爆
-
组件设计哲学:
- 展示组件与容器组件分离
- 单一职责原则
- 受控与非受控组件的结合使用
-
Mock数据的价值:前端独立开发,不阻塞后端进度
-
用户体验细节:
- 清空按钮的显隐逻辑
- 输入框自动聚焦
- 热门搜索的默认展示
总结
今天完成的旅游项目搜索功能,涵盖了React核心概念、状态管理、性能优化等关键技术点。从输入框的防抖处理到搜索结果的实时展示,从Zustand状态管理到Mock数据模拟,每个环节都让我对现代前端开发有了更深入的理解。
特别是当看到自己实现的搜索建议流畅地展现出来时,那种成就感真的爆棚!建议每个前端初学者都尝试实现这样的搜索功能,它能全面锻炼你的React技能。
最后项目的全部代码已上传至 Github