引言
效果图

在这个教程中,我们将介绍如何使用 React、Zustand 状态管理库和 Mock.js 来构建一个包含热门推荐和关键词搜索功能的搜索页面。我们将逐步讲解每一个组件的功能以及它们之间的交互。
完整代码
1. 创建 Zustand Store 实现全局状态管理
store/useSearchStore.js
在 src/store/useSearchStore.js
文件中创建一个 Zustand Store 来管理搜索建议列表 (suggestList
) 和热门搜索列表 (hotList
) 的状态。
js
// search 全局状态共享
import {
create
} from 'Zustand'
import {
getSuggestList,
getHotList
} from '@/api/search'
const useSearchStore = create((set,get) => {
// get 读操作
const searchHistory = JSON.parse(localStorage.getItem('searchHistory')) || []
return {
searchHistory,
suggestList: [], // 返回list
hotList:[], // 热门搜索
setSuggestList: async(keyword)=>{
const res = await getSuggestList(keyword);
console.log(res);
set({
suggestList: res.data
})
},
setHotList: async () => {
const res = await getHotList();
console.log(res);
set({
hotList: res.data
})
}
}
})
export default useSearchStore
2. API 请求封装与模拟数据配置
在 src/api/search.js
中定义获取搜索建议和热门搜索列表的方法:
api/search.js
js
// search模块
import axios from './config'
export const getSuggestList = (keyword) => {
return axios.get(`/search?keyword=${keyword}`);
}
export const getHotList = async () => {
return axios.get('/hotList');
}
3. 模拟后端接口响应
为了便于本地开发,我们可以使用 Mock.js 来模拟后端接口响应。在 src/mock/index.js
中添加如下代码:
mock/data.js
js
import Mock from 'mockjs';
Mock.mock('/api/search', 'get', (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'
});
list.push(`${randomData.title} ${keyword}`);
}
return {
code: 0,
data: list
};
});
Mock.mock('/api/hotlist', 'get', {
code: 0,
data: [
{ id: '101', city: "北京" },
{ id: '102', city: "上海" },
{ id: '103', city: "福州" }
]
});
不要忘记配置 Axios 基础 URL 和拦截器:
api/config.js
js
import axios from 'axios'
axios.defaults.baseURL = 'http://localhost:5173/api'
axios.interceptors.request.use((config) => {
return config
})
// 响应拦截
axios.interceptors.response.use((data) => {
return data.data
})
export default axios
4. 构建 Search
组件
接下来,在 src/components/Search.js
中创建 Search
组件,它将负责渲染整个搜索界面,包括搜索框、热门推荐和搜索建议列表。
Pages/Search/index.jsx
jsx
import SearchBox from '@/components/SearchBox'
import useTitle from '@/hooks/useTitle'
import useSearchStore from '@/store/useSearchStore'
import styles from './search.module.css'
import { useState, useEffect, memo } from 'react';
// 性能优化
const HotListItems = memo((props) => {
console.log('___________',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>
)
})
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.trim() == '' ? 'none' : 'block'
}
useTitle('搜索')
return (
<div className={styles.container}>
<div className={styles.wrapper}>
<SearchBox handleQuery={handleQuery} />
{/* 组件化,维护性更好 */}
<HotListItems hotList={hotList} />
<div className={styles.suggestList} style={suggestListStyle}>
{
suggestList.map( item => (
<div key={item} className={styles.suggestItem}>
{item}
</div>
))
}
</div>
</div>
</div>
)
}
export default Search
5. 构建 SearchBox
组件
在 src/components/SearchBox.js
中创建 SearchBox
组件,它是一个输入框,允许用户输入搜索关键词,并触发搜索操作。
components/SearchBox/index.jsx
jsx
import { memo, useEffect, useRef, useState, useMemo } from 'react';
import { ArrowLeft, Close } from '@react-vant/icons';
import styles from './search.module.css';
import { debounce } from '@/utils';
const SearchBox = ({ handleQuery }) => {
const [query, setQuery] = useState('');
const queryRef = useRef(null);
const handleChange = (e) => {
if (e.target.value.trim() === '') {
setQuery('');
return;
}
setQuery(e.target.value.trim());
};
const clearQuery = () => {
setQuery('');
queryRef.current.value = '';
queryRef.current.focus();
};
const handleQueryDebounce = useMemo(() => debounce(handleQuery, 300), []);
const displayStyle = query.trim() ? { display: 'block' } : { display: 'none' };
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>
);
};
export default memo(SearchBox);
关键词搜索整体流程梳理
从用户输入到数据展示
首先,用户在
SearchBox
组件中输入框中输入关键词(如:"北京"),组件使用useEffect
和防抖debounce
,调用通过props
传入的handleQuery
函数,作为防抖函数的参数传入,在用户停止输入约300ms后执行,将"北京"作为参数传递。
子组件searchBox.jsx
jsx
<input
type="text"
className={styles.ipt}
placeholder="搜索旅游相关"
ref={queryRef}
onChange={handleChange}
/>
jsx
const handleQueryDebounce = useMemo(() => {
return debounce(handleQuery, 300)
}, [])
// 非受控组件
useEffect(() => {
handleQueryDebounce(query);
}, [query])
父组件的
handleQuery("北京")
被调用,更新输入框query
的状态,随后调用 Zustand Store 中的setSuggestList("北京")
方法。 父组件search.jsx
jsx
const handleQuery = (query) => {
setQuery(query);
if(!query.trim()) return
setSuggestList(query);
}
/*....其他*/
<SearchBox handleQuery={handleQuery} />
setSuggestList
方法是一个异步函数,接收一个参数,即关键词"北京",在它内部会调用getSuggestList("北京")
。 而getSuggestList
方法会进行API请求 ,函数执行axios.get('/search?keyword=北京')
。
状态管理:useSearchStore.js
js
setSuggestList: async(keyword)=>{
const res = await getSuggestList(keyword);
console.log(res);
set({
suggestList: res.data
})
}
api请求
js
export const getSuggestList = (keyword) => {
return axios.get(`/search?keyword=${keyword}`);
}
找到匹配的配置 (url) 后,
Mock.js
会执行该配置项中的response
函数,并将请求信息(req
)和一个响应对象(res
,虽然这里没用到)作为参数传递进去。在
response
函数内部,const keyword = req.query.keyword;
这行代码就是用来从拦截到的 HTTP 请求中提取查询参数keyword
的值。
mock.js
js
{
url: '/api/search',
method: 'get',
timeout: 1000,
response:(req, res)=>{
// ?keyword=释小龙
const keyword = req.query.keyword;
let num = Math.floor(Math.random() * 10); //生成0-9的随机数
let list = [];
for(let i = 0; i < num; i++) {
const randomData = Mock.mock({
title: '@ctitle'
}) //Mock.mock 返回一个对象
console.log(randomData)
list.push(`${randomData.title} ${keyword}`)
}
return {
code: 0,
data: list
}
}
}
使用
Mock.js
生成随机数据,并将提取到的keyword
值("北京")拼接到生成的标题后面,形成建议列表。最后将生成的列表包装成
{ code: 0, data: list }
的格式返回给axios
。而
axios
收到Mock.js
返回的模拟数据,getSuggestList
函数的 Promise 被 resolve。
Search
组件因为订阅了suggestList
状态,所以重新渲染,将新的建议列表展示在页面上。
总结
通过这个教程,我们学习了如何使用 React 结合 Zustand 状态管理库来实现一个简单的搜索页面,该页面支持热门推荐和关键词搜索功能。同时我们也了解了如何利用 Mock.js 来为前端开发提供稳定的模拟数据环境。希望这篇教程对你有所帮助!