在上一教程中,我们已经成功调用 File-X API 来获取数据。接下来,我们对发现页面进行优化,添加滚动加载以及下拉刷新的功能,并且调整“事件”的展示
滚动加载及下拉刷新
滚动加载以及下拉刷新在现在的手机页面上还是比较常见的功能。这里我们将使用 react-infinite-scroll-component
这个第三方组件来实现这个功能。
react-infinite-scroll-component A component to make all your infinite scrolling woes go away with just 4.15 kB! Pull Down to Refresh feature added. An infinite-scroll that actually works and super-simple to integrate!
安装依赖包
$ npm i react-infinite-scroll-component -S
为配合 react-infinite-scroll-component
的API设计,我们先将数据加载的代码抽取出来,做成一个独立的函数 loadMore
;并添加一个 refresh
函数,来对应“下拉刷新”的功能。
打开 src/Explore.js
,开始编辑
function Explore() {
// state...
const loadMore = (reset = false) => {
if (state.loading) {
return;
}
setState({
...state,
loading: true,
items: reset ? [] : state.items,
});
const params = reset ? {
page: 1,
page_size: 12,
} : (
state.next || { page: 1, page_size: 12 }
)
fetchPublicFeed(params).then(({ data, pagination }) => {
setState({
...state,
loading: false,
next: pagination.next ? {
page: pagination.next,
page_size: pagination.page_size
} : null,
hasMore: !!pagination.next,
items: reset ? data : [
...state.items,
...data,
]
})
}).catch((err) => {
setState({
...state,
loading: false,
fetchError: err,
})
})
}
const refresh = () => loadMore(true);
useEffect(loadMore, []);
// rendering...
}
确定代码运行无误后,继续修改组件渲染的代码,我们会从 react-infinite-scroll-component
引入 InfiniteScroll
,并根据它的API文档使用该组件。
import InfiniteScroll from 'react-infinite-scroll-component';
function Explore() {
// state...
// loadMore and effect
// rendering...
return (
<div className='App'>
<div className="App__header">
<div className="PageHeader">
<h1 className="PageTitle pl_16">发现</h1>
</div>
</div>
<div className="App__content">
{/* 使用 InfiniteScroll 替换原渲染逻辑 */}
<div id="FeedList" style={{ height: '100%', overflow: 'auto'}}>
<InfiniteScroll
dataLength={state.items.length}
next={loadMore}
hasMore={state.hasMore}
loader={
<div className='px_16 py_12'>
<span>加载中</span>
</div>
}
endMessage={
<div className="px_16 py_12">
{!state.items.length && !state.hasMore ? (
<b>无相关内容</b>
) : (
<b>已经到底了</b>
)}
</div>
}
scrollableTarget='FeedList'
refreshFunction={refresh}
pullDownToRefresh
pullDownToRefreshThreshold={80}
pullDownToRefreshContent={
<h4 style={{textAlign: 'center'}}>↓ 下拉刷新</h4>
}
releaseToRefreshContent={
<h4 style={{textAlign: 'center'}}>↑ 释放后刷新</h4>
}
>
{state.fetchError ? (
[<div key='error'>{state.fetchError.message}</div>]
) : state.items.map((item, index) => (
<div className='px_16 py_12' key={item.id}>
<h3>{item.title}</h3>
<div>{item.content}</div>
</div>
))}
</InfiniteScroll>
</div>
</div>
<div className="App__footer">
<BottomBar />
</div>
</div>
)
}
打开浏览器 http://localhost:3000,刷新一下,可以看到如下所示的效果:
调整 “事件” 内容及样式
现在我们只是简单地输出了事件的内容,为了方便管理事件内容的展示,我们相关代码抽取出来,独立创建一个组件 EventItem
来处理事件的渲染。
新建文件 src/EventItem.js
import React from 'react'
import { Link } from 'react-router-dom'
import './EventItem.css'
function EventItem (props) {
const { data } = props
return (
<div className='EventItem'>
<div className='EventItem__header'>
<div className='EventItem__time'>{data.event_time}</div>
<Link
className='EventItem__user'
to={{
pathname: `/user/${data.user.uid}`,
state: {
user: data.user,
}
}}
>
<img
className='EventItem__avatar'
src={data.user.avatar}
alt={data.user.username}
/>
<span className='EventItem__username'>{data.user.username}</span>
</Link>
</div>
<h1 className='EventItem__title'>{data.title}</h1>
<div className='EventItem__content'>{data.content}</div>
<div className='EventItem__footer'>
<Link
className='button button_sm button_merge'
to={{
pathname: `/event/${data.id}/comment`,
state: {
event: data,
}
}}
>
{data.comment_count !== undefined ? `评论(${data.comment_count})` : '评论'}
</Link>
</div>
</div>
)
}
EventItem.defaultProps = {
showUserInfo: true
}
export default EventItem
在 src/EventItem.js
组件中,我们不仅输出了“事件”的标题,正文。还添加了用户信息的输出,以及一个“评论”链接。通过点击这个“评论”链接,用户可以进入到“评论列表”页面。
添加样式文件 EventItem.css
.EventItem {
/* margin-left: 12px;
margin-right: 12px; */
background-color: white;
border: 1px solid #f3f3f3;
padding: 12px 16px;
margin-bottom: 12px;
position: relative;
}
.EventItem__time {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.EventItem__title {
margin-top: 0px;
font-size: 18px;
}
.EventItem__content {
overflow: hidden;
}
.EventItem__user {
display: flex;
align-items: center;
/* padding-left: 5px; */
margin-top: 8px;
margin-bottom: 8px;
}
.EventItem__username {
margin-left: 5px;
color: midnightblue;
}
.EventItem__avatar {
width: 24px;
height: 24px;
border-radius: 12px;
}
.EventItem__deleteBtn {
position: absolute;
right: 12px;
top: 6px;
border-color: #ddd;
}
.EventItem__footer {
padding-top: 8px;
}
修改 src/Explore.js
替换原事件渲染代码, 并调整列表容器的背景色
import EventItem from './EventItem
//...
<div id="FeedList" style={{ height: '100%', overflow: 'auto', backgroundColor: '#f6f6f6'}}>
<InfiniteScroll
// ...
>
{state.fetchError ? (
[<div key='error'>{state.fetchError.message}</div>]
) : state.items.map((item, index) => (
<EventItem
data={item}
key={item.id}
/>
))}
</InfiniteScroll>
</div>
//...
最后的效果如图下图:
小结
到这里,这次优化的目标已经完成来。这个教程中,我们主要做了两个事情:
- 引入第三方组件,实现滚动加载及下拉刷新功能
- 使用独立组件对“事件”内容进行展示。
在下一部分,我们将会介绍如何OAuth 2.0 的 Implicit Grant Type 机制获取用户 Token,并且以用户的身份向 Feat.com API 请求数据。
额外任务
通过“发现”页面的制作,我们已经掌握了一下 react 开发的基本流程。我们可以自己做一个“用户”页面,来检验一下自己的学习效果。这里就不在教程中进行说明了,只提供一下思路,“用户”页面的代码可以在 github 仓库对应的 commit 中找到。
制作用户页面
- 提取
EventList
组件 - 从路由中获取参数,可参考文档 URL parameters
- 调用 Feat API :获取用户事件
经过分析,“用户”页面的页面布局、内容、以及数据加载流程,与“发现”页面还是蛮接近的。所以这里考虑中将“发现”页面的事件列表部分分离出来,作为一个独立的组件进行调用。调用情形设计:
<EventList
request={(paginationInfo) => {
// fetchPublicFeed();
// fetchUserEvents();
// ...
}}
/>
调整 EventItem
- 显示 EventItem 的附件图片的缩略
- 添加
LightBox
来预览图片, 可以第三方组件react-image-lightbox
来实现
大致效果预览: