React hooks——前后端交互处理分页

时间:2020-8-21 作者:admin

React hooks——前后端交互处理分页

我认为,对于一个优秀的前端工程狮来说,不仅仅要熟练前端,同样的,后端的流程也需要了解。这样日后在和后端对接时也会十分流畅,这里我推荐使用 mocker-api 实现模拟数据,方便和后端进行对接。

前言

在本小节中需要完成以下功能,

  1. 下拉刷新,
  2. 上拉加载更多。
  3. 数据分页返回的处理。

我们可以直接使用神三元项目中封装好的better-scroll。

先上成果图

React hooks——前后端交互处理分页

部分项目结构

|-- pages // 页面文件夹|   |-- forum // 讲堂页面文件夹|   |   |-- course // 课程页面|   |   |   |-- Forum.css|   |   |   |-- Forum.jsx|   |   |   |-- style.js|   |   |   |-- store // 课程页面 store|   |   |       |-- actionCreators.js // action 文件|   |   |       |-- constants.js // 常量定义文件|   |   |       |-- index.js // store 入口文件|   |   |       |-- reducer.js // reducer 文件

理解项目结构, 是学习React架构思想的第一步,也是最重要的一步。在我的项目中, pages 文件夹存放着各个页面的主文件夹。这里我们主要是看 forum 即讲堂这一部分内容的实现。通过阅读神三元的项目,我理解到可以分页面建立store,使得数据访问更加简洁,store 设计思路更加清楚。

前端代码

讲堂页面分析

  • forum.jsx

思路分析: forum.jsx 文件中引入封装好的 better-scroll(可以直接使用神三元封装好的better-scroll代码) 监听页面下拉和上拉事件。pulldown函数,监听下拉动作,可以实现下拉刷新。pullup函数,监听上拉动作,可以实现上拉加载。handlePullUp 函数 和 handlePullDown 函数通过props传递给公共组件 Scroll。

  1. 当浏览器监听到对应事件比如说 pulldown 下拉动作时 就会调用函数 handlePullDown, 函数handlePullDown又会调用从props解构出来的pullDownRefresh(), 该函数会dispatch 两个action,一个是changeListOffset(0),一个是getInfoList()。changeListOffset(0) 用于将 store里面的listOffset设置为0,getInfoList()用于获取详情列表数据。

代码如下:

