[Actual Combat] 9. In-depth React state management and Redux mechanism (3) —— React17+React Hook+TS4 best practice, imitating Jira enterprise-level projects (18)


Source of learning content: React + React Hook + TS Best Practice - MOOC


Compared with the original tutorial, I used the latest version at the beginning of my study (2023.03):

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

The specific configuration, operation and content will be different, and the "pit" will also be different. . .


1. Project launch: project initialization and configuration

2. React and Hook application: implement the project list

3. TS Application: JS God Assist - Strong Type

4. JWT, user authentication and asynchronous request


5. CSS is actually very simple - add styles with CSS-in-JS


6. User experience optimization - loading and error state handling



7. Hook, routing, and URL state management



8. User selector and item editing function


9. In-depth React state management and Redux mechanism

1&2

3&4

5. Introduction to redux usage

Predictable state container for JavaScript apps - Predictable state container for JavaScript applications

  • reduxreactNot directly related to , it can also be used in vueor other js/ts projects
  • reduxCalled a state container is more accurate than a state management tool

Next, let's look at an official case of html + jsusing it in ordinaryredux

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>

Does it feel very similar to the use-undo I wrote before?

Predictable: For the same input parameters, the return value of the function and its impact are certain


Dos and don'ts in Redux

  • reduxThe in stateis immutable, it can only be replaced and cannot be changed. Its design concept is consistent with reactthat in , they all use to compare whether is updated.state===state
  • reduxin reducermust be a pure function. But this does not mean that asynchronous functions cannot be used, you can use them in the callback of an asynchronous functiondispatch

Why not just return to the original state, but replace it?

Because the only way to compare whether all the attributes in two javascript objects are exactly the same is to do a deep comparison. However, in real applications, the code of a deep comparison is very large, which consumes a lot of performance and requires a lot of comparisons, so a An effective solution is to make a provision that the developer returns a new object whenever any changes occur.


What is a pure function?

  1. Does not depend on the state of the external environment, only on its input parameters - the same input will always return the same output
  2. No side effects - does not modify the input values ​​​​of the function, and does not produce any observable side effects, such as network requests, input and output devices, or data mutations (mutation)

6. react-redux and HoC

Container components are separated from presentation components

7. [Extended Learning] History of React Hook

The following is the original text of the courseware:

History of Hooks

The React team has paid great attention to the code reusability of React from the beginning.
.
Their solutions to code reusability have gone through: Mixin, HOC, Render Prop, until now Custom Hook
.
So Custom Hook is not a product that was born out of nowhere, even if many have rich experience in developing Custom Hook Developers don't know how Hook came about and what role it plays in React
.
If you don't understand this design idea, you can't understand Custom Hook deeply. Let's learn it together today.

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上的方法
  }
})

advantage:

  1. does serve the purpose of reusing code

shortcoming:

  1. It's an implicit dependency, and implicit dependencies are considered bad in React
  2. name conflict problem
  3. Can only work in ReactcreateClass, does not support ES6 ClassComponent
  4. In practice, it is found that it is difficult to maintain
    .

It has been marked as deprecated on the React official website, and the official complaint is here.

2. HOC

Beginning in 2015, the React team announced that Mixin is not recommended, and everyone is recommended to use the HOC mode
.
HOC uses the 'decorator mode' to reuse code:

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() {
     
     ...}
  }
}

This is where the separation of container presidential and classic container components begins.
.The
following is the most classic case of separation of HOC container components and presentation components - example code of connect in Redux:

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>
          ...
        )
      }
    }
  }
}

advantage

  1. Can work in any component including Class Component
  2. The principle of separation of container components and display components advocated by it has been achieved: separation of concerns

shortcoming

  1. unintuitive, difficult to read
  2. name conflict
  3. Components are nested layer by layer
3. Render Prop

Since 2017, Render Prop has become popular.
.
Render Prop uses the 'proxy pattern' to reuse code:

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 also adopts this API design:

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

advantage:

  1. flexible

shortcoming:

  1. hard to read, hard to understand
4. Hook

In 2018, the React team announced a new way to reuse code - React Hook.
.
Its core change is to allow functional components to store their own state. Before that, functional components could not have their own state.
.
This change allows us to abstract the logic in a React component as if it were a normal function.
.Principle
of implementation: closure

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

advantage:

  1. It is very easy to extract the logic out
  2. very easy to combine
  3. very readable
  4. No name conflict issues

shortcoming

  1. Hook has its own usage restrictions: it can only be used at the top level of the component, and can only be used in the component
  2. Since the principle is a closure, in rare cases there will be incomprehensible problems

8. Why do we need redux-thunk?

reduxjs/redux-thunk: Thunk middleware for Redux

Core source code:

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 can be placed in an asynchronous operation
  • Using redux-thunk or other middleware can make asynchronous operations as elegant as synchronous operations, and asynchronous and other operations are separated separately

Some reference notes are still in draft stage, so stay tuned. . .

Guess you like

Origin blog.csdn.net/qq_32682301/article/details/132064487