Redux

1. What problem does Redux solve

  • Redux appears to solve data problems in state
  • React allows data to flow in one direction through components
  • Data flows from a parent component to a child component (via props), and communication between non-parent components (or siblings) is cumbersome due to this feature

2. Redux design idea

  • Redux stores the entire application state in one place, called a store
  • It keeps a state tree insidestate tree
  • Components can be distributeddispatchbehavioractiontostoreInstead of notifying other components directly
  • Other components are available by subscriptionstoreThe state of(state)To refresh your own view

3. The Three Redux Principles

  • In the entire applicationstateIs stored in a treeobject treeC, and thisobject treeOnly one storestoreIn the
  • stateIt’s read-only, the only changestateThe method is triggeraction.actionIs a common object that describes events that have occurred, using pure functions to perform modifications, in order to describeactionHow to changestate tree, you need to writereducers
  • The design of a single data source makesreactCommunication between components is more convenient and state management is also easier

4. Native calculator

4.1.public/index.html

<! -- * @Author: dfh * @Date: 2021-03-07 13:46:20 * @LastEditors: dfh * @LastEditTime: 2021-03-07 14:16:29 * @Modified By: dfh * @FilePath: /day27-redux/public/index.html -->
<! DOCTYPEhtml>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="# 000000" />
  <meta name="description" content="Web site created using create-react-app" />
  <title>React App</title>
</head>

<body>
  <div id="root"></div>
  <div id="counter">
    <p id="counter-value"></p>
    <button id="add-btn">Add +1</button>
    <button id="minus-btn">Minus -1</button>
  </div>
</body>

</html>
Copy the code

4.2.src/index.js

import { createStore } from './redux';
// Get the native component
const counterValue = document.getElementById('counter-value');
const addBtn = document.getElementById('add-btn');
const minusBtn = document.getElementById('minus-btn');

// Define operation constants
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
/ / initial value
const initialState = { num: 0 };
/ / define reducer
const reudcer = (state = initialState, action) = > {
    switch (action.type) {
        case INCREMENT:
            return { num: state.num + 1 };
        case DECREMENT:
            return { num: state.num - 1 }
        default:
            returnstate; }}/ / create a store
const store = createStore(reudcer, { num: 1 })

function render() {
    counterValue.innerHTML = store.getState().num + ' ';
}

/ / subscribe reducer
store.subscribe(render);

render();

// Bind events
addBtn.addEventListener('click'.() = > {
    store.dispatch({ type: INCREMENT });// Send events
})

minusBtn.addEventListener('click'.() = > {
    store.dispatch({ type: DECREMENT });
})
Copy the code

4.3.redux/createStore

/ * * * *@param {} '*' Reducer processor *@param {*} PreloadedState Initial state of the repository */
function creatStore(reducer, preloadedState) {
    // Define a state variable and assign the default value
    let state = preloadedState;
    const listeners = [];

    function getState() {// Get the status
        return state;
    }

    /** * subscribe method, return an unsubscribe function *@param {*} Listener Subscribes to events */
    function subscribe(listener) {
        listeners.push(listener);/ / subscribe
        return () = > {// Used for destruction
            const idx = listeners.indexOf(listener);
            listeners.splice(idx, 1); }}/** ** distributed *@param {*} Action Indicates the action */
    function dispatch(action) {
        // Execute the actuator to get the new state
        state = reducer(state, action);
        // Notification of status change
        listeners.forEach(listener= > listener());
        //react SSR used on the return value
        return action;
    }

    // When the repository is created, an action will be sent and the default reducer Settings will take effect
    dispatch({ type: '@@REDUX/INIT' });
    const store = {
        getState,
        subscribe,
        dispatch,
    }
    return store;
}
export default creatStore;
Copy the code

4.4.redux/index.js

export { default as createStore } from './createStore';
Copy the code

5. Use the react

5.1.src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import Counter from './components/Counter'

ReactDOM.render(<Counter />, document.getElementById('root'));
Copy the code

5.2.components/Counter.js

import React from 'react';
import { createStore } from '.. /redux';

// Define operation constants
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

/ / initial value
const initialState = { num: 0 };

