React-Hooks、Redux实战教学

622 阅读8分钟

前言

本篇文章基于上篇文章所写,增加了新的页面和功能...,距离上次项目过了挺长一段时间,学习了更多关于react的相关知识,学以致用,将所学运用到实战当中,于是决定继续上次项目,进行改善、优化和增加内容。在本篇文章中我将着重来讲如何使用Redux来管理状态、以及最新的react-hooks。希望对你有所帮助。

项目新增

将上次项目的状态都改成了使用了redux进行状态管理,新增多个详情页和评价页面,至于有多少个完全看数据有多少,组件复用的好处就体现在这了,虽然说是复用的组件,要知道每个详情页是要区分开的,通过redux的数据共享以及状态处理,不仅让不同详情页的内容不一样,交互也区分开来了。还新增了性能优化,以及更多hooks的使用技巧。

效果展示

观看需知:防止视频过长,这里就不过多展示了,仅展示两个详情页,赛事筛选的也只是部分录制,具体可见上篇文章。每个详情页不仅内容会不一样,状态的更新也不会影响其他详情页,比如你在这个赛事详情里点赞或评论了,另一个赛事详情是不会出现的。

gitpage在线浏览效果

576711977.github.io/react-cat/

SC-1658471049366.gif

了解Redux

Redux 是 JavaScript 状态容器,提供可预测化的状态管理。使用Redux的优势有很多,一般大的项目使用redux来管理状态,使用起来非常舒服,不会像使用useState一样传递状态时可能发生的,父传子,子传孙...... 有了redux,它可以帮你处理应用的共享状态,数据被合并到一个集中的位置:store,哪个组件有需要就可以快速去获取想要的数据

在越来越多的人使用redux来集中管理后,react官方向我们提供更方便去获取redux状态的react-redux ,我们先安装这两个包redux和react-redux,然后继续讲解 原理图

redux原理图.png

redux要想学好,这张图是不可或缺的。

我们先了解这图中的几个模块

react components不用说,我们知道这代表多个组件,注意是多个,如果是一个那还用什么redux呢?

在组件想要通过redux做些什么,那需要明确动作对象,就是你要做什么操作,是什么类型的操作

action

action是动作的对象,对象里包含两个属性:

  • type: 值是字符串,必有且仅有的标识属性,动作操作的类型
  • data: 数据属性,我们要处理的数据 dispatch是一个函数,如果没有了dispatch,后面的流程都不会存在,它会帮我们把action分发下去,继续完成后续流程。

store

为了集中状态,store只有一个,仓库store将state、action、reducer联系起来,在图中就可以发现,store连接的线最多,是就一个c位,全局掌控者,但它实际上并不干活,什么意思呢,在它接收到action动作对象的时候,它不会加工状态,它就像一个让别做事的老板,它会直接交给reducer去做

reducer

老板就一个,但reducer是个打工人可以有多个(打工人泪奔),每个reducer干不同的事。那reducer做什么事呢

  • 用于加工状态,第一次执行时是初始化状态(previousState)
  • 加工时,浅比较新旧state,注意这里的浅比较,当state是引用数据类型时,我们需要返回新的对象产生新的state,然后return newState,每个reducer最后统一在一块交给store集中管理

流程走完,我们的组件就可以获取Store里已经处理好的状态了,图中这里是通过getState() 的api去获取,在后面我们增加react-redux中的概念后就能使用更加方便的方法去获取

详情页的redux搭建

reducer和action有多个,本文只拿出了部分来讲解

创建仓库

import { createStore, compose, applyMiddleware } from "redux";
import thunk from 'redux-thunk'
import reducer from './reducers'

const composeEnhancers = 
    window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

export default createStore(reducer,composeEnhancers(applyMiddleware(thunk)))
  • 我们使用createStoreapi来创建我们的仓库,传入集合后的reducer
  • composeEnhancers应用我们的开发者工具,可以更方便在浏览器查看状态(浏览器安装插件redux devtools)
  • applyMiddleware():应用上基于redux的中间件(插件库)
  • 使用异步中间件来支持异步action,npm install --save redux-thunk

编写action

写action之前,我们先定义要用的常量,可以减少我们的出错

export const CHANGE_CITY_SELECT = 'CHANGE_CITY_SELECT'
export const CHANGE_EVALUATE = 'CHANGE_EVALUATE'
export const CHANGE_EVALUATE_ID = 'CHANGE_EVALUATE_ID'
.....

action分同步和异步,同步action很好理解,异步action是用来处理异步任务的,比如获取接口数据

import * as actionTypes from '../constants'
import { getDetailDataRequest } from '../../api/request'

export const changeDetailData = data => ({ // 同步
    type: actionTypes.CHANGE_DETAIL_DATA,
    data
})
....
....
export const getDetailData = (id) => { // 异步
    return (dispatch)  => {
        (async () => {
            let {data} = await getDetailDataRequest(id)
            dispatch(changeEnterLoading(false))
            dispatch(changeDetailData(data))
        })()
    }
}

reducer加工

根据type类型来做不同处理

import * as actionTypes from '../constants'

