在线地址:ireader.liumin.me
github开源地址:github.com/liumin1128/…
( ̄▽ ̄)~*先声明哈,API来自追书神器,作者绝对支持正版,支持追书神器,本文只是为了在实战中探讨学习前端技术。
项目中会使用到的技术框架:React,Dva,material-ui
准备工作:Node环境,VSCode
dva new ireader
cd ireader
cnpm i material-ui --save
1. 首先我们添加最核心的功能:获取书源
新建阅读器的modals:reader
dva g model reader
我们可以看到modals
文件夹下已经出现了reader.js
文件,这是dva脚手架生成的modal文件,用以封装redux
相关功能,所有项目中涉及的数据,变量都要保存在这里。
查看reader.js
:
export default {
namespace: 'reader', // 命名空间,区别于其他modals
state: {}, // redux中的state,数据就存储在这里
subscriptions: { // 用于订阅事件
setup({ dispatch, history }) {
},
},
effects: { // 用于获取异步数据,进行流程控制
*fetch({ payload }, { call, put }) {
yield put({ type: 'save' });
},
},
reducers: { // 定义操作state的方法
save(state, action) {
return { ...state, ...action.payload };
},
},
};
在不考虑复杂场景的情况下,阅读器modals,其实就是对应了一本书的类,而一本电子书类需要保存那些信息呢?(O_O)?
- 当前章节(章节内容,章节标题,vip信息等等)
- 章节列表
- 源列表(目标就是实现换源功能,这个必须有)
- 以及浏览历史记录(章节位置,当前源等)
由此,我们在state
中加入:
state: {
chapter: {}, // 当前章节
chapterList: [], // 章节列表
bookSource: [], // 源
book: {
currentChapter: 0, // 当前章节
currentSource: 0, // 当前源
},
},
ok,我们的阅读器modals就成型了,我们现在需要获取一点数据。
获取数据的流程:书籍id => 源 => 章节列表 => 章节
获取id我们先不做,网上随便找个即可,比如《凡人》的id:508662b8d7a545903b000027
,下文常用此id来实验。
有了id,我们直接获取源即可,在effects
中加入以下代码:
effects: {
*getSource({ query }, { call, put }) {
const { data: bookSource } = yield call(bookReaderService.getSource, { query });
yield put({ type: 'save', payload: { bookSource } });
},
},
年轻司机们可能会感到疑惑,这代码怎么和平时见到的不一样?(O_o)??
确实,这里代码虽短,却使用了好几个es6新特性,一个一个来解释:
- 方法前面的
*
号,dva是基于redux-saga的封装,用来表示这个方法是异步用法,相当于async/await
中的async
- 参数
{ payload }, { call, put }
,这其实是两个参数,但每个参数都有自己的属性,即es6中的解构,这样的好处是不在乎参数的数量,参数的位置,以及参数中不需要的部分,还有就是不需要再let id = xxx.id
了。 const { bookSource } =
,依然是解构,从promise
返回值中获取bookSource
yield
,也是redux-saga
中的关键字,后面接一个异步方法,一般是promise
,表示等待其执行完成,才会继续执行下面的代码。相当于async/await
中的await
。call
和put
,都是redux-saga
中的关键字,call
接收一个异步方法,以及这个方法的参数,返回这个方法的结果;put
在redux
中负责调用一个action
,在dva
则表示调用一个effects
或reducers
,包括自身。
到这里,我们大概明白了getSource
方法是用来干什么的了,就是接收一个参数(就是书籍id),调用获取书源的方法,把返回结果保存到state
中。
我们先写保存书籍到state
的方法,在reducers
中加入:
reducers: {
save(state, { payload }) {
return { ...state, ...payload };
},
},
功能很简单,就是将新的数据与原来的state
合并。
当然现在代码还不能跑,我们还没写bookReaderService
,在service
中建立bookReader.js
文件,添加以下代码:
import request from '../utils/request';
export function getSource({ query }) {
return request(`/api/toc?view=summary&book=${query.id}`);
}
回到modals/reader.js
中引用:
import * as bookReaderService from '../services/bookReader.js';
同时设置一下代理,毕竟要跨域访问外网api嘛,在.roadhogrc
中加入如下代码:
"proxy": {
"/api": {
"target": "http://api.zhuishushenqi.com/",
"changeOrigin": true,
"pathRewrite": { "^/api" : "" }
},
"/chapter": {
"target": "http://chapter2.zhuishushenqi.com/",
"changeOrigin": true,
"pathRewrite": { "^/api" : "" }
}
},
至此,getSource已经可以正常工作,但现在还没有办法触发这个方法。我们需要通过一个事件,将书籍id传入,并调用getSource方法。 而另一方面,我们希望何时触发呢?我们可以在路由中监听url变化,判断用户一旦开始阅读某个书籍(也就是url中的书籍id发生变化),就触发这个方法获取书源。至于本地储存,性能优化,我们稍后再讲。
我们希望url应该是这个样子reader?id=508662b8d7a545903b000027
当用户访问这个路由时,即触发getSource
获取书源。我们并不需要自己去做这件事,dva直接就可以监听路由变化,我们只需要稍作配置,在reader.js
中的subscriptions
中加入:
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname, query }) => {
if (pathname === '/reader') {
dispatch({ type: 'getSource', query });
}
});
},
},
监听事件写好了,我们只需要用 dva-cli
来生成路由即可:
dva g route reader
现在我们可以启动项目了:
npm start
什么也没有发生?年轻司机们要有耐心,访问:http://localhost:8000/#/reader?id=508662b8d7a545903b000027即可在调试工具查看结果。
至此我们已经成功获取到书源,并且存入redux中。
这里需要chrome以及redux调试插件,年轻司机们需要额外安装:
进不了谷歌应用商店的点击这里:hosts
2. 获取章节列表
首先我们在获取书源后,获取章节列表:
yield put({ type: 'getChapterList', query: { id: bookSource[1]._id } });
这里我们默认传入第二个书源中的id,以便跳过优质书源。 然后增加获取章节列表的方法:
*getChapterList({ query }, { call, put, select }) {
const { data: { chapters } } = yield call(bookReaderService.getChapterList, { query });
yield put({ type: 'save', payload: { chapterList: chapters } });
},
别忘了在service/reader.js中增加getChapterList
方法:
export function getChapterList({ query }) {
return request(`/api/toc/${query.id}?view=chapters`);
}
刷新即可看到,章节列表也被存到redux中了:
3. 获取章节信息
同样的思路,我们需要在获取章节列表后获取具体章节信息:
yield put({ type: 'getChapter', query: { link: chapters[0].link } });
增加获取章节的方法:
*getChapter({ query }, { call, put }) {
const { data: { chapter } } = yield call(bookReaderService.getChapter, { query });
yield put({ type: 'save', payload: { chapter } });
},
同样不要忘了加上service/reader.js中的方法:
export function getChapter({ query }) {
return request(`/chapter/${query.link}?k=2124b73d7e2e1945&t=1468223717`);
}
这样就获取到了章节列表,是不是很轻松?
回顾以上内容,我们一步一步获取到了书源,用书源id获取了章节列表,最终获取章节内容,过程还是比较清晰的。
最终reader.js代码如下:
import * as bookReaderService from '../services/bookReader.js';
export default {
namespace: 'reader',
state: {
chapter: {}, // 当前章节
chapterList: [], // 章节列表
bookSource: [], // 源
book: {
currentChapter: 0, // 当前章节
currentSource: 0, // 当前源
},
},
reducers: {
save(state, { payload }) { return { ...state, ...payload }; },
},
effects: {
*getSource({ query }, { call, put }) {
const { data: bookSource } = yield call(bookReaderService.getSource, { query });
yield put({ type: 'save', payload: { bookSource } });
yield put({ type: 'getChapterList', query: { id: bookSource[1]._id } });
},
*getChapterList({ query }, { call, put, select }) {
const { data: { chapters } } = yield call(bookReaderService.getChapterList, { query });
yield put({ type: 'save', payload: { chapterList: chapters } });
yield put({ type: 'getChapter', query: { link: chapters[0].link } });
},
*getChapter({ query }, { call, put }) {
const { data: { chapter } } = yield call(bookReaderService.getChapter, { query });
yield put({ type: 'save', payload: { chapter } });
},
},
subscriptions: {
setup({ dispatch, history }) {
return history.listen(({ pathname, query }) => {
if (pathname === '/reader') {
dispatch({ type: 'getSource', query });
}
});
},
},
};
到目前为止,我们仅仅只是在调试工具中看到了结果,却从来没有在页面上体现内容。年轻司机们不要心急,我们离胜利仅有一步之遥~
我们打开routes/Reader.js
,先将redux数据传到组件中:
function Reader({ chapter }) {
return (
<div className={styles.normal}>
{chapter.title}
{chapter.body}
</div>
);
}
function mapStateToProps(state) {
const { chapter } = state.reader;
return {
chapter,
};
}
不出意外的话,章节标题和内容就正常显示出来啦。 这一节就到这里咯,下一节我们就让这款阅读器变的好用起来吧~[]~( ̄▽ ̄)~*
遇到问题的年轻司机们不要气馁,好好检查代码,没有错误才能继续下一步哦。