/ / define reducer
const reudcer = (state = initialState, action) = > {
    switch (action.type) {
        case INCREMENT:
            return { num: state.num + 1 };
        case DECREMENT:
            return { num: state.num - 1 }
        default:
            returnstate; }}/ / create a store
const store = createStore(reudcer, { num: 0 });

function Counter() {
    const [num, setNum] = React.useState(0);

    React.useEffect(() = > {
        / / subscribe
        const unsubscribe = store.subscribe(() = > setNum(store.getState().num))
        return () = > {/ / destroyunsubscribe(); }}, [])return <div>
        <p>{num}</p>
        <button onClick={()= > store.dispatch({ type: INCREMENT })}>add +1</button>
        <button onClick={()= > store.dispatch({ type: DECREMENT })}>minus -1</button>
    </div>
}

export default Counter;
Copy the code

6.bindActionCreators

6.1.components/Counter.js

  import React from 'react';
+ import { createStore, bindActionCreators } from '.. /redux';

// Define operation constants
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';

/ / initial value
const initialState = { num: 0 };

/ / define reducer
const reudcer = (state = initialState, action) = > {
    switch (action.type) {
        case INCREMENT:
            return { num: state.num + 1 };
        case DECREMENT:
            return { num: state.num - 1 }
        default:
            returnstate; }}/ / create a store
const store = createStore(reudcer, { num: 0 });

+ function add() {+return { type: INCREMENT }; +},function minus() {+return { type: DECREMENT }; +},const actions = { add, minus };

+ const boundActions = bindActionCreators(actions, store.dispatch);

function Counter() {
    const [num, setNum] = React.useState(0);

    React.useEffect(() = > {
        / / subscribe
        const unsubscribe = store.subscribe(() = > setNum(store.getState().num))
        return () = > {/ / destroyunsubscribe(); }}, [])return <div>
        <p>{num}</p>
+       <button onClick={boundActions.add}>add +1</button>
+       <button onClick={boundActions.minus}>minus -1</button>
    </div>
}

export default Counter;
Copy the code

6.2.redux/bindActionCreators.js

/** * Bind the action creator to the store.dispatch method *@param {*} ActionCreators Actions object *@param {*} dispatch 
 */
function bindActionCreators(actionCreators, dispatch) {
    const boundActionCreators = {};
    for (const key in actionCreators) {
        // Add or minus functions
        const actionCreator = actionCreators[key];
        boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
    }
    return boundActionCreators;
}

/ * * * *@param {*} actionCreator 
 * @param {*} dispatch 
 */
function bindActionCreator(actionCreator, dispatch) {
    const boundActionCreator = function (. args) {
        //{type:ADD}
        const action = actionCreator.apply(this, args);
        dispatch(action);
    }
    return boundActionCreator;
}

export default bindActionCreators;
Copy the code

6.3.redux/index.js

	export { default as createStore } from './createStore';
+ export { default as bindActionCreators } from './bindActionCreators';
Copy the code

7.combineReducers

7.1.src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import Counter1 from './components/Counter1'
import Counter2 from './components/Counter2'

ReactDOM.render(<div>
    <Counter1 />
    <Counter2 />
</div>, document.getElementById('root'));
Copy the code

7.2.componets/Counter1.js

import React from 'react' import { bindActionCreators } from '.. /redux' import store from '.. /store'; import actions from '.. /store/actions/counter1'; const boundAction = bindActionCreators(actions, store.dispatch); function Counter1() { const [num, setNum] = React.useState(store.getState().Counter1.num); React.useEffect(() => { return store.subscribe(() => { setNum(store.getState().Counter1.num); }) }) return <div> <p>{num}</p> <button onClick={boundAction.add1}>+</button> <button onClick={boundAction.minus1}>-</button> </div> } export default Counter1;Copy the code

7.3.components/Counter2.js

import React from 'react' import { bindActionCreators } from '.. /redux' import store from '.. /store'; import actions from '.. /store/actions/counter2'; const boundAction = bindActionCreators(actions, store.dispatch); function Counter2() { const [num, setNum] = React.useState(store.getState().Counter1.num); React.useEffect(() => { return store.subscribe(() => { setNum(store.getState().Counter2.num); }) }) return <div> <p>{num}</p> <button onClick={boundAction.add2}>+</button> <button onClick={boundAction.minus2}>-</button> </div> } export default Counter2;Copy the code

