react hooks + redux +mocker-api + express + immutable 仿极客时间App

667 阅读6分钟

前言

秋招正在进行时,听说很多公司都是React+ts技术栈,最近在准备提升自己的React实战能力,而且很多公司已全面升级到react hooks函数式 编程风格,读到了神三元在掘金的React Hooks 与 Immutable 数据流实战,写的很不错,于是,自己动手写一个仿极客时间APP的实战项目,提高自己的实战能力。

在线体验地址

github地址

项目整体使用的技术栈:react hooks + redux + mocker-api + express + immutable

项目优化:

  1. 图片懒加载: 监听屏幕滚动事件,以防造成页面空白甚至卡顿
  2. 路由懒加载: react-lazyload,React自带的路由懒加载,进行性能优化。
  3. memo:页面优化,减少组件的重复渲染。
  4. better-scroll:超级好用的scroll基础组件,滚动列表,提高用户体验。
  5. styled-component:组件化,引入style-component,爱上切页面。
  6. 路由:react-router-config 让网页地址更清晰
  7. 防抖:使用户体验更佳。
  8. Immutable:结构共享,能够最大效率地更新数据结构,提高 React 渲染性能

项目效果如下:

项目结构

对项目结构的整体架构,是学习神三元react架构思想的第一步,也是自己写实战项目重要的一步。

  • 全栈项目:一分为二的思想,前后端分离

  • api 架构:深入分析 axios配置文件config性更好, request 每个结构返回一个promise对象的函数,通过看函数,就可以把握应用业务全局。

  • 组件思想:页面与组件分离,把页面开发当成搭积木。

  • 后端API设计流程

    1. mongodb/mysql 写真后端 mockerAPI 解决了跨域
    2. 模块化输出api配置
    3. require json -> reducer -> connet -> 组件

    数据请求都放在actions中,异步的actions中 dispatch多个action

  • 项目整体目录结构如下:

├─React-geek-time
  ├─ geek-time_backend   //前端部分
  │  ├─ index.js
  │  ├─ mocker   		 //提供假数据
  │  │  ├─ data			 //数据
  │  │  └─ mocker.js	 //数据请求
  ├─ geek-time_frontend  //后端部分
  • 前端目录结构如下
├─src
  ├─ api 		// 数据请求接口及数据
  ├─ App.css		
  ├─ App.js
  ├─ assets		// 静态资源
  ├─ common		// 公用组件
  ├─ components	// 子组件
  ├─ index.css
  ├─ index.js	// 入口文件
  ├─ layouts	// 布局页面
  ├─ pages		// 页面
  │  ├─ explore	// 发现页面
  │  ├─ forum	// 讲堂页面
  │  ├─ my		// 我的页面
  │  ├─ payment	// 支付页面
  │  ├─ study	// 学习页面
  │  └─ tribe	// 部落页面
  ├─ routes		// 路由
  └─ store		// 总的store redux 模块化

前端部分

这里前端部分主要是对 路由配置及懒加载、styled-component样式、redux结构的设计及React.memo性能优化的使用讲解。

路由

  • 路由配置: routes/index.js 部分代码如下:

    1. react-router-config:路由的设置,能够更清晰的看到网页地址。

    2. react-lazyload:路由懒加载。

    使用React.lazySuspense来实现路由懒加载组件进行优化,可以理解成,先存放不加载就是lazy ,需要时即满足渲染条件就开始加载,就是Suspense ,这样可以快速打开页面。

    React文档对React.lazy的说法:

    React.lazy必须通过调用动态的import()加载一个函数,此时会返回一个Promise,并解析(resolve)为一个带有包含React组件的默认导出的模块。

    我们使用Suspense,它必须是延迟加载组件的父级(即React.lazy加载的组件只能在<React.Suspense>组件下使用)

styled-component

  • styled-components 专门解决切页面, 不用过于组件化的方法,且方便复用,类名不会冲突

  • 图片是styled-components的写法,在项目中我是在My.jsx页面中,给列表容器添加一个样式style.js,直接在组件中包一下即可

redux 结构的设计

axios定义完接口后用Redux进行数据请求,这里想讲一下 Redux 数据缓存。

就是你现在切换到浏览课程的讲堂页面时,然后切回到首页发现页面,在浏览器的 Network 中会看到又发了两次网络请求,而这两次请求是完全没有必要的,纯属浪费性能。而我们利用 Redux 的数据来进行页面缓存 成本最低。

 useEffect(() => { 
     	//immutable 数据结构中长度属性 length
        // 如果页面有数据,则不发请求
        if (!pathList.toJS().length || !directionList.toJS().length) {
            getForumListDataDispatch();
        }
        if (!infoList.toJS().length) {
            getInfoListDataDispatch();
        }
    }, [getForumListDataDispatch, getInfoListDataDispatch, pathList, directionList, infoList])

memo 的使用

  1. react页面渲染过程中,为了减少diff算法,优化性能,使用shouldComponentUpdate,因为 默认的 shouldComponentUpdate 会在 propsstate 发生变化时返回 true, 表示组件会重新渲染,从而调用 render 函数,进行新旧 DOM 树的 diff 比较。

  2. 但是这个项目全面拥抱函数式组件,不再用类组件了,因此 shouldComponentUpdate 就不能再用了。React 为函数组件提供了一个 memo 方法,它和 PureComponent 在数据比对上唯一的区别就在于 只进行了 props 的浅比较。而且它的用法很简单,直接将函数组件React.memo 包裹然后导出即可。形如:

      function Home () {
            //xxx
        } 
      export default React.memo (Home);
    

    ​ 他将上一次的props与这次的props作比较 相同返回 true 不同返回 false

3.memo为高阶组件,通过记忆组件渲染结果的方式来提高组件的性能表现。默认情况下其只会对复杂对象做浅层对比,如果我们想要控制对比过程,可以将自定义的比较函数通过第二个参数传入来实现。通过第二个参数指定一个自定义的方法 来判断两次 props 有什么不同,memo会帮我们缓存上一个值,当我们接收一个新的值之后,两个值进行比较,相同的话,拒绝修改,直接使用已经缓存的值,不同的话, 则改变。 达到即缓存又更新的能力。

后端部分

后端主要实现假数据的模拟和数据请求,使用mockjs模拟假数据方便前后端对接,axios的GET和POST请求封装express使用代理,解决跨域问题。

mockjs模拟假数据

data目录下用json文件格式写模拟的假数据,然后在mock.js里面引入即可使用模拟的数据

axios的GET和POST请求封装

  1. api目录下, 用axios定义了接口函数axiosInstance
const axiosInstance = axios.create({
    baseURL:baseUrl
})
// 回复处理
axiosInstance.interceptors.response.use(
    res => res.data,
    err => {
        console.log(err, '网络错误')
    }
)
  1. 然后在请求中引入axiosInstance,通过这个函数,get 得到 URL 获取我们需要的数据,部分如下:

    // 获取学习路径和课程方向的数据
    export const getLessonsRequest = () => {
        return axiosInstance.get("/all");
    }
    
    // 获取全部课程的数据
    export const getInfoListRequest = count => {
        return axiosInstance.get(`/infos?offset=${count}`);
    }
    

express

通过express框架使用代理来启动一个服务。

入口文件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'))
//使用express监听8080端口号,
app.listen(8080);