1.Redux应用场景 #

reduxflow

2.Redux设计思想 #

redux-flow

3.预备知识 #

3.1 redux全家桶 #

3.2 Context(上下文) #

contextapi

import React,{Component} from 'react';
import ReactDOM from 'react-dom';
let ThemeContext = React.createContext('theme');
class Child extends Component {
    render() {
        return (
             <ThemeContext.Consumer>
                {
                    value => (
                        <div style={{ border: `5px solid ${value.color}`, padding: 5 }}>
                          Child
                           <button onClick={() =>value.changeColor('red')} style={{color:'red'}}>红色</button>
                           <button onClick={() => value.changeColor('green')} style={{color:'green'}}>绿色</button>
                        </div>
                    )
                }
            </ThemeContext.Consumer>
        )
    }
}

class Father extends Component {
    constructor() {
        super();
        this.state = { color: 'red' };
    }
    changeColor = (color) => {
        this.setState({ color })
    }
    render() {
        let contextVal = {changeColor: this.changeColor,color:this.state.color };
        return (
            <ThemeContext.Provider value={contextVal}>
                <div style={{margin:'10px', border: `5px solid ${this.state.color}`, padding: 5, width: 200 }}>
                    page
                    <Child />
                </div>
            </ThemeContext.Provider>

        )
    }
}
ReactDOM.render(<Father />, document.querySelector('#root'));

3.3 useReducer #

const [state, dispatch] = useReducer(reducer, initialArg);
import React,{useReducer} from 'react';
import ReactDOM from 'react-dom';
const initialState = {number:0};
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
function reducer(state, action) {
  switch (action.type) {
    case INCREMENT:
      return {number: state.number + 1};
    case DECREMENT:
      return {number: state.number - 1};
    default:
      throw new Error();
  }
}
function Counter(){
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
        <>
          Count: {state.number}
          <button onClick={() => dispatch({type: INCREMENT})}>+</button>
          <button onClick={() => dispatch({type: DECREMENT})}>-</button>
        </>
    )
}
ReactDOM.render(<Counter />, document.querySelector('#root'));

3.4 compose #

composes

function add1(str){
    return '1'+str;
}
function add2(str){
    return '2'+str;
}
function add3(str){
    return '3'+str;
}

function compose(...funcs){
    return funcs.reduce((a,b)=>(...args)=>a(b(...args)));
}

let result = compose(add3,add2,add1)('zfpx');
console.log(result);

4.原版redux #

import React,{Component,useReducer} from 'react';
import ReactDOM from 'react-dom';
import {createStore} from 'redux';
import {Provider,connect} from 'react-redux';
const initialState = { number: 0 };
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";
function reducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { number: state.number + 1 };
    case DECREMENT:
      return { number: state.number - 1 };
    default:
      return state;
  }
}
let store = createStore(reducer);
function Counter(props) {
  return (
    <>
      <p>{props.number}</p>
      <button onClick={props.add}>+</button>
    </>
  );
}
let mapStateToProps = state => state;
let mapDispatchToProps = dispatch => ({
  add() {
    dispatch({ type: INCREMENT });
  },
  minus() {
    dispatch({ type: DECREMENT });
  }
});
let ConnectedCounter = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedCounter />
  </Provider>,
  document.querySelector("#root")
);

5.hooks版redux #

5.1 index.js #

import React,{Component,useReducer} from 'react';
import ReactDOM from 'react-dom';
- import {createStore} from 'redux';
- import {Provider,connect} from 'react-redux';
+ import { createStore } from "./redux";
const initialState = { number: 0 };
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";
function reducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { number: state.number + 1 };
    case DECREMENT:
      return { number: state.number - 1 };
    default:
      return state;
  }
}
-  let store = createStore(reducer);
+  let { store, Provider, connect } = createStore(reducer, initialState);
function Counter(props) {
  return (
    <>
      <p>{props.number}</p>
      <button onClick={props.add}>+</button>
    </>
  );
}
let mapStateToProps = state => state;
let mapDispatchToProps = dispatch => ({
  add() {
    dispatch({ type: INCREMENT });
  },
  minus() {
    dispatch({ type: DECREMENT });
  }
});
let ConnectedCounter = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedCounter />
  </Provider>,
  document.querySelector("#root")
);

5.2 index.js #

src\redux\index.js