7.4.store/index.js

import { createStore } from '.. /redux';
import reducers from './reducers';

const store = createStore(reducers);
export default store;
Copy the code

7.5.store/action-types.js

const ADD1 = 'ADD1';
const MINUS1 = 'MINUS1';

const ADD2 = 'ADD2';
const MINUS2 = 'MINUS2';

export {
    ADD1,
    ADD2,
    MINUS1,
    MINUS2
}
Copy the code

7.6.store/reducers/index.js

import { combineReducers } from '.. /.. /redux';
import Counter1 from './counter1';
import Counter2 from './counter2';

const rootReducers = combineReducers({
    Counter1,
    Counter2
})
export default rootReducers;
Copy the code

7.7.store/reducers/counter1.js

import * as types from '.. /action-types';

const initialState = { num: 1 };
function reducer(state = initialState, action) {
    switch (action.type) {
        case types.ADD1:
            return { num: state.num + 1 };
        case types.MINUS1:
            return { num: state.num - 1 };
        default:
            returnstate; }}export default reducer;
Copy the code

7.8.store/reducers/counter2.js

import * as types from '.. /action-types';

const initialState = { num: 1 };
function reducer(state = initialState, action) {
    switch (action.type) {
        case types.ADD2:
            return { num: state.num + 1 };
        case types.MINUS2:
            return { num: state.num - 1 };
        default:
            returnstate; }}export default reducer;
Copy the code

7.9.store/actions/counter1.js

import * as types from '.. /action-types';

const actions = {
    add1() {
        return { type: types.ADD1 };
    },
    minus1() {
        return { type: types.MINUS1 }; }}export default actions;
Copy the code

7.10.store/actions/counter2.js

import * as types from '.. /action-types';

const actions = {
    add2() {
        return { type: types.ADD2 };
    },
    minus2() {
        return { type: types.MINUS2 }; }}export default actions;
Copy the code

7.11.CombineReducers implementation

  • redux/combineReducers.js
/** * Turn a reducers object into a reducer function *@param {*} reducers 
 * @returns * /
function combineReducers(reducers) {
    return (state = {}, action) = > {// The return function is our final reducer
        const nextState = {};// Declare an empty object to hold the final state (total state)
        let changed = false;
        for (let key in reducers) {
            const reducer = reducers[key];/ / reducer
            const previouseStateForKey = state[key];/ / state
            const nextStateForKey = reducer(previouseStateForKey, action);// Calculate the new partition state
            if(previouseStateForKey ! == nextStateForKey) {// Check whether the new and old states are the same
                changed = true;
            }
            nextState[key] = nextStateForKey;
        }
        // Return to the final state
        returnchanged ? nextState : state; }}export default combineReducers;
Copy the code
  • redux/indexjs
	export { default as createStore } from './createStore';
	export { default as bindActionCreators } from './bindActionCreators';
+ export { default as combineReducers } from './combineReducers';
Copy the code

8.react-redux

  • Provider.js
  • connect.js

8.1.src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import Counter1 from './components/Counter1'
import Counter2 from './components/Counter2'
import { Provider } from './react-redux'
import store from './store'

ReactDOM.render(<Provider store={store}>
    <Counter1 />
    <Counter2 />
</Provider>, document.getElementById('root'));
Copy the code

8.2.components/Counter1.js

import React from 'react' import { connect } from '.. /react-redux'; import actions from '.. /store/actions/counter1'; class Counter1 extends React.Component { render() { const { num, add1, minus1 } = this.props return <div> <p>{num}</p> <button onClick={add1}>+</button> <button onClick={minus1}>-</button> </div> } } const mapStateToProps = state => state.Counter1; export default connect( mapStateToProps, actions )(Counter1);Copy the code

8.3.components/Counter2.js

import React from 'react' import { connect } from '.. /react-redux'; import actions from '.. /store/actions/counter2'; class Counter1 extends React.Component { render() { const { num, add2, minus2 } = this.props return <div> <p>{num}</p> <button onClick={add2}>+</button> <button onClick={minus2}>-</button> </div> } } const mapStateToProps = state => state.Counter2; export default connect( mapStateToProps, actions )(Counter1);Copy the code

8.4.react-redux/Provider.js

import React from 'react';
import ReactReduxContext from './ReactReduxContext';

function Provider(props) {
    return (
        <ReactReduxContext.Provider value={{ store: props.store }}>
            {props.children}
        </ReactReduxContext.Provider>
    )
}
export default Provider;
Copy the code

8.5.react-redux/ReactReduxContext.js

import React from 'react';
export default React.createContext();
Copy the code

8.6.react-redux/connect.js

import React from 'react'; import ReactReduxContext from './ReactReduxContext'; import { bindActionCreators } from '.. /redux'; @param {*} mapStateToProps props store. Dispatch mapDispatchToProps store function connect(mapStateToProps, mapDispatchToProps) { return OldComponent => { return props => { const { store } = React.useContext(ReactReduxContext); const { getState, dispatch, subscribe } = store; const prevState = getState(); Const statetoprops = React. UseMemo (() => mapStateToProps(prevState), [prevState]); let dispatchProps = React.useMemo(() => { if (typeof mapDispatchToProps === 'object') { return bindActionCreators(mapDispatchToProps, dispatch); } else if (typeof mapDispatchToProps === 'function') { return mapDispatchToProps(dispatch, props); } else { return { dispatch }; Const [, forceUpdate] = React. UseReducer (x => x + 1, 0);}}, [dispatch]) // For data changes to refresh const [, forceUpdate] = React. React. UseLayoutEffect (() => {// subscribe return (forceUpdate)}, [subscribe]) return <OldComponent {... props} {... stateProps} {... dispatchProps} /> } } } export default connect;Copy the code

8.7.react-redux/index.js

export { default as Provider } from './Provider';
export { default as connect } from './connect';
Copy the code

9.hooks

  • useDispatch
  • useSelector

9.1.components/Counter1.js

import React from 'react'
import { useSelector, useDispatch } from '.. /react-redux'

function Counter1() {
    const state = useSelector(state= > state.Counter1);
    const dispatch = useDispatch();
    return <div>
        <p>{state.num}</p>
        <button onClick={()= > dispatch({ type: 'ADD1' })}>+</button>
        <button onClick={()= > dispatch({ type: 'MINUS1' })}>-</button>
    </div>
}
export default Counter1;
Copy the code

9.2.react-redux/hooks/index.js

export { default as useDispatch } from './useDispatch';
export { default as useSelector } from './useSelector';
Copy the code

9.3.react-redux/index.js

	export { default as Provider } from './Provider';
	export { default as connect } from './connect';
+ export { useSelector, useDispatch } from './hooks';
Copy the code

9.4.react-redux/hooks/useDispatch.js

import React from 'react'; import ReactReduxContext from '.. /ReactReduxContext'; const useDispatch = () => { return React.useContext(ReactReduxContext).store.dispatch; } export default useDispatch;Copy the code

9.5.react-redux/hooks/useSelector.js

import React from 'react'; import ReactReduxContext from '.. /ReactReduxContext'; function useSelector(selector) { const { store } = React.useContext(ReactReduxContext); const selectorState = useSelectorWithStore(selector, store); return selectorState; } function useSelectorWithStore(selector, store) { const { getState, subscribe } = store; const storeState = getState(); // total state const selectorState = selector(storeState); Const [, forceUpdate] = react. useReducer(x => x + 1, 0); React. UseLayoutEffect (() => {return subscribe(forceUpdate)// subscribe and destroy}, [store]) return selectorState; } export default useSelector;Copy the code

Middleware in 10.

  • Without middleware, redux’s workflow would beaction->reducer, which is equivalent to synchronous operation bydispatchThe triggerAfter action, go directlyReducer ‘Perform the corresponding actions
  • But without middleware, there can be problems with some complex logic. For example: we click a button -> Request data -> New data render view, at this time because the request data is asynchronous, at this time synchronous Redux is not satisfied, so the concept of middleware is introduced, with middleware Redux workflow becomesaction->middlewares->reducerClicking the button is equivalent todispatchtriggeredactionThen get the server datamiddlewaresTo perform, whenmiddlewaresTriggered when the server data has been successfully retrievedreducerThe corresponding action updates the view
  • Middleware mechanisms allow us to change the flow of data, for example asynchronouslyaction.The action filter.Log outputEtc.

10.1. Logging middleware

  • src/redux-logger.js
function logger(middlewareApi) {//middlewareApi={getState,dispatch}
    return next= > {// The original dispatch
        return action= > {/ / action
            const { getState } = middlewareApi;
            console.log('Old state', getState());
            next(action);//执行dispatch
            console.log('New state', getState()); }}}export default logger;
Copy the code

10.2. Promise middleware

  • src/redux-promise.js
const promise = middlewareApi= > next= > action= > {
    const { dispatch } = middlewareApi;
    // Check if it is a Promise action
    if (typeof action.then === 'function') {
        return action.then(dispatch)
    } 
    next(action);// Dispatch the action
}
export default promise;
Copy the code

10.3. Thunk middleware

  • src/redux-thunk.js
const thunk = middlewareApi= > next= > action= > {
    const { dispatch } = middlewareApi;
    if (typeof action === 'function') {
        return action(dispatch)
    }
    next(action);
}
export default thunk;
Copy the code

10.4.redux/compose.js

function compose(. fns) {
    return fns.reduce((a, b) = > (. args) = >a(b(... args))); }export default compose;
Copy the code

10.5.redux/applyMiddleware.js

import compose from './compose'

/ * * *@param {*} Middlewares Multiple middleware *@returns * /
function applyMiddleware(. middlewares) {
    return createStore= > {
        return reducers= > {
            const store = createStore(reducers);
            let dispatch;
            const middlewareApi = {
                getState: store.getState,
                dispatch: (action) = > dispatch(action)
            }
            const chain = middlewares.map(middleware= >middleware(middlewareApi)); dispatch = compose(... chain)(store.dispatch);return {
                ...store,
                dispatch
            }
        }
    }
}

export default applyMiddleware;
Copy the code

10.6.src/index.js

import React from 'react';
import ReactDOM from 'react-dom';
import Counter1 from './components/Counter1'
import Counter2 from './components/Counter2'
import Counter3 from './components/Counter3'
import { Provider } from './react-redux'
import store from './store'

ReactDOM.render(<Provider store={store}>
    <Counter1 />
    <Counter2 />
+   <Counter3 />
</Provider>, document.getElementById('root'));
Copy the code

10.7.components/Counter3.js

import React from 'react'; import { connect } from '.. /react-redux' import actions from '.. /store/actions/counter3' class Counter3 extends React.Component { render() { const { num, asyncAdd, pMinus } = this.props; return <div> <p>{num}</p> <button onClick={asyncAdd}>asyncAdd</button> <button onClick={pMinus}>promise minus</button> </div> } } const mapStateToProps = state => state.Counter3; export default connect(mapStateToProps, actions)(Counter3);Copy the code

10.8.store/index.js

 	import { createStore,applyMiddleware } from '.. /redux';
  import reducers from './reducers';
+ import logger from '.. /redux-logger'
+ import promise from '.. /redux-promise'
+ import thunk from '.. /redux-thunk'

+ const store = applyMiddleware(promise, thunk, logger)(createStore)(reducers);
  export default store;
Copy the code

10.9.store/actions/counter3.js

import * as types from '.. /action-types';

const actions = {
    asyncAdd() {
        return dispatch= > {
            setTimeout(() = > {
                dispatch({ type: types.ADD3 })
            }, 3000)}},pMinus() {
        return dispatch= > {
            new Promise((resolve) = > {
                resolve(1)
            }).then(res= > {
                dispatch({ type: types.MINUS3 })
            })
        }
    }
}

export default actions;
Copy the code

10.10.store/reducers/counter3.js

import * as types from '.. /action-types';

const initialState = { num: 1 };
function reducer(state = initialState, action) {
    switch (action.type) {
        case types.ADD3:
            return { num: state.num + 1 };
        case types.MINUS3:
            return { num: state.num - 1 };
        default:
            returnstate; }}export default reducer;
Copy the code

10.11.store/action-types.js

const ADD1 = 'ADD1';
const MINUS1 = 'MINUS1';

const ADD2 = 'ADD2';
const MINUS2 = 'MINUS2';

+ const ADD3 = 'ADD3';
+ const MINUS3 = 'MINUS3';

export {
    ADD1,
    ADD2,
    MINUS1,
    MINUS2,
+   ADD3,
+   MINUS3
}
Copy the code