Let’s take it one step at a time and do a mini-redux first

Chinese document

1. Mini version implementation

1.1 Through the original library

Use Redux and React to create a simple page, and then implement your own functions.

page

import React, { Component } from 'react';
import store from '.. /store';

export default class ReduxPage extends Component {
  // If the button cannot be updated, check whether the subscribe status changes.
  componentDidMount() {
    this.unsubscribe = store.subscribe(() = > {
      this.forceUpdate();
    });
  }

  componentWillUnmount() {
    if (this.unsubscribe) {
      this.unsubscribe();
    }
  }

  add = () = > {
    store.dispatch({ type: 'ADD' });
  };

  minus = () = > {
    store.dispatch({ type: 'MINUS' });
  };

  render() {
    return (
      <div style={{ padding: 24}} >
        <h3>ReduxPage</h3>
        <p>{store.getState()}</p>
        <button type="button" onClick={this.add}> + add</button>
        <button type="button" onClick={this.minus}> - minus</button>
      </div>); }}Copy the code

store

import { createStore } from 'redux';

function countReducer(state = 0, action) {
  switch (action.type) {
  case 'ADD':
    return state + 1;
  case 'MINUS':
    return state - 1;
  default:
    returnstate; }}const store = createStore(countReducer);

export default store;

Copy the code

The effect

1.2 analysis

Click the button => Execute the store.dispatch function (parameters only support objects) => Store is created by countReducer of Redux (parameters are the reducer pure function defined by us) => Subscribe => Subscribe is triggered when the store data changes. Then the page calls forceUpdate to update the page

Conclusion: Redux requires the following functions

  1. Dispatch submit update
  2. CreateStore create store
  3. GetState Gets the status value
  4. Subscribe to

1.3 createStore

Calling this function returns store, which contains dispatch, subscribe, and getState

// function countReducer(state = 0, action) {
// switch (action.type) {
// case 'ADD':
// return state + 1;
// case 'MINUS':
// return state - 1;
// default:
// return state;
/ /}
// }
// const store = createStore(countReducer); A Reducer of the definition was passed in
export default function createStore(reducer) {
  let currentState;
  const currentListeners = [];
  function getState() {
    return currentState;
  }

  // store.dispatch({ type: 'ADD' }); An action is passed in
  function dispatch(action) {
    // Do the reducer
    currentState = reducer(currentState, action);
    // Execute all listeners after data update
    currentListeners.forEach(listener= > listener());
  }

  // A callback function is passed in, the data is updated, and the callback is executed
  function subscribe(listener) {
    currentListeners.push(listener);
  }

  // Initial value, manually issue a dispatch, in order to avoid the same user created, the source code uses a random string.
  dispatch({ type: 'REDUX/MIN_REDUX' });

  return {
    subscribe,
    getState,
    dispatch
  };
}
Copy the code

Then replace the place in our store where redux was introduced with our createStore.

import { createStore } from ‘redux’; // Replace the createStore function path in store

2. Advanced – Middleware

Asynchronous data flow

By default, the Redux store created by createStore() does not use Middleware, so it only supports synchronous data streams.

You can augment createStore() with applyMiddleware().

2.1 Using Middleware

We add two buttons to the page page and bind the event

asyncAdd = () = > {
    store.dispatch((dispatch) = > {
        setTimeout(() = > {
            dispatch({ type: 'ADD' });
        }, 1000);
    });
}
promiseMinus = () = > {
    store.dispatch(Promise.resolve({
        type: 'MINUS'.payload: 1000
    }));
}

<button style={{ margin: '0 8px' }} type="button" onClick={this.asyncAdd}> + async add</button>

<button style={{ margin: '0 8px'}}type="button" onClick={this.promiseMinus}> - promise minus</button>
Copy the code

In the code above, we passed in a function at Dispatch, along with a Promise.

Redux-promise supports promise, redux-thunk supports asynchronous data, and redux-Logger prints store data change logs.

import { applyMiddleware, createStore } from 'redux';
import promise from 'redux-promise';
import thunk from 'redux-thunk';
import logger from 'redux-logger';

const store = createStore(countReducer, applyMiddleware(thunk, logger, promise));
Copy the code

The effect is as follows:

2.2 applyMiddleware

From the above usage, we can see that applyMiddleware supports passing in multiple middleware to enhance the store and dispatch to support promises and asynchronous data flows.

The createStore function currently supports two arguments, but the code we wrote above supports only one. Add the following code, if there is a second parameter we execute the second parameter, we need to add store all the parameters were passed to store, then we need to use reducer when executing.

export default function createStore(reducer, enhancer) {
  if (enhancer) {
    returnenhancer(createStore)(reducer); }... }Copy the code

ApplyMiddleware implementation

import compose from './compose';

export default function applyMiddleware(. middlewares) {
  CreateStore (reducer) createStore (reducer) createStore (reducer
  // args => reducer (count
  return createStore= > (. args) = > {
    CreateStore is passed as the first parameter, and we get store, which has dispatch
    conststore = createStore(... args);let { dispatch } = store;
    // Once we get the dispatch, we need to enhance it
    const middlewareAPI = {
      getState: store.getState,
      // disptch originally has any parameters, pass them in
      dispatch: (. params) = >dispatch(... params) };// Pass in the getState and dispatch that each middleware needs to enforce
    const chain = middlewares.map(middleware= > middleware(middlewareAPI));
    // Just execute each middleware, using the compse function we wrote above.
    The compose function is composed for some reason.
    // applyMiddleware(thunk, Logger, promise) is passed in three middleware
    // compose is equivalent to thunk(logger(promise(dispatch)))
    The first execution of promise returns a callback, the second execution of Logger returns a callback, and the actual external callback is executed only after thunkdispatch = compose(... chain)(store.dispatch);return {
      ...store,
      // Returns the enhanced dispatch
      dispatch
    };
  };
}
Copy the code
export default function compose(. funcs) {
  if (funcs.length === 0) {
    return arg= > arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  return funcs.reduce((a, b) = > (. args) = >a(b(... args))); }Copy the code

2.3 story – logger

Let’s start with the simplest redux-Logger that prints store changes

// const chain = middlewares.map(middleware => middleware(middlewareAPI));
// In the code above, we pass in the middlewareAPI, destruct to get the getState
export default function logger({ getState }) {
  // In the compose aggregation function, when reduce is executed, fn1 and f2, next is F1.
  return next= > action= > {
    console.log('* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *');
    console.log(`action ${action.type} @ The ${new Date().toLocaleString()}`);
    // Execute getState() to get the current state.
    const prevState = getState();
    console.log('prev state', prevState);

    / / dispatch
    const returnValue = next(action);
    // Get the state after execution
    const nextState = getState();
    console.log('next state', nextState);

    console.log('* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *');
    return returnValue;
  };
}
Copy the code

2.4 story – thunk

export default function thunk({ getState, dispatch }) {
  return next= > action= > {
    // If dispatch passes in a function, it executes the function, returns a function, and enters the callback
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }
    return next(action);
  };
}
Copy the code

2.5 story – promise

import isPromise from 'is-promise';

/ / Jane version
export default function promise({ dispatch }) {
  return next= > action= > (isPromise(action) ? action.then(dispatch)
    : next(action));
}
Copy the code

3. combineReducers

It is not possible to have only one Reducer in the project, at this point combineReducers are needed to merge all the Reducer into one Reducer.

3.1 usage

page

addTodo = () = > {
    store.dispatch({ type: 'ADD_TODO'.payload: ' world! ' });
}

<h2>ReduxPage</h2>
<h4>Count</h4>
<p>{store.getState().count}</p>

<h4 style={{ marginTop: '24px' }}>Todo</h4>
<p>{store.getState().todos}</p>
<button type="button" onClick={this.addTodo}>Add a todo</button>
Copy the code

store

import { applyMiddleware, createStore, combineReducers } from 'redux';

function todoReducer(state = ['hello'], action) {
  switch (action.type) {
  case 'ADD_TODO':
    return state.concat([action.payload]);
  default:
    returnstate; }}There is only one reducer
// const store = createStore(countReducer, applyMiddleware(thunk, logger, promise));

// Multiple reducer merge
const store = createStore(
  combineReducers({
    count: countReducer,
    todos: todoReducer
  }),
  applyMiddleware(thunk, logger, promise)
);
Copy the code

3.2 implementation

export default function combineReducers(reducers) {
  return function combination(state = {}, action) {
    const nextState = {};
    let hasChanged = false;
    Object.keys(reducers).forEach(key= > {
      constreducer = reducers[key]; nextState[key] = reducer(state[key], action); hasChanged = hasChanged || nextState[key] ! == state[key]; });// replaceReducer is provided in the source code.
    // replaceReducer => {a: 0, b: 1}
    // There is no change in the length of the two comparisons
    hasChanged = hasChanged || Object.keys(nextState).length ! = =Object.keys(state).length;
    return hasChanged ? nextState : state;
  };
} 
Copy the code

4.bindActionCreator

Use with React-redux, documentation

implementation

function bindActionCreator(actionCreator, dispatch) {
  return (. args) = >dispatch(actionCreator(... args)); }// Add dispatch to creators
// let creators = {
// add: () => ({ type: 'ADD' }),
// minus: () => ({ type: 'MINUS' })
// };
// creators = bindActionCreators(creators, dispatch);
export default function bindActionCreators(actionCreators, dispatch) {
  const boundActionCreators = {};
  Object.keys(actionCreators).forEach(key= > {
    const actionCreator = actionCreators[key];
    boundActionCreators[key] = bindActionCreator(actionCreator, dispatch);
  });
}
Copy the code

At the end

Warehouse address: github.com/claude-hub/… Star, o ~.