[File-X Demo] — 优化“发现”页面

[File-X Demo] — 优化“发现”页面

在上一教程中,我们已经成功调用 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 
    request={(paginationInfo) => {
        // fetchPublicFeed();
        // fetchUserEvents();
        // ...
    }}
/>

调整 EventItem

  • 显示 EventItem 的附件图片的缩略
  • 添加 LightBox 来预览图片, 可以第三方组件 react-image-lightbox 来实现

大致效果预览:

发表评论

您的电子邮箱地址不会被公开。

您可以使用以下 HTML标签和属性:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Captcha Code