想要用RN写个小说APP第五天【通过自定义规则进行搜索与展示🔎】

2,893 阅读3分钟

我正在参加「掘金·启航计划」

在某天某时,我立下了这个奇怪的flag,关于我和他的爱恨情仇,请看我的第一篇文章
想要用RN写个小说APP第一天
当前已经实现的功能有:

  • 爬取页面内容✅
  • 状态管理以及数据持久化✅
  • 小说内容主题自定义(字体/颜色/...)
  • 爬取后如何进行文本分页✅
  • 分页后如何在用户阅读时,无感加载上/下一章✅
    项目地址,star看我后续更新🌠🌠🌠

今日内容

今天做的是小说搜索和详情的部分,使用了MMKV将搜索记录进行存储,使用new Function对编写的js代码进行还原得到搜索结果。

效果展示

74a2e2065d806c3409fb325442cdd160 00_00_00-00_00_30.gif

思路

因为搜索页面和搜索结果页,都有着同样的搜索框组件。所以我并没有将其分为两个页面分开加入Stack.Navigator(之前第一章写过),在用户键盘的确认回调中,控制历史记录隐藏,搜索结果展示。

获取搜索记录并展示

clickText中,需要将搜索结果设置为展示,并更新MMKV中的记录,在更新之前,对记录进行遍历,如果之前进行过一模一样的搜索记录时,将其移动到数组的最前面,保持规律。

// 代码并不完整
// 取到MMKV储存的值,第一次使用时,初始化成[]
const History = ()=>{
    const [history = [], setHistory] = useMMKVObject<string[]>('user.history');
    const clickText = (text: string) => {
    console.log(text);
    setSearxhText(text);
    setIsShowHistory(false);
    setHistory(
      [text, ...history].filter((txt, index) => txt !== text || index === 0),
    );
  };
    return (
    <View style={Style.history_wrap}>
          <Text style={Style.history_text}>
            搜索历史:{' '}
            <Text
              onPress={() => setHistory([])}
              style={[Style.history_text, {color: Style.history_list.color}]}>
              清空
            </Text>
          </Text>
          <View style={Style.history_content}>
            {history.map((text, index) => (
              <Text
                key={index}
                onPress={() => {
                  clickText(text);
                }}
                style={Style.history_list}>
                {text}
              </Text>
            ))}
          </View>
        </View>
    )
}

获取搜索结果并展示

将搜索的文本传递给本组件,交由封装的自定义hook useSearchNovel去获取结果内容。结果需要遵循一定的格式。返回结果数组,这里使用的是RN自带组件FlatList进行渲染。

const SearchDetail: React.FC<{text: string}> = ({text}) => {
  console.log('SearchDetail', text);
  const DATA = useSearchNovel(text);
  const navigation = useNavigation();
  const renderItem = useCallback(({item: {title, url, cover, author}}) => {
    return (
      <TouchableOpacity
        style={style.item}
        onPress={() => {
          navigation.navigate('NovelDetail', {
            info: {title, url, cover, author},
          });
        }}>
        <Image
          style={style.cover}
          source={{
            uri: cover,
          }}
        />
        <View style={style.item_info}>
          <Text style={style.title}>{title}</Text>
          <Text style={style.author}>{author}</Text>
        </View>
      </TouchableOpacity>
    );
  }, []);
  return (
    <View style={style.warp}>
      <FlatList
        refreshing={DATA.length === 0}
        data={DATA}
        showsVerticalScrollIndicator={false}
        renderItem={renderItem}
        keyExtractor={item => item.url}
      />
    </View>
  );
};

自定义hook

现在来看看自定义useSearchNovel中如何得到我们的搜索结果:

const useSearchNovel = (text: string): any[] => {
  const [data, setData] = useState([]);
  useLayoutEffect(() => {
    getListFromSource(text).then(res => {
      setData(res);
    });
  }, [text]);
  return data;
};
export default useSearchNovel;

很简单,主要逻辑其实是在getListFromSource中进行处理,需要从url中使用正则表达式替换成我们的关键词,这块的功能主要是得到请求下来的HTML文本,再继续交给getBookListWithRule进行解析:

export const getListFromSource = async (text: string) => {
  const info = source[0];
  const url = '/plus/search.php?kwtype=0&searchtype=搜索&q={{key}}'.replace(
    /\{\{(.+?)\}\}/g,
    str => {
      return text;
    },
  );
  const data = await fetch(info.bookSourceUrl + url).then(function (response) {
    return response.text();
  });
  const $ = cheerio.load(data);
  const res = getBookListWithRule($, info.bookSourceUrl);
  return res;
};

getBookListWithRule

我暂定这一块是自定义实现的,传入cheerio生成的对象,对象,对象可以使用cheerio的所有方法,rule是这样的一个字符串:

const rule = `return $('.ul_b_list li')
  .map(function (index) {
    return {
      title: $('h2 a', this).text(),
      author: $('.state', this)
        .text()
        .match(/作者:(\\S*)/)[1],
      cover: url+$('img', this).attr('src'),
      url: url+$('h2 a', this).attr('href'),
    };
  })
  .get();`
const getBookListWithRule = ($, url) => {
    const ruleFn = new Function('$','url',rule);
    return ruleFn($, url);
};

拿到的是结果是这样的一个数组:

[{
    title:string,
    author:string,
    url:string,
    cover:string
}]

0cbe9400d920819c87925ecdc6721f7.jpg

小说详情页

63d79b94b0ec50ccd9b94de23731d88.jpg

大致流程一样,主要还是通过自定义规则获取详情:
const getBookDetailWithRule = ($, url) => {
  const node = $('.Left');
  const data = {
    title: $('.title  h1', node).text(),
    author: $('.info  li:nth-child(1) a', node).text(),
    totalWords: $('#cms_ready_1', node).text(),
    info: $('.words > p:nth-child(3)', node).text().substring(7),
    cover: $('.pic img', node).attr('src'),
    newer: $('.words a', node).text(),
    tags: [$('.info .lb a', node).text()],
  };
  return data;
};

结束

项目地址,star看我后续更新🌠🌠🌠