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 inside
state tree
- Components can be distributed
dispatch
behavioraction
tostore
Instead of notifying other components directly - Other components are available by subscription
store
The state of(state)
To refresh your own view
3. The Three Redux Principles
- In the entire application
state
Is stored in a treeobject tree
C, and thisobject tree
Only one storestore
In the state
It’s read-only, the only changestate
The method is triggeraction
.action
Is a common object that describes events that have occurred, using pure functions to perform modifications, in order to describeaction
How to changestate tree
, you need to writereducers
- The design of a single data source makes
react
Communication 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 be
action->reducer
, which is equivalent to synchronous operation bydispatch
The triggerAfter action, go directly
Reducer ‘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 becomes
action
->middlewares
->reducer
Clicking the button is equivalent todispatch
triggeredaction
Then get the server datamiddlewares
To perform, whenmiddlewares
Triggered when the server data has been successfully retrievedreducer
The corresponding action updates the view - Middleware mechanisms allow us to change the flow of data, for example asynchronously
action
.The action filter
.Log output
Etc.
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