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


学习内容来源: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

<!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
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 采用了’装饰器模式’来复用代码:

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 的实例代码:

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 采用了‘代理模式’ 来复用代码:

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 设计:

<Route path = "/about" render= {
     
      (props) => <About {
     
     ...props} />}>

优点:

  1. 灵活

缺点:

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

2018 年,React团队宣布推出一种全新的重用代码的方式 —— React Hook。
.
它的核心改变是:允许函数式组件存储自己的状态,在这之前函数式组件是不能有自己的状态的。
.
这个改变使我们可以像抽象一个普通函数一样抽象 React 组件中的逻辑。
.
实现的原理:闭包

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
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

核心源码:

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 或其他中间件 可以让异步操作像同步操作一样优雅,异步和其他操作单独抽离出来

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

猜你喜欢

转载自blog.csdn.net/qq_32682301/article/details/132064487