rn_for_openharmony商城项目app实战-搜索结果实现
本文介绍了搜索结果页的实现方案,主要包含以下内容: 通过关键词在前端筛选商品列表,实现模糊搜索功能 处理三种页面状态:加载中、无结果、有结果 使用FlatList展示两列商品布局 优化搜索体验的思路:分词、拼音、权重和高亮 讨论了分页加载和排序功能的实现方式 虽然前端搜索在数据量大时性能不足,但对于学习项目来说已经足够。文章还强调了实际项目中应使用后端搜索的重要性。
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_buy
写在前面

上一篇写了搜索页面,用户输入关键词后会跳转到搜索结果页。这篇就来实现搜索结果页,把匹配的商品展示出来。
搜索结果页的逻辑不复杂:拿到关键词,去商品列表里筛选,把匹配的商品显示出来。但有几个细节要处理好:加载状态、空结果、搜索匹配规则。
说实话,真正的搜索功能应该由后端来做,前端只负责展示。但我们用的 FakeStoreAPI 没有搜索接口,只能在前端做模糊匹配。虽然不够专业,但对于学习来说够用了。
引入依赖
import React, {useEffect, useState} from 'react';
import {View, Text, FlatList, StyleSheet, ActivityIndicator} from 'react-native';
import {Product} from '../types';
import {storeApi} from '../api/store';
import {ProductCard} from '../components/ProductCard';
import {useApp} from '../store/AppContext';
import {Header} from '../components/Header';
import {Empty} from '../components/Empty';
引入的东西不少。FlatList 用来渲染商品列表,ActivityIndicator 是加载动画,Empty 是空状态组件。
组件主体
export const SearchResultScreen = () => {
const {screenParams, navigate} = useApp();
const keyword = screenParams?.keyword || '';
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState(true);
从 screenParams 里拿到搜索关键词,这是上一个页面传过来的。如果没传就用空字符串兜底。
两个状态:products 存搜索结果,loading 控制加载动画。
为什么 loading 初始值是 true?
因为页面一打开就要去搜索,这时候应该显示加载动画。如果初始值是 false,页面会先闪一下空状态,然后才显示加载动画,体验不好。
触发搜索
useEffect(() => {
searchProducts();
}, [keyword]);
用 useEffect 监听 keyword 的变化,关键词变了就重新搜索。
实际上这个页面的 keyword 不会变(除非用户返回搜索页重新搜索再进来),所以这个 effect 基本上就是在组件挂载时执行一次。
依赖数组里要不要放 keyword?
严格来说应该放。虽然当前场景 keyword 不会变,但如果以后需求变了(比如在结果页直接修改关键词重新搜索),不放 keyword 就会出 bug。养成好习惯,用到的变量都放进去。
搜索逻辑
const searchProducts = async () => {
setLoading(true);
try {
const all = await storeApi.getProducts();
先把 loading 设为 true 显示加载动画,然后请求所有商品数据。
因为 FakeStoreAPI 没有搜索接口,我们只能先拿到全部商品,再在前端筛选。这种做法在商品数量少的时候没问题,商品多了就不行了,每次搜索都要下载全部数据,太浪费流量。
const filtered = all.filter((p: Product) =>
p.title.toLowerCase().includes(keyword.toLowerCase()) ||
p.category.toLowerCase().includes(keyword.toLowerCase())
);
setProducts(filtered);
筛选逻辑:商品标题或分类名包含关键词就算匹配。
toLowerCase() 把字符串转成小写,这样搜索就不区分大小写了。用户搜 “PHONE” 和 “phone” 能得到一样的结果。
includes vs indexOf
includes返回布尔值,indexOf返回位置(找不到返回 -1)。这里只需要知道"有没有",不需要知道"在哪里",用includes更语义化。
} catch (error) {
console.error('Search failed:', error);
} finally {
setLoading(false);
}
};
错误处理和关闭加载动画。finally 保证不管成功还是失败,loading 都会被设为 false。
页面渲染
return (
<View style={styles.container}>
<Header title={`搜索: ${keyword}`} />
头部显示搜索关键词,让用户知道当前搜的是什么。Header 组件自带返回按钮,点击可以回到搜索页。
三种状态的处理
搜索结果页有三种状态:加载中、无结果、有结果。用条件渲染来处理:
{loading ? (
<ActivityIndicator size="large" color="#3498db" style={styles.loader} />
) : products.length === 0 ? (
<Empty icon="🔍" title="未找到相关商品" subtitle="换个关键词试试吧" />
) : (
加载中显示菊花图,搜索完没结果显示空状态组件。
嵌套三元表达式
这种写法有人觉得难读,但我觉得还好,逻辑很清晰:先判断 loading,再判断有没有结果。如果嵌套更多层就该拆成函数了,两层还能接受。
空状态组件给了友好的提示:“未找到相关商品”,还有个副标题"换个关键词试试吧",引导用户下一步操作。
商品列表
<FlatList
data={products}
numColumns={2}
keyExtractor={item => item.id.toString()}
contentContainerStyle={styles.list}
columnWrapperStyle={styles.row}
renderItem={({item}) => (
<ProductCard product={item} onPress={() => navigate('productDetail', {product: item})} />
)}
/>
)}
</View>
);
};
有结果时用 FlatList 渲染商品列表。
几个关键属性:
numColumns={2}: 两列布局,和首页、分类页保持一致keyExtractor: 用商品 ID 作为 keycolumnWrapperStyle: 控制每行的样式,让两列商品左右分开renderItem: 渲染每个商品,用ProductCard组件
点击商品跳转到详情页,把商品对象传过去。
FlatList vs ScrollView + map
搜索结果可能有很多商品,用
FlatList比ScrollView + map性能好。FlatList只渲染屏幕上可见的项目,滑动时动态加载,内存占用小。
样式
const styles = StyleSheet.create({
container: {flex: 1, backgroundColor: '#f5f5f5'},
loader: {flex: 1, justifyContent: 'center'},
list: {padding: 16},
row: {justifyContent: 'space-between'},
});
样式很简单。loader 的 flex: 1 让加载动画垂直居中。row 的 justifyContent: 'space-between' 让每行的两个商品左右分开。
搜索匹配的优化思路
当前的搜索逻辑比较简单,只是看标题和分类是否包含关键词。实际项目中可以做得更智能:
1. 分词搜索
用户搜"红色连衣裙",可以拆成"红色"和"连衣裙"两个词,只要商品包含其中一个词就算匹配。这样搜索结果会更多。
2. 拼音搜索
用户输入拼音"shouji",也能搜到"手机"。需要引入拼音转换库。
3. 搜索权重
标题匹配的权重高于分类匹配,完全匹配的权重高于部分匹配。根据权重排序,最相关的商品排在前面。
4. 搜索高亮
在搜索结果中,把匹配的关键词高亮显示。比如搜"手机",商品标题里的"手机"两个字变成红色。
这些优化要不要做?
看需求。如果是学习项目,当前的实现够用了。如果是正式产品,这些优化能显著提升搜索体验。不过最好的方案还是让后端来做搜索,后端可以用 Elasticsearch 这类专业的搜索引擎,效果比前端筛选好得多。
加载更多
当前实现是一次性加载所有结果。如果结果很多,可以做分页加载:
<FlatList
// ...其他属性
onEndReached={loadMore}
onEndReachedThreshold={0.5}
ListFooterComponent={hasMore ? <ActivityIndicator /> : null}
/>
onEndReached 在滑动到底部时触发,可以在这里加载下一页数据。onEndReachedThreshold 控制触发的时机,0.5 表示距离底部还有 50% 时就触发。
不过因为我们是前端筛选,数据已经全部拿到了,分页加载意义不大。如果后端支持分页搜索接口,这个功能就很有用了。
排序功能
搜索结果页通常还有排序功能:按价格排序、按销量排序、按评分排序。
实现思路是在页面顶部加一排排序按钮,点击后对 products 数组重新排序:
const sortByPrice = (asc: boolean) => {
const sorted = [...products].sort((a, b) => asc ? a.price - b.price : b.price - a.price);
setProducts(sorted);
};
注意要用 [...products] 创建新数组再排序,不能直接 products.sort(),因为 React 状态是不可变的,直接修改不会触发重新渲染。
写在最后
搜索结果页的核心就是:拿关键词、筛选商品、展示结果。三种状态(加载中、无结果、有结果)都要处理好,给用户清晰的反馈。
前端搜索只是权宜之计,正式项目应该用后端搜索。后端可以做更复杂的匹配逻辑、支持分页、返回搜索建议,前端只负责展示就行。
下一篇写收藏功能,包括收藏列表页面。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)