const defaultState = {
    detailData: [],
    enterLoading: true,
    ...
    ...
    ...
}

export default (state=defaultState, action) => {
    const {type, data} = action;
    switch (type) {
        case actionTypes.CHANGE_IS_WANT:
            return {...state, isWant: data}
        ...
        ...
        ...
        default:
            return state
    }
}

组件关联仓库

在main.jsx中使用react-redux提供的Provider:让所有组件都可以得到state数据 引入我们的actions,connect用于包装 UI 组件生成容器组件

import store from './redux/store'
import {Provider} from 'react-redux'


ReactDOM.createRoot(document.getElementById('root')).render(
    <Provider store={store}>
        <BrowserRouter>
            <App />
        </BrowserRouter>
    </Provider>
)

connect的两个参数

1.  mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性。mapDispatchToProps:将分发action的函数转换为UI组件的标签属性,下方使用了简写方式

  1. 组件
import {  
    getDetailData, 
    changeDetailData,
} from '../../../redux/actions/detail'
...
function Detail(props) {
    const { 
        detailData,
        evaluate,
    } = props
    const { 
        getDetailData, 
        changeEvaluateId
    } = props // 
    ...
    ...
}
 export default connect( // 简写
    state => ({
        detailData: state.detail.detailData,
        contestList: state.contest.contestList,
        ...
    }),
    {
        getDetailData,
        changeDetailData,
        ...
    }
)(React.memo(Detail))

功能实现

上篇所涉及的就不再说了(太多了,讲不完),比如详情页的popup弹出层,还有一些其他antd-mobile的组件使用也很简单。

要把页面写漂亮很简单,多使用antd-mobile和一些好看的css样式以及好看的图片视频之类的静态资源就好了。这些也不详细展开讲了

但要完成交互就不是一件简单的事,状态的更新和联系很重要,如何去处理状态,怎样在不同情况下使用不同的状态,redux就显得格外重要

先看一下每部分的效果,再主要讲一下整体的思路和逻辑

上一篇文章有的图也不展示了

详情页面

111.gif

评论页面

111111111111111111666.gif

不同详情页的分离

aaa.gif

其他详情页

333333333333331.gif

详情页

首先详情页,异步action获取数据,不同详情页数据有些是共同的接口,然后reducer处理,使每个详情页的数据分离,交互状态也交给redux来管理,要注意每个状态的改变情况

比如当第一次进入第一个详情页要请求数据是需要有loading状态效果的,然后如果

  • 第二次如果是进入相同详情页,数据不需要重新获取,loading效果也就可以直接不加载
  • 第二次如果是进入不同详情页,数据是需要重新筛选获取的,就需要先把之前的组件里的数据给清理掉,再把新请求的数据放进来,而且还要有loading效果。在这里如果控制不好,很容易出现点进去时,会有一刻是之前的详情页然后闪一下才变成需要的数据,或者明明页面已经加载好了,loading效果却还持续了一小段时间才消失

详细流程下载代码浏览

评论页

评价页面要做到在评论后,每个详情页的评论都是互相分开的,可以在评论的时候,设置id值,然后在呈现到页面时进行筛选就行了,详细代码见源代码

其他

性能优化

React的memo:当数据变化时,代码会重新执行一遍,但是子组件数据没有变化也会执行,如果使用memo将子组件封装起来,只有子组件的数据发生改变时才会执行,达到性能优化的效果,useMemo上篇文章已提过

React.memo(Detail)

防抖

搜索 封装一个函数用于防抖,参考神三元大佬的防抖(建议去买他的小册,能学到很多东西),防抖是将多次执行变为最后一次执行

const debounce = (func, delay) => {
    let timer;
    return function (...args) {
        if(timer) {
        clearTimeout(timer);
        }
        timer = setTimeout(() => {
        func.apply(this, args);
        clearTimeout(timer);
        }, delay);
    };
};
  
export { debounce };

CSSTransition

  1. 给页面的路由切换带来动画效果
  2. 来自react-transition-group
  3. in + 私有的useState(show) 初始时 show false 内存中 useEffect mounted挂载后 浏览器上 show true
  4. classNames="xxx"
  5. xxx-enter 立刻
  6. xxx-exit 卸载,fly-exit-active 卸载进行时
<CSSTransition
    in={show}
    timeout={300}
    appear={true}
    classNames="xxx"
    unmountOnExit
    onExit={() => {navigate(-1)}} 
>
    <Container >
        ....
        ....
    </Container>
</CSSTransition>

项目主题

global-style.js

export default {
    "theme-color-red": "#f33c39",
    "font-color-gray": "#808080",
    "font-color-tips": "#c9aa8f", 
    "font-size-s": "13px",
    "font-size-m": "14px",
    "font-size-l": "16px",
    "font-size-ll": "18px",
    ....
    ....
 };

css in js 中引入使用

import styled from "styled-components";
import style from '../../assets/styles/global-style';

export const Container = styled.div`
    color: ${style["font-color-tips"]};
    .....
    .....
`

结语

写得有点仓促,后续补充,先点个赞支持一下吧

gitpage在线浏览页面效果 576711977.github.io/react-cat/