import React from "react";
const Context = React.createContext();
export function createStore(reducer, initialState) {
  let store = {};
  const Provider = props => {
    const [state, dispatch] = React.useReducer(reducer, initialState);
    store.getState = () => {
      return state;
    };
    store.dispatch = dispatch;
    return (
      <Context.Provider value={state}>
        {React.cloneElement(props.children)}
      </Context.Provider>
    );
  };

  function connect(mapStatetoProps,mapDispatchToProps) {
    return function(Component) {
      let state = initialState;
      let actions ={};
      return props => {
        if (store.getState) state = mapStatetoProps(store.getState());
        actions = mapDispatchToProps(store.dispatch);
        return <Component {...state} {...props} dispatch={store.dispatch} {...actions}/>;
      };
    };
  }
  return { store, connect, Provider };
}

6.hooks版中间件 #

middlewares

6.1 index.js #

import React, { Component, useReducer } from "react";
import ReactDOM from "react-dom";
/* import {createStore} from 'redux';
import {Provider,connect} from 'react-redux'; */
import { createStore,applyMiddleware } from "./redux";
const initialState = { number: 0 };
const INCREMENT = "INCREMENT";
const DECREMENT = "DECREMENT";
function reducer(state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return { number: state.number + 1 };
    case DECREMENT:
      return { number: state.number - 1 };
    default:
      return state;
  }
}
+ let logger = store=>next=>action=>{
+   console.log(`%c prev state`,`color: #a3a3a3; font-weight: bold`,store.getState());
+   console.log(`%c action`,`color: #7fbedf; font-weight: bold`,store.getState());
+   next(action);
+   console.log(`%c next state`,`color: #9cd69b; font-weight: bold`,store.getState());
+ }
+ let thunk = store=>next=>action=>{
+  if(typeof action === 'function'){
+    return action(store.dispatch,store.getState);
+  }
+  return next(action);
+ }
+ let promise = store=>next=>action=>{
+   if(action.then){
+     return action.then(store.dispatch);
+   }
+   return next(action);
+ }
+ //let { store, Provider, connect } = createStore(reducer, initialState);
+ let { store, Provider, connect } = applyMiddleware(thunk,promise,logger)(createStore)(reducer, initialState);
function Counter(props) {
  return (
    <>
      <p>{props.number}</p>
      <button onClick={props.add}>+</button>
+       <button onClick={()=>store.dispatch(function(dispatch,getState){
+             setTimeout(() => {
+               dispatch({type:INCREMENT});
+             }, 1000);
+       })}>异步+1</button>
+       <button onClick={()=>store.dispatch(new Promise(function(resolve,reject){
+               setTimeout(() => {
+                 resolve({type:INCREMENT});
+               }, 1000);
+       }))}>Promise+1</button>
+     </>
+   );
}
let mapStateToProps = state => state;
let mapDispatchToProps = dispatch => ({
  add() {
    dispatch({ type: INCREMENT });
  },
  minus() {
    dispatch({ type: DECREMENT });
  }
});
let ConnectedCounter = connect(
  mapStateToProps,
  mapDispatchToProps
)(Counter);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedCounter />
  </Provider>,
  document.querySelector("#root")
);

6.2 redux #

import React from "react";
const Context = React.createContext();
+ function compose(...funcs) {
+   return funcs.reduce((a, b) => (...args) => a(b(...args)));
+ }
+ export function applyMiddleware(...middlewares){
+     return createStore => (...args)=>{
+         const { store, connect, Provider } = createStore(...args);
+         let dispatch;
+         const middlewareAPI= {
+          getState:()=>store.getState(),
+          dispatch:(...args)=>dispatch(...args)
+         }
+         const chain = middlewares.map(middleware=>middleware(middlewareAPI));
+         dispatch = compose(...chain)((...args)=>store._dispatch(...args));
+         store.dispatch = dispatch;
+         return {
+            store, connect, Provider
+         }
+     }
+ }
export function createStore(reducer, initialState) {
  let store = {};
  const Provider = props => {
    const [state, dispatch] = React.useReducer(reducer, initialState);
    store.getState = () => {
      return state;
    };
+    store._dispatch = dispatch;
    return (
      <Context.Provider value={state}>
        {React.cloneElement(props.children)}
      </Context.Provider>
    );
  };

  function connect(mapStatetoProps,mapDispatchToProps) {
    return function(Component) {
      let state = initialState;
      let actions ={};
+       return props => {
+         if (store.getState) state = mapStatetoProps(store.getState());
+         actions = mapDispatchToProps(store.dispatch);
+         return <Component {...state} {...props} dispatch={store.dispatch} {...actions}/>;
+       };
    };
  }
  return { store, connect, Provider };
}