【实战】 九、深入React 状态管理与Redux机制(三) —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(十八)

文章目录

    • 一、项目起航:项目初始化与配置
    • [二、React 与 Hook 应用:实现项目列表](#二、React 与 Hook 应用:实现项目列表)
    • [三、TS 应用:JS神助攻 - 强类型](#三、TS 应用:JS神助攻 - 强类型)
    • 四、JWT、用户认证与异步请求
    • [五、CSS 其实很简单 - 用 CSS-in-JS 添加样式](#五、CSS 其实很简单 - 用 CSS-in-JS 添加样式)
    • [六、用户体验优化 - 加载中和错误状态处理](#六、用户体验优化 - 加载中和错误状态处理)
    • [七、Hook,路由,与 URL 状态管理](#七、Hook,路由,与 URL 状态管理)
    • 八、用户选择器与项目编辑功能
    • [九、深入React 状态管理与Redux机制](#九、深入React 状态管理与Redux机制)
      • 1&2
      • 3&4
      • 5.redux用法介绍
      • [6.react-redux 与 HoC](#6.react-redux 与 HoC)
      • [7.【扩展学习】 React Hook 的历史](#7.【扩展学习】 React Hook 的历史)
        • [Hook 的发展历程](#Hook 的发展历程)
          • [1. Mixin](#1. Mixin)
          • [2. HOC](#2. HOC)
          • [3. Render Prop](#3. Render Prop)
          • [4. Hook](#4. Hook)
      • 8.为什么我们需要redux-thunk?

学习内容来源:React + React Hook + TS 最佳实践-慕课网


相对原教程,我在学习开始时(2023.03)采用的是当前最新版本:

版本
react & react-dom ^18.2.0
react-router & react-router-dom ^6.11.2
antd ^4.24.8
@commitlint/cli & @commitlint/config-conventional ^17.4.4
eslint-config-prettier ^8.6.0
husky ^8.0.3
lint-staged ^13.1.2
prettier 2.8.4
json-server 0.17.2
craco-less ^2.0.0
@craco/craco ^7.1.0
qs ^6.11.0
dayjs ^1.11.7
react-helmet ^6.1.0
@types/react-helmet ^6.1.6
react-query ^6.1.0
@welldone-software/why-did-you-render ^7.0.1
@emotion/react & @emotion/styled ^11.10.6

具体配置、操作和内容会有差异,"坑"也会有所不同。。。


一、项目起航:项目初始化与配置

二、React 与 Hook 应用:实现项目列表

三、TS 应用:JS神助攻 - 强类型

四、JWT、用户认证与异步请求


五、CSS 其实很简单 - 用 CSS-in-JS 添加样式


六、用户体验优化 - 加载中和错误状态处理



七、Hook,路由,与 URL 状态管理



八、用户选择器与项目编辑功能


九、深入React 状态管理与Redux机制

1&2

3&4

5.redux用法介绍

Predictable state container for JavaScript apps ------ 用于JavaScript应用程序的可预测的状态容器

  • reduxreact 并没有直接关系,它也可以用在 vue 或其他 js/ts 项目中
  • redux 称为状态容器比状态管理工具要更准确

接下来看一个官方提供的在普通 html + js 中使用 redux 的案例

examples/counter-vanilla

js 复制代码
<!DOCTYPE html>
<html>
  <head>
    <title>Redux basic example</title>
    <script src="https://unpkg.com/redux@latest/dist/redux.min.js"></script>
  </head>
  <body>
    <div>
      <p>
        Clicked: <span id="value">0</span> times
        <button id="increment">+</button>
        <button id="decrement">-</button>
        <button id="incrementIfOdd">Increment if odd</button>
        <button id="incrementAsync">Increment async</button>
      </p>
    </div>
    <script>
      function counter(state, action) {
        if (typeof state === 'undefined') {
          return 0
        }

        switch (action.type) {
          case 'INCREMENT':
            return state + 1
          case 'DECREMENT':
            return state - 1
          default:
            return state
        }
      }

      var store = Redux.createStore(counter)
      var valueEl = document.getElementById('value')

      function render() {
        valueEl.innerHTML = store.getState().toString()
      }

      render()
      store.subscribe(render)

      document.getElementById('increment')
        .addEventListener('click', function () {
          store.dispatch({ type: 'INCREMENT' })
        })

      document.getElementById('decrement')
        .addEventListener('click', function () {
          store.dispatch({ type: 'DECREMENT' })
        })

      document.getElementById('incrementIfOdd')
        .addEventListener('click', function () {
          if (store.getState() % 2 !== 0) {
            store.dispatch({ type: 'INCREMENT' })
          }
        })

      document.getElementById('incrementAsync')
        .addEventListener('click', function () {
          setTimeout(function () {
            store.dispatch({ type: 'INCREMENT' })
          }, 1000)
        })
    </script>
  </body>
</html>

有没有感觉和之前写的 use-undo 十分类似?

可预测的(Predictable):对于相同入参,函数的返回值以及它产生的影响是一定的


Redux中的注意事项

  • redux 中的 stateimmutable,只能被替换,不能被改变。其设计理念与 react 中的 state 是一致的,它们都是通过 === 来比较 state 是否更新了。
  • redux 中的 reducer 必须是一个纯函数。但这并不代表不能使用异步函数,你完全可以在一个异步函数的回调中去使用dispatch

为什么不直接返回原state,而是要替换?

因为比较两个 javascript 对象中所有的属性是否完全相同,唯一的办法就是深比较,然而,深比较在真实的应用中代码是非常大的,非常耗性能的,需要比较的次数特别多,所以一个有效的解决方案就是做一个规定,当无论发生任何变化时,开发者都要返回一个新的对象。


何谓纯函数?

  1. 不依赖外部环境状态,只依赖于其输入参数 ------ 相同的输入永远返回相同的输出
  2. 无任何副作用 ------ 不修改函数的输入值,不会产生任何可观察的副作用,例如网络请求,输入和输出设备或数据突变(mutation)

6.react-redux 与 HoC

容器组件与展示组件分离

7.【扩展学习】 React Hook 的历史

以下内容是课件原文:

Hook 的发展历程

React团队从一开始就很注重 React 的代码复用性。

.

他们对代码复用性的解决方案历经:Mixin, HOC, Render Prop,直到现在的 Custom Hook

.

所以 Custom Hook 并不是一拍脑门横空出世的产物,即使是很多对 Custom Hook 有丰富开发经验的开发者,也不了解 Hook 到底是怎么来的,以及在 React 里扮演什么角色

.

不理解这段设计思路是无法深刻的理解 Custom Hook 的,今天我们就一起来学习一下。

1. Mixin
js 复制代码
var SetIntervalMixin = {
  componentWillMount: function() {
    this.intervals = [];
  },
  setInterval: function() {
    this.intervals.push(setInterval.apply(null, arguments));
  },
  componentWillUnmount: function() {
    this.intervals.forEach(clearInterval);
  }
};

var createReactClass = require('create-react-class');

var TickTock = createReactClass({
  mixins: [SetIntervalMixin], //使用mixin
  getInitialstate: function() {
    return {seconds: 0;},
  },
  componentDidMount: function() {
    this.setInterval(this.tick, 1000);//调用mixin上的方法
  }
})

优点:

  1. 确实起到了重用代码的作用

缺点:

  1. 它是隐式依赖,隐式依赖被认为在React中是不好的
  2. 名字冲突问题
  3. 只能在ReactcreateClass里工作,不支持ES6的ClassComponent
  4. 实践下来发现:难以维护
    .

在React官网中已经被标记为不推荐使用,官方吐槽点这里。

2. HOC

2015年开始,React团队宣布不推荐使用Mixin,推荐大家使用 HOC 模式

.

HOC 采用了'装饰器模式'来复用代码:

js 复制代码
function withWindowWidth(BaseComponent) {
  class DerivedClass extends ReactComponent {
    state = {
      windowWidth: window.innerwidth,
    }
    onResize = () => {
      this.setState({
        windowWidth: window.innerwidth,
      })
    }
    componentDidMount() {
      window.addEventListener(resizethisonResize)
    }
    componentWillUnmount(){
      window.removeEventListener(resizethisonResize)
    }
    render() {...}
  }
}

经典的容器组件与展示组件分离(separation of container presidential) 就是从这里开始的。

.

下面是最最经典的 HOC 容器组件与展示组件分离 案例 - Redux 中的 connect 的实例代码:

js 复制代码
export const createInfoScreen = (ChildComponent, fetchData, dataName) => {
  class HOComponent extends Comnonent {
    state = { counter: 0 }
    handleIncrementCounter = () => {
      this.setState({ counter:this.state.counter + 1 });
    }
    componentDidMount(){
      this.props.fetchData();
    }
    render() {
      const { data={},isFetching, error } = this.props[dataName];
      if (isFetching) {
        return(
          <div>Loading</div>
        );
      }
      if (error) {
        return(
          <div>Something is wrongPlease tryagain!</div>
          ...
        )
      }
    }
  }
}

优点

  1. 可以在任何组件包括 Class Component 中工作
  2. 它所倡导的容器组件与展示组件分离原则做到了:关注点分离

缺点

  1. 不直观,难以阅读
  2. 名字冲突
  3. 组件层层层层层层嵌套
3. Render Prop

2017 年开始,Render Prop 流行了起来。

.

Render Prop 采用了'代理模式' 来复用代码:

js 复制代码
class WindowWidth extends React.Component {
  propTypes = {
    children: PropTypes.func.isRequired
  }
  state = {
    windowWidth: window.innerWidth,
  }
  onResize = () => {
    this.setState({
      windowWidth: window.innerWidth,
    })
  }
  componentDidMount() {
    window.addEventListener('resize', this.onResize);
  }
  componentWillUnmount() {
    window.removeEventListener('resize', this.onResize);
  }
  ...
 }

React Router 也采用了这样的 API 设计:

js 复制代码
<Route path = "/about" render= { (props) => <About {...props} />}>

优点:

  1. 灵活

缺点:

  1. 难以阅读,难以理解
4. Hook

2018 年,React团队宣布推出一种全新的重用代码的方式 ------ React Hook。

.

它的核心改变是:允许函数式组件存储自己的状态,在这之前函数式组件是不能有自己的状态的。

.

这个改变使我们可以像抽象一个普通函数一样抽象 React 组件中的逻辑。

.

实现的原理:闭包

js 复制代码
import { useState, useEffect } from "react";
const useWindowsWidth = () => {
  const [isScreenSmall, setIsScreenSmall] = useState(false)

  let checkScreenize = () => {
    setIsScreenSmall(window.innerWidth < 600);
  };
  useEffect(()=> {
    checkscreenSize();
    window.addEventListener("resize", checkscreenSize);
    return () => window.removeEventListener("resize", checkScreenSize);
  }, []);

  return isScreenSmall
};

export default useWindowsWidth
js 复制代码
import React from 'react'
import useWindowWidth from'./useWindowWidth.js'

const MyComponent = () => {
  const onSmallScreen = useWindowWidth;

  return (
    // Return some elements
  )
}

优点:

  1. 提取逻辑出来非常容易
  2. 非常易于组合
  3. 可读性非常强
  4. 没有名字冲突问题

缺点

  1. Hook 有自身的用法限制:只能在组件顶层使用,只能在组件中使用
  2. 由于原理为闭包,所以极少数情况下会出现难以理解的问题

8.为什么我们需要redux-thunk?

reduxjs/redux-thunk: Thunk middleware for Redux

核心源码:

js 复制代码
import type { Action, AnyAction } from 'redux'

import type { ThunkMiddleware } from './types'

export type {
  ThunkAction,
  ThunkDispatch,
  ThunkActionDispatch,
  ThunkMiddleware
} from './types'

/** A function that accepts a potential "extra argument" value to be injected later,
 * and returns an instance of the thunk middleware that uses that value
 */
function createThunkMiddleware<
  State = any,
  BasicAction extends Action = AnyAction,
  ExtraThunkArg = undefined
>(extraArgument?: ExtraThunkArg) {
  // Standard Redux middleware definition pattern:
  // See: https://redux.js.org/tutorials/fundamentals/part-4-store#writing-custom-middleware
  const middleware: ThunkMiddleware<State, BasicAction, ExtraThunkArg> =
    ({ dispatch, getState }) =>
    next =>
    action => {
      // The thunk middleware looks for any functions that were passed to `store.dispatch`.
      // If this "action" is really a function, call it and return the result.
      if (typeof action === 'function') {
        // Inject the store's `dispatch` and `getState` methods, as well as any "extra arg"
        return action(dispatch, getState, extraArgument)
      }

      // Otherwise, pass the action down the middleware chain as usual
      return next(action)
    }
  return middleware
}

export const thunk = createThunkMiddleware()

// Export the factory function so users can create a customized version
// with whatever "extra arg" they want to inject into their thunks
export const withExtraArgument = createThunkMiddleware
  • dispatch 是可以放在异步操作中的
  • 使用了 redux-thunk 或其他中间件 可以让异步操作像同步操作一样优雅,异步和其他操作单独抽离出来

部分引用笔记还在草稿阶段,敬请期待。。。

相关推荐
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_029】3.5 给 D3 条形图加注图表标签(上)
开发语言·前端·javascript·信息可视化·数据可视化·d3.js
小白学习日记2 小时前
【复习】HTML常用标签<table>
前端·html
丁总学Java2 小时前
微信小程序-npm支持-如何使用npm包
前端·微信小程序·npm·node.js
yanlele2 小时前
前瞻 - 盘点 ES2025 已经定稿的语法规范
前端·javascript·代码规范
懒羊羊大王呀2 小时前
CSS——属性值计算
前端·css
DOKE3 小时前
VSCode终端:提升命令行使用体验
前端
xgq3 小时前
使用File System Access API 直接读写本地文件
前端·javascript·面试
用户3157476081353 小时前
前端之路-了解原型和原型链
前端
永远不打烊3 小时前
librtmp 原生API做直播推流
前端