我正在参加「掘金·启航计划」
在某天某时,我立下了这个奇怪的flag,关于我和他的爱恨情仇,请看我的第一篇文章
想要用RN写个小说APP第一天
当前已经实现的功能有:
- 爬取页面内容
- 状态管理以及数据持久化
- 小说内容主题自定义(字体/颜色/...)
- 爬取后如何进行文本分页
- 分页后如何在用户阅读时,无感加载上/下一章
(相关的bug经历我最近都会写出来🧐🧐🧐)
项目地址,star看我后续更新🌠🌠🌠
今日内容
我在上一篇大致实现完三个Tab栏的布局以及在非相干页面将在下方的Tab进行隐藏。也许是你的话,还会继续将页面的导航流程继续完善下去进行。也就是我们经常将所有页面的子路由定义好页面文件。最后再去实现详细功能。而我就不一样了🕺🕺。我想要立刻实现关于阅读一本小说的相关所有的流程。
抓取内容
首先我们需要的是将小说的内容从网页中抓取下来,也就是爬虫,将页面请求下来我们一般需要使用网络请求Api,类似ajax、fetch等。而RN的官方文档中明确标明了以及内置了fetch。所以这当然也就是第一选择。
SHOW CODE
fetch(url)
.then(function (response) {
console.log(response.text());
return response.text();
})
控制台输出: 作为一个纯真的切图仔,看到这熟悉的HTML文本不得不说是有多么亲切了㊗️㊗️㊗️
解析内容
虽然说吧,上面的HTML文本再让我亲切。当时他不够方便啊🤹
有那么一瞬间,我竟然想像Vue的模板解析那样,通过匹配尖括号将其进行解析。但是吧,emmm我很快就将这个不实际的想法抛走了。老老实实的找到了那个它。
cheerio
note:为服务器特别定制的,快速、灵活、实施的jQuery核心实现.
简单的说就是他可以将我们上面获取到的HTML文本进行解析,并返回一个类似于我们在jquery中使用的$对象,然后你就可以尽情的享受jquery方法带来的快乐。(快乐何止是加倍那么简单🤸🤸)
获取内容标题
喜闻乐见的Dom选择器环节,通过浏览器访问,确定我们要获取的目标选择器(后续替换成自定义书源选择器匹配),上面是标题,下面是内容:
相关代码如下:
const $ = cheerio.load(
html.replace(/<\s*br[^>]*>/gi, '\n').replace(/\n{2,}/g, '\n'),
);
const textContent = $('.box_box')
.text()
.trim()
.split('\n')
.map(_ => '\u3000\u3000' + _.trim())
.join('\n');
title = $('.box_con h1').text();
content = toDBC(textContent);
内容处理
cheerio.load传入我们简单处理后的HTML文本,为什么要处理?获取到的内容有时候会有两个<br>或者多个换行标签,显示出来之后会很丑(对我来说哈哈哈哈)。我们首先通过正则将其全都替换成一个\n。
再通过\n符号对文本分段,再在每一段的开头插入两个全角空格\u3000,全角的空格在显示中与一个汉字同等宽度。视觉上更加舒服(还是对我来说哈哈哈🙌🙌):
const reg =
/[\u3002|\uff1f|\uff01|\uff0c|\u3001|\uff1b|\uff1a|\u201c|\u201d|\u2018|\u2019|\uff08|\uff09|\u300a|\u300b|\u3008|\u3009|\u3010|\u3011|\u300e|\u300f|\u300c|\u300d|\ufe43|\ufe44|\u3014|\u3015|\u2026|\u2014|\uff5e|\ufe4f|\uffe5]/;
function toDBC(str: string): string {
var result = '';
for (var i = 0; i < str.length; i++) {
const code = str.charCodeAt(i);
if (code >= 33 && code <= 126) {
result += String.fromCharCode(str.charCodeAt(i) + 65248);
} else if (code === 32) {
result += String.fromCharCode(str.charCodeAt(i) + 12288 - 32);
} else {
result += str.charAt(i);
}
if (reg.test(str[i])) {
result += ' ';
}
}
return result.replace(/【/g, '[').replace(/】/g, ']').replace(/!/g, '!');
}
封装与缓存
最后将获取的这一段逻辑封装成hooks,将对应文本的的url作为键,设置缓存,在下一次获取时,判断当前缓存是否命中,若存在,直接获取缓存:
const useChapterForUrl = (url: string) => {
const [content, setContent] = useState<ChapterContent>({
content: '',
title: '',
});
const isGet = useRef(false);
useLayoutEffect(() => {
if (cache.has(url)) {
const data: ChapterContent = cache.get(url) || {};
setContent(data ?? '');
isGet.current = true;
} else {
getNovelContent(url).then(res => {
setContent(res);
cache.set(url, res);
isGet.current = true;
});
}
}, [url]);
return {...content, get: isGet.current};
};
结语
在开始写的时候还是很顺利的, 这次滴内容就写到这了,后续计划更新的内容有: (下面计划的功能都是已经大致完成了💨)
- 搭建页面基础结构🎈
- 如何爬取页面内容💖
- 状态管理以及数据持久化🧡
- 小说内容主题自定义(字体/颜色/...)🤎
- 爬取后如何进行文本分页💘
- 分页后如何在用户阅读时,无感加载上/下一章💝