import React, { useEffect, memo } from 'react';import { connect } from 'react-redux';import * as actionTypes from './store/actionCreators';import ForumList from '../../../components/ForumList/ForumList';import Loading from '../../../common/loading/Loading';import Scroll from '../../../common/scroll/Scroll';import { ListContainer } from './style';import { forceCheck } from 'react-lazyload';import './Forum.css';import { renderRoutes } from 'react-router-config';function Forum(props) {    const { route, pathList, directionList, infoList, enterLoading, pageCount, pullUpLoading, pullDownLoading } = props;    const { getForumListDataDispatch, getInfoListDataDispatch, pullUpRefresh, pullDownRefresh } = props;        const handlePullUp = () => {        pullUpRefresh();    };    const handlePullDown = () => {        pullDownRefresh();    };    useEffect(() => {        // 如果没有数据 请求一次        if (!pathList.toJS().length || !directionList.toJS().length) {            getForumListDataDispatch();        }        if (!infoList.toJS().length) {            getInfoListDataDispatch();        }    }, [getForumListDataDispatch, getInfoListDataDispatch, pathList, directionList, infoList])    return (        <div className="forum">            <ListContainer>                <Scroll                    onScroll={forceCheck}                    pullUp={handlePullUp}                    pullDown={handlePullDown}                    pullUpLoading={pullUpLoading}                    pullDownLoading={pullDownLoading}                // pulldown,监听下拉动作,可以实现下拉刷新;                // pullup,监听上拉动作,可以实现上拉加载;                >                    <div>                        <div className="forum-xw">                            <ForumList infoList={infoList} />                        </div>                    </div>                </Scroll>            </ListContainer>            <Loading Loading={enterLoading} title="正在加载中..." />            {/* 重新 render routes 一次 */}            {renderRoutes(route.routes)}        </div>)}const mapStateToProps = (state) => ({    pathList: state.forum.pathList,    directionList: state.forum.directionList,    infoList: state.forum.infoList,    enterLoading: state.forum.enterLoading,    pageCount: state.forum.pageCount,    pullUpLoading: state.forum.pullUpLoading,    pullDownLoading: state.forum.pullDownLoading })const mapDispatchToProps = (dispatch) => {    return {        getForumListDataDispatch() {            dispatch(actionTypes.getPathList())            dispatch(actionTypes.getDirectionList())        },        getInfoListDataDispatch() {            dispatch(actionTypes.getInfoList())        },        // 滑到最底部刷新部分的处理        pullUpRefresh() {            dispatch(actionTypes.changePullUpLoading(true));            dispatch(actionTypes.refreshMoreInfoList());        },                //顶部下拉刷新        pullDownRefresh() {            dispatch(actionTypes.changePullDownLoading(true));            dispatch(actionTypes.changeListOffset(0));            dispatch(actionTypes.getInfoList());        }    }}export default connect(mapStateToProps, mapDispatchToProps)(memo(Forum))

store 的设计

  • constants.js 文件
export const CHANGE_INFOS = 'CHANGE_INFOS';export const CHANGE_LIST_OFFSET = 'home/singers/CHANGE_LIST_OFFSET'; // 数据分页常量export const CHANGE_ENTER_LOADING = 'CHANGE_ENTER_LOADING';export const CHANGE_PULLUP_LOADING = 'home/singers/PULLUP_LOADING'; // 上拉常量export const CHANGE_PULLDOWN_LOADING = 'home/singers/PULLDOWN_LOADING'; // 下拉常量

constants.js 文件负责对常量的定义以及导出。

  • reducer.js
import * as actionTypes from './constants';const defaultState = {    // 一个页面只有一个 loading 值    // 全部课程数据    infoList:[],    enterLoading:true,     // 加载中    pullUpLoading: false, // 上拉加载    pullDownLoading: false, // 下拉刷新    listOffset: 0, // 请求列表偏移是个数}export default (state = defaultState, action) => {    switch(action.type) {        case actionTypes.CHANGE_INFOS:            return { ...state, infoList: action.data}        case actionTypes.CHANGE_ENTER_LOADING:            return { ...state, enterLoading: action.data}        case actionTypes.CHANGE_LIST_OFFSET:            return { ...state, listOffset: action.data}        case actionTypes.CHANGE_PULLUP_LOADING:            return { ...state, pullUpLoading: action.data}        case actionTypes.CHANGE_PULLDOWN_LOADING:            return { ...state, pullDownLoading: action.data}        default:            return state    }}

reducer 纯函数 返回状态及接受状态的更新 只有一个状态与之相对应。

  • actionCreators.js
// 负责进行数据请求import * as actionTypes from './constants';// 请求接口文件import { getInfoListRequest} from '../../../../api/request';export const changeListOffset = (data) => ({    type: actionTypes.CHANGE_LIST_OFFSET,    data  });// 进场 loadingexport const changeEnterLoading = (data) => ({    type: actionTypes.CHANGE_ENTER_LOADING,    data})//滑动最底部loadingexport const changePullUpLoading = (data) => ({    type: actionTypes.CHANGE_PULLUP_LOADING,    data  });  //顶部下拉刷新loadingexport const changePullDownLoading = (data) => ({    type: actionTypes.CHANGE_PULLDOWN_LOADING,    data  });export const changeInfoList = (data) => ({    type: actionTypes.CHANGE_INFOS,    data})// 获取详情列表数据export const getInfoList = () => {    return ( dispatch, getState) => {        // 获取原store中的偏移量        const offset = getState().forum.listOffset;        // 偏移量传进接口请求中        getInfoListRequest(offset).then(res=> {            const data = res.infos            dispatch(changeInfoList(data));            // 拿到数据  EnterLoading 变成false            dispatch(changeEnterLoading(false));            dispatch(changePullDownLoading(false));            dispatch(changeListOffset(data.length));        })        // 如果拿到错误的数据        .catch(() => {            console.log('详情列表数据传输错误')        })    }}// 加载更多数据export const refreshMoreInfoList = () => {    return ( dispatch, getState ) => {     // 获取 原来的 listOffset 和 infoList 数据        const offset = getState().forum.listOffset;        const infos = getState().forum.infoList;        getInfoListRequest(offset).then(res => {            // 让当前的 infos 和新请求的 infos 使用拓展运算符展开并拼接成一个新的数据;            const data = [...infos, ...res.infos]            dispatch(changeInfoList(data));            dispatch(changePullUpLoading(false));            dispatch(changeListOffset(data.length));        })        .catch(() => {            console.log('详情列表数据传输错误')        })    }}

思路分析:

当获取详情列表数据,即调用getInfoList时,我们需要先获取原store 中的偏移量并发出请求,成功拿到数据后,我们将下拉刷新设为false,并且将listoffset偏移量设置为接收到数据的长度。

当我们需要加载更多数据时,我们需要使用 getState 先获取原有 的偏移量listOffset和infoList 数据,
得到数据之后,我们将老数据和新数据进行拼接得到一个新的data,然后再changeInfoList(data)修改store的数据,使我们之前的数据不会丢失。并且将listoffset设置为新的data的长度,保证数据分页正确。
我们来到store里面的getInfoList

export const getInfoList = () => {    return ( dispatch, getState) => {        // 获取原store中的偏移量        const offset = getState().forum.listOffset;        // 偏移量传进接口请求中        getInfoListRequest(offset).then(res=> {            const data = res.infos            dispatch(changeInfoList(data));            // 拿到数据  EnterLoading 变成false            dispatch(changeEnterLoading(false));            dispatch(changePullDownLoading(false));            dispatch(changeListOffset(data.length));        })        // 如果拿到错误的数据        .catch(() => {            console.log('详情列表数据传输错误')        })    }}

这个函数return 一个函数,里面接受两个参数 dispatch 和 getState,dispatch 用于修改store里面的内容,getState用于获取store里面的内容,我们先从 store 里面获取listOffset,然后将 offset 作为参数传进getInfoListRequest中,该函数会拿到结果,然后再修改store里面的数据,并将EnterLoading变成false,PullDownLoading变为false 修改store 里面的listoffset 为 当前获取数据的长度。

请求接口文件

  • config.js
import axios from 'axios';// 推荐使用 axios 兼容性更好export const baseUrl = "http://localhost/data"; // 全局的后端 api 前缀const axiosInstance = axios.create({    baseURL:baseUrl})// 回复处理axiosInstance.interceptors.response.use(    res => res.data,    err => {        console.log(err, '网络错误')    })export { axiosInstance }
  • request.js
// 获取学习路径和课程方向的数据import { axiosInstance } from './config';// 获取全部课程的数据export const getInfoListRequest = count => {    return axiosInstance.get(`/infos?offset=${count}`);}

后端代码

const infos = require('./data/infos.json');module.exports = {    // 分页功能数据    'GET /data/infos': (req, res) => {        // 获取传进来的 参数offset        const { offset = 0 } = req.query;        // 使用 Number 转化为 Number 型        const data = infos.slice(Number(offset) , Number(offset)  + 10);        res.json({            "infos":data});    }}

用户滑动屏幕,触发相应事件,传入的offset,可以通过req.query解构出来,如果没有传入则默认是0,我们可以根据这个 offset 对数组进行切割处理。然后返回相应的数据。

入口文件 index.js

const express = require('express');const path = require('path');const apiMocker = require('mocker-api');const app = express();apiMocker(app, path.resolve('./mocker/mocker.js'))app.listen(8080);

以上就是本小节的主要内容,个人技术不佳,项目还有缺陷,欢迎掘友指正,我会好好改进,下面是项目源码。Github React-geek-time 项目源码

笔者正在准备秋招,欢迎各位大佬一起讨论,一起加油!

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。