directory

  • preface
  • Initialize the project
    • 1. Install scaffolding globally
    • 2. Create projects
    • 3. Project Catalog
  • Implement Redux basic functionality
    • 1. The story
    • 2. Use it with React
  • Implement the React – story
    • 1.context
    • 2.react-readux
      • To realize the Provider
      • To realize the connect
  • Implement the Redux middleware mechanism
    • Implement applyMiddleware
    • Implement redux middleware
    • Add multiple middleware processes

preface

Redux, as the React state management tool, has become indispensable for large-scale application development. In order to better understand the entire implementation mechanism of Redux, we decided to implement a Redux with basic functions from scratch

Project address welcome star/fork

preview

Initialize the project

1. Install scaffolding globally
npm install -g create-react-appCopy the code
2. Create projects
create-react-app mini-reduxCopy the code
3. Project Catalog
├─ ├─ public │ ├─ favicon │ ├─ └.html │ └ ─ ─ the manifest. Json └ ─ ─ the SRC └ ─ ─ App. CSS └ ─ ─ App. Js └ ─ ─ App. Test, js └ ─ ─ index. The CSS └ ─ ─ index. The js └ ─ ─ logo. The SVG └ ─ ─ registerServiceWorker.jsCopy the code

Implement Redux basic functionality

1. The story

Create ~/ SRC /mini-redux/mini-redux.js, redux will expose a createStore method and accept reducer as the reducer parameter

export function createStore(reducer) {
  let currentState = {}
  let currentListeners = []

  function getState() {
    return currentState
  }
  function subscribe(listener) {
    currentListeners.push(listener)
  }
  function dispatch(action) {
    currentState = reducer(currentState, action)
    currentListeners.forEach(v= > v())
    return action
  }
  dispatch({type: '@REACT_FIRST_ACTION'})  // Initialize state
  return { getState, subscribe, dispatch}
}Copy the code

Now that we have implemented the basic functions of Redux, let’s call our implementation of Mini-Redux to check whether it meets expectations. New ~ / SRC/index. Redux. Js

import { createStore } from './mini-redux/mini-redux'

const ADD = 'ADD'
const REMOVE = 'REMOVE'

// reducer
export function counter(state=0, action) {
  switch (action.type) {
    case ADD:
        return state + 1
    case REMOVE:
        return state - 1
    default:
        return 10}}export function add() {
  return {type: 'ADD'}}export function remove() {
  return {type: 'REMOVE'}}const store = createStore(counter)
const init = store.getState()
console.log('Start value:${init}`)

function listener(){
  const current = store.getState()
  console.log('Current value:${current}`)}// The listener is executed each time state is changed
store.subscribe(listener)
// Submit a request for a status change
store.dispatch({ type: 'ADD' })
store.dispatch({ type: 'ADD' })
store.dispatch({ type: 'REMOVE' })
store.dispatch({ type: 'REMOVE' })Copy the code

Import the above file in index.js for execution and view the console to see the following log information

Starting value:10       index.redux.js:27Current value:11       index.redux.js:31Current value:12       index.redux.js:31Current value:11       index.redux.js:31Current value:10       index.redux.js:31Copy the code

So far, we’ve implemented Redux, but it’s far from what we expected because we need to use react with it

2. Use it with React

Use the react and mini-react components together, and modify index.redux.js as follows

const ADD = 'ADD'
const REMOVE = 'REMOVE'

// reducer
export function counter(state=0, action) {
  switch (action.type) {
    case ADD:
        return state + 1
    case REMOVE:
        return state - 1
    default:
        return 10}}export function add() {
  return {type: 'ADD'}}export function remove() {
  return {type: 'REMOVE'}}Copy the code

The index.js file initializes redux

import { createStore } from './mini-redux/mini-redux'
import { counter } from './index.redux'

// Initialize redux
const store = createStore(counter)

function render() {
  ReactDOM.render(<App store={store} />, document.getElementById('root')); Subscribe (render); // Subscribe (render)Copy the code

From the app.js file we can call redux

import {add, remove} from './index.redux'

class App extends Component {
    render() {
        const store = this.props.store
        // Get the current value
        const num = store.getState()
        return (
            <div className="App">
                <p>The initial value is {num}</p>
                <button onClick={()= > store.dispatch(add())}>Add</button>
                <button onClick={()= > store.dispatch(remove())}>Remove</button>
            </div>); }}export default App;Copy the code

As shown above, we can change the mini-Redux state in the React component

Implement the React – story

We’ve already implemented Redux and can use it in conjunction with React. However, the React link is cumbersome and highly coupled, and would not be used in daily development. We’ll use the React-Redux library to connect to React(check out this blog post if you don’t know about React-Redux). Let’s implement a simple react-Redux

1.context

Before implementing react-redux, we need to know the react context. The implementation of react- Redux uses the context mechanism. Here is an example of how to use context.

New ~ / SRC/mini – story/context. Test. Js

import React from 'react'
import PropTypes from 'prop-types'
// Context is global, declared in the component, and all child elements can be retrieved directly

class Sidebar extends React.Component {
  render(){
    return (
      <div>
        <p>Sidebar</p>
        <Navbar></Navbar>
      </div>)}}class Navbar extends React.Component {
  // Restrict type, must
  static contextTypes = {
    user: PropTypes.string
  }
  render() {
    console.log(this.context)
    return (
      <div>{this.context.user} Navbar</div>)}}class Page extends React.Component {
  // Restrict type, must
  static childContextTypes = {
    user: PropTypes.string
  }
  constructor(props){
    super(props)
    this.state = {user: 'Jack'}
  }
  getChildContext() {
    return this.state
  }
  render() {
    return (
      <div>
        <p>I am {this. State. User}</p>
        <Sidebar/>
      </div>)}}export default PageCopy the code

2.react-readux

React-redux has two common components: connect and Provider. Connect is used to fetch data from redux (state and Action). Provider is used to place store in context. Make all child elements available to store. Implement connect and Provider, respectively

To realize the Provider

Create ~/ SRC /mini-redux/mini-react-redux

import React from 'react'
import PropTypes from 'prop-types'


// Put store in context, all child elements can be taken directly to store
export class Provider extends React.Component{
  // Restrict the data type
    static childContextTypes = {
    store: PropTypes.object
  }
  getChildContext(){
    return { store:this.store }
  }
  constructor(props, context){
    super(props, context)
    this.store = props.store
  }
  render(){
    // Return all child elements
    return this.props.children
  }
}Copy the code

To verify that the Provider works as expected, modify the ~/ SRC /index.js file as follows

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

import { createStore } from './mini-redux/mini-redux'
import { Provider } from './mini-redux/mini-react-redux'
import { counter } from './index.redux'

const store = createStore(counter)

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

Finally, we need to change the method of obtaining store data in ~/ SRC/app. js file to use connect. However, since connect is not implemented yet, we will use the connect component of the original React-Redux to verify the Provider implemented above

import React, { Component } from 'react';
import { connect } from 'react-redux'
import {add, remove} from './index.redux'

class App extends Component {
    render() {
        return (
            <div className="App">
                <p>The initial value is {this.props. Num}</p>
                <button onClick={this.props.add}>Add</button>
                <button onClick={this.props.remove}>Remove</button>
            </div>
        );
    }
}

App = connect(state= > ({num: state}), {add, remove})(App)

export default App;Copy the code

Verify that the Provider successfully connects to connect

To realize the connect

We implemented above the Provider, but the connect is still use the original react – redux connect, will come here in ~ / SRC/mini – story/mini – react – redux. Add a connect method js file

import React from 'react'
import PropTypes from 'prop-types'
import {bindActionCreators} from './mini-redux'

// Connect is responsible for linking components and putting data to redux into component properties
// 1. Accept a component, put some data in state, return a component
// 2. Notify components when data changes

export const connect = (mapStateToProps = state=>state, mapDispatchToProps = {}) = > (WrapComponent) => {
  return class ConnectComponent extends React.Component{
    static contextTypes = {
      store:PropTypes.object
    }
    constructor(props, context){
      super(props, context)
      this.state = {
        props:{}
      }
    }
    componentDidMount(){
      const {store} = this.context
      store.subscribe((a)= >this.update())
      this.update()
    }
    update(){
      // Put mapStateToProps and mapDispatchToProps in this. Props
      const {store} = this.context
      const stateProps = mapStateToProps(store.getState())
      // The method cannot be given directly because dispatch is required
      const dispatchProps = bindActionCreators(mapDispatchToProps, store.dispatch)
      this.setState({
        props: {... this.state.props, ... stateProps, ... dispatchProps } }) } render(){return <WrapComponent {. this.state.props} ></WrapComponent>}}}Copy the code

In the code above, we also need to add a bindActionCreators method to mini-redux.js to wrap the actionCreator method with the Dispatch package, as shown below

. function bindActionCreator(creator, dispatch){return (. args) = >dispatch(creator(... args)) }export function bindActionCreators(creators,dispatch){
  let bound = {}
  Object.keys(creators).forEach(v= >{
    let creator = creators[v]
    bound[v] = bindActionCreator(creator, dispatch)
  })
  return bound
}
......Copy the code

Finally, replace the connect in ~/ SRC/app.js with the connect completed above to complete the test

import { connect } from './mini-redux/mini-react-redux'Copy the code

Implement the Redux middleware mechanism

Implement applyMiddleware

While we typically use Redux to extend redux functionality with a variety of middleware, such as redux-Thunk for asynchronous submission of actions, let’s add a middleware mechanism to our Mini-Redux

Modify the ~/ SRC /mini-redux/mini-redux.js code as follows

export function createStore(reducer, enhancer) {
  if (enhancer) {
    return enhancer(createStore)(reducer)
  }

  let currentState = {}
  let currentListeners = []

  function getState() {
    return currentState
  }
  function subscribe(listener) {
    currentListeners.push(listener)
  }
  function dispatch(action) {
    currentState = reducer(currentState, action)
    currentListeners.forEach(v= > v())
    return action
  }
  // Initialize state
  dispatch({type: '@REACT_FIRST_ACTION'})
  return { getState, subscribe, dispatch}
}

export function applyMiddleware(middleware) {
  return createStore= >(... args) => {conststore = createStore(... args)let dispatch = store.dispatch

    const midApi = {
      getState: store.getState,
      dispatch: (. args) = >dispatch(... args) } dispatch = middleware(midApi)(store.dispatch)return {
      ...store,
      dispatch
    }

  } 
}
......Copy the code

Now that we have added the middleware mechanism to Mini-Redux, let’s use the middleware for verification. Since we don’t have our own middleware, we now use redux-thunk to implement an asynchronous commit action

Modify the ~ / SRC/index. Js

. import { createStore, applyMiddleware }from './mini-redux/mini-redux'
import thunk from 'redux-thunk'

const store = createStore(counter, applyMiddleware(thunk))
......Copy the code

Modify ~/ SRC /index.redux.js to add an asynchronous method

export function addAsync() {
    return dispatch= > {
    setTimeout((a)= > {
        dispatch(add());
    }, 2000);
  };
}Copy the code

Finally, we will introduce the asynchronous method in ~/ SRC/app.js, modified as follows

. import React, { Component }from 'react';
import { connect } from './mini-redux/mini-react-redux'
import {add, remove, addAsync} from './index.redux'

class App extends Component {
    render() {
        return (
            <div className="App">
                <p>The initial value is {this.props. Num}</p>
                <button onClick={this.props.add}>Add</button>
                <button onClick={this.props.remove}>Remove</button>
                <button onClick={this.props.addAsync}>AddAsync</button>
            </div>
        );
    }
}

App = connect(state= > ({num: state}), {add, remove, addAsync})(App)
export default App;Copy the code

And then you can verify it

Implement redux middleware

We used the Redux-Thunk middleware above, so why not write your own

New ~ / SRC/mini – story/mini – story – thunk. Js

const thunk = ({dispatch, getState}) = > next => action= > {
  // If it is a function, execute it with the arguments dispatch and getState
  if (typeof action=='function') {
    return action(dispatch,getState)
  }
  // By default, nothing is done,
  return next(action)
}
export default thunkCopy the code

Replace thunk in ~/ SRC /index.js with thunk implemented above to see if the program still works correctly

On the basis of the above, we will implement an arrThunk middleware, which provides the function of submitting an action array

New ~ / SRC/mini – story/mini – story – arrayThunk. Js

const arrayThunk = ({dispatch,getState}) = >next= >action= >{
  if (Array.isArray(action)) {
    return action.forEach(v= >dispatch(v))
  }
  return next(action)
}
export default arrayThunkCopy the code

Add multiple middleware processes

The middleware mechanism we implemented above allows only one middleware to be added, which is not sufficient for our daily development needs

Modify the ~/ SRC /mini-redux/mini-redux.js file

.// Receive middleware
export function applyMiddleware(. middlewares) {
  return createStore= >(... args) => {conststore = createStore(... args)let dispatch = store.dispatch

    const midApi = {
      getState: store.getState,
      dispatch: (. args) = >dispatch(... args) }const middlewareChain = middlewares.map(middleware= >middleware(midApi)) dispatch = compose(... middlewareChain)(store.dispatch)return {
      ...store,
      dispatch
    }

  } 
}
// compose(fn1,fn2,fn3) ==> fn1(fn2(fn3))
export function compose(. funcs){
  if (funcs.length==0) {
    return arg= >arg
  }
  if (funcs.length==1) {
    return funcs[0]}return funcs.reduce((ret,item) = >(... args)=>ret(item(... args))) } ......Copy the code

Finally, we will use the two middleware thunk and arrThunk at the same time to see whether the above realization of multi-middleware merge is completed

Modify the ~ / SRC/index. Js

. import arrThunkfrom './mini-redux/mini-redux-arrThunk'
const store = createStore(counter, applyMiddleware(thunk, arrThunk))
...Copy the code

Add an addTwice Action generator in ~/ SRC /index.redux.js

. exportfunction addTwice() {
  return [{type: 'ADD'}, {type: 'ADD'}}]...Copy the code

Add an addTwice button in ~/ SRC/app.js to modify the corresponding code

import {add, remove, addAsync, addTwice} from './index.redux'

class App extends Component {
    render() {
        return (
            <div className="App">
                <p>now num is {this.props.num}</p>
                <button onClick={this.props.add}>Add</button>
                <button onClick={this.props.remove}>Remove</button>
                <button onClick={this.props.addAsync}>AddAsync</button>
                <button onClick={this.props.addTwice}>addTwice</button>
            </div>
        );
    }
}

App = connect(state= > ({num: state}), {add, remove, addAsync, addTwice})(App)Copy the code

And you’re done!