Skip to content

Redux

一、基础

概述

JavaScript 状态容器,提供可预测化的状态管理。

js
const state = {
  modelOpen: 'yes',
  btnClicked: 'no',
  btnActiveClass: 'active',
  page: 5,
  size: 10
}
const state = {
  modelOpen: 'yes',
  btnClicked: 'no',
  btnActiveClass: 'active',
  page: 5,
  size: 10
}

核心概念及工作流程

Store:存储状态的容器,JavaScript 对象

View:视图,HTML 页面

Actions:对象,描述对状态进行怎样的操作

Reducers:函数,操作状态并返回新的状态

redux 计数器案例

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux</title>
</head>
<body>

  <button id="J-plus">+</button>
  <span id="J-count">0</span>
  <button id="J-minus">-</button>


  <script src="https://cdn.bootcdn.net/ajax/libs/redux/4.1.0/redux.min.js"></script>

  <script>
    // 3. 存储默认状态
    const initialState = {
      count: 0
    };

    // 2. 创建 reducer 函数
    function reducer (state = initialState, action) {
      switch (action.type) {
        case 'increment':
          return {
            count: state.count + 1
          }
        case 'decrement':
          return {
            count: state.count - 1
          }
        default:
          return state;
      }
    }

    // 1. 创建 store 对象
    const store = Redux.createStore(reducer);
    
    // 4. 定义 action
    const increment = { type: 'increment' };
    const decrement = { type: 'decrement' };

    // 5. 获取按钮,添加点击事件
    document.getElementById('J-plus').onclick = function () {
      // 6. 触发 action
      store.dispatch(increment);
    }
    document.getElementById('J-minus').onclick = function () {
      // 触发 action
      store.dispatch(decrement);
    }

    // 7. 订阅 store
    store.subscribe(() => {
      document.getElementById('J-count').innerHTML = store.getState().count;
    });
    
    console.log(store); // dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, @@observable: ƒ}
    console.log(store.getState()); // {count: 0}

  </script>
  
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux</title>
</head>
<body>

  <button id="J-plus">+</button>
  <span id="J-count">0</span>
  <button id="J-minus">-</button>


  <script src="https://cdn.bootcdn.net/ajax/libs/redux/4.1.0/redux.min.js"></script>

  <script>
    // 3. 存储默认状态
    const initialState = {
      count: 0
    };

    // 2. 创建 reducer 函数
    function reducer (state = initialState, action) {
      switch (action.type) {
        case 'increment':
          return {
            count: state.count + 1
          }
        case 'decrement':
          return {
            count: state.count - 1
          }
        default:
          return state;
      }
    }

    // 1. 创建 store 对象
    const store = Redux.createStore(reducer);
    
    // 4. 定义 action
    const increment = { type: 'increment' };
    const decrement = { type: 'decrement' };

    // 5. 获取按钮,添加点击事件
    document.getElementById('J-plus').onclick = function () {
      // 6. 触发 action
      store.dispatch(increment);
    }
    document.getElementById('J-minus').onclick = function () {
      // 触发 action
      store.dispatch(decrement);
    }

    // 7. 订阅 store
    store.subscribe(() => {
      document.getElementById('J-count').innerHTML = store.getState().count;
    });
    
    console.log(store); // dispatch: ƒ, subscribe: ƒ, getState: ƒ, replaceReducer: ƒ, @@observable: ƒ}
    console.log(store.getState()); // {count: 0}

  </script>
  
</body>
</html>

react 中 redux 解决的问题

在 React 中组件通信的数据流是单向的,顶层组件可以通过 props 属性向下层组件传递数据,而下层组件不能向上层传递数据,要实现下层组件修改数据,需要上层组件传递修改数据的方法到下层组件。当项目越来越大时,组件之间传递数据变得越来越困难。

使用 Redux 管理数据,由于 Store 独立于组件,使得数据管理独立于组件,解决了组件与组件之间传递数据困难的问题。

react 计数器

redux 工作流程

组件通过 dispatch 方法触发 Action。

Store 接收 Action 并将 Action 分发给 Reducer。

Reducer 根据 Action 类型对状态进行更改并将更改后的状态返回给 Store。

组件订阅 Store 状态,Store 中的状态更新会同步到组件。

安装

jsx
npm i redux react-redux
npm i redux react-redux

基本实现

jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';

const initialState = {
  count: 0
}

function reducer (state = initialState, action) {
  switch (action.type) {
    case 'increment':
      return {
        count: state.count + 1
      };
    case 'decrement':
      return {
        count: state.count -1
      };
    default:
      return state;
  }
}

const store = createStore(reducer);

const increment = { type: 'increment' };
const decrement = { type: 'decrement' };


function Counter () {
  return (
    <div>
      <button onClick={() => store.dispatch(increment)}>+</button>
      <span>{ store.getState().count }</span>
      <button onClick={() => store.dispatch(decrement)}>-</button>
    </div>
  )
}

store.subscribe(() => {
  ReactDOM.render(
    <Counter />,
    document.getElementById('root')
  );
});

ReactDOM.render(
  <Counter />,
  document.getElementById('root')
);
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';

const initialState = {
  count: 0
}

function reducer (state = initialState, action) {
  switch (action.type) {
    case 'increment':
      return {
        count: state.count + 1
      };
    case 'decrement':
      return {
        count: state.count -1
      };
    default:
      return state;
  }
}

const store = createStore(reducer);

const increment = { type: 'increment' };
const decrement = { type: 'decrement' };


function Counter () {
  return (
    <div>
      <button onClick={() => store.dispatch(increment)}>+</button>
      <span>{ store.getState().count }</span>
      <button onClick={() => store.dispatch(decrement)}>-</button>
    </div>
  )
}

store.subscribe(() => {
  ReactDOM.render(
    <Counter />,
    document.getElementById('root')
  );
});

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

Provider 组件与 connect 方法

connect 方法:

  • 订阅 store ,状态改变时重新渲染组件
  • 获取 store 状态,将状态映射到组件的 props
  • 获取 dispatch 方法

components/Counter.js

jsx
import React from "react"
import { connect } from 'react-redux';

function Counter ({ count, dispatch }) {
  return (
    <div>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <span>{ count }</span>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.count
})

export default connect(mapStateToProps)(Counter);
import React from "react"
import { connect } from 'react-redux';

function Counter ({ count, dispatch }) {
  return (
    <div>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <span>{ count }</span>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.count
})

export default connect(mapStateToProps)(Counter);

index.js

jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import Counter from './components/Counter';

const initialState = {
  count: 0
}

function reducer (state = initialState, action) {
  switch (action.type) {
    case 'increment':
      return {
        count: state.count + 1
      };
    case 'decrement':
      return {
        count: state.count -1
      };
    default:
      return state;
  }
}

const store = createStore(reducer);

const increment = { type: 'increment' };
const decrement = { type: 'decrement' };

ReactDOM.render(
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import Counter from './components/Counter';

const initialState = {
  count: 0
}

function reducer (state = initialState, action) {
  switch (action.type) {
    case 'increment':
      return {
        count: state.count + 1
      };
    case 'decrement':
      return {
        count: state.count -1
      };
    default:
      return state;
  }
}

const store = createStore(reducer);

const increment = { type: 'increment' };
const decrement = { type: 'decrement' };

ReactDOM.render(
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);

使用 connect 方法的第二个参数

jsx
import React from "react"
import { connect } from 'react-redux';

function Counter ({ count, increment, decrement }) {
  return (
    <div>
      <button onClick={ increment }>+</button>
      <span>{ count }</span>
      <button onClick={ decrement }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.count
});

const mapDispatchToProps = dispatch => ({
  increment () {
    dispatch({ type: 'increment' })
  },
  decrement () {
    dispatch({ type: 'decrement' })
  }
});

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
import React from "react"
import { connect } from 'react-redux';

function Counter ({ count, increment, decrement }) {
  return (
    <div>
      <button onClick={ increment }>+</button>
      <span>{ count }</span>
      <button onClick={ decrement }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.count
});

const mapDispatchToProps = dispatch => ({
  increment () {
    dispatch({ type: 'increment' })
  },
  decrement () {
    dispatch({ type: 'decrement' })
  }
});

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

bindActionCreator 方法

store/actions/counter.js

js
export const increment = () => ({ type: 'increment' });
export const decrement = () => ({ type: 'decrement' });
export const increment = () => ({ type: 'increment' });
export const decrement = () => ({ type: 'decrement' });

components/Counter.js

js
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement }) {
  return (
    <div>
      <button onClick={ increment }>+</button>
      <span>{ count }</span>
      <button onClick={ decrement }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement }) {
  return (
    <div>
      <button onClick={ increment }>+</button>
      <span>{ count }</span>
      <button onClick={ decrement }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

代码重构 - 代码拆分

store/const/counter.js

js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';

store/actions/counter.js

js
import { INCREMENT, DECREMENT } from "../const/counter";

export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });
import { INCREMENT, DECREMENT } from "../const/counter";

export const increment = () => ({ type: INCREMENT });
export const decrement = () => ({ type: DECREMENT });

store/reducers/counter.js

js
import { INCREMENT, DECREMENT } from "../const/counter";

const initialState = {
  count: 0
}

export function reducer (state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        count: state.count + 1
      };
    case DECREMENT:
      return {
        count: state.count -1
      };
    default:
      return state;
  }
}
import { INCREMENT, DECREMENT } from "../const/counter";

const initialState = {
  count: 0
}

export function reducer (state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        count: state.count + 1
      };
    case DECREMENT:
      return {
        count: state.count -1
      };
    default:
      return state;
  }
}

store/index.js

js
import { createStore } from 'redux';
import { reducer } from './reducers/couner';

export const store = createStore(reducer);
import { createStore } from 'redux';
import { reducer } from './reducers/couner';

export const store = createStore(reducer);

index.js

jsx
import React from 'react';
import ReactDOM from 'react-dom';

import { Provider } from 'react-redux';
import Counter from './components/Counter';

import { store } from './store';

ReactDOM.render(
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);
import React from 'react';
import ReactDOM from 'react-dom';

import { Provider } from 'react-redux';
import Counter from './components/Counter';

import { store } from './store';

ReactDOM.render(
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);

action 传递参数

使用方法

传递参数

jsx
<button onClick={ () => increment(5) }>+</button>
<button onClick={ () => increment(5) }>+</button>

接收参数,传递 reducer

js
export const increment = payload = ({ type: INCREMENT, payload });
export const increment = payload = ({ type: INCREMENT, payload });

reducer 根据接收到的数据进行处理

js
export default (state, action) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + action.payload };
  }
}
export default (state, action) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + action.payload };
  }
}

代码改造

component/Counter.js

js
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement }) {
  return (
    <div>
      <button onClick={ () => increment(5) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement }) {
  return (
    <div>
      <button onClick={ () => increment(5) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

store/actions/actions/counter.js

js
import { INCREMENT, DECREMENT } from "../const/counter";

export const increment = payload => ({ type: INCREMENT, payload });
export const decrement = payload => ({ type: DECREMENT, payload });
import { INCREMENT, DECREMENT } from "../const/counter";

export const increment = payload => ({ type: INCREMENT, payload });
export const decrement = payload => ({ type: DECREMENT, payload });

store/action/reducers/counter.js

js
import { INCREMENT, DECREMENT } from "../const/counter";

const initialState = {
  count: 0
}

export function reducer (state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        count: state.count + action.payload
      };
    case DECREMENT:
      return {
        count: state.count - action.payload
      };
    default:
      return state;
  }
}
import { INCREMENT, DECREMENT } from "../const/counter";

const initialState = {
  count: 0
}

export function reducer (state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        count: state.count + action.payload
      };
    case DECREMENT:
      return {
        count: state.count - action.payload
      };
    default:
      return state;
  }
}

redux 弹出框案例

components/Modal.js

js
import React from "react";
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as modalActions from '../store/actions/modal';

function Modal ({ showStatus, show, hide }) {
  const styles = {
    width: 200,
    height: 200,
    position: 'absolute',
    left: '50%',
    top: '50%',
    marginLeft: -100,
    marginTop: -100,
    background: 'orange',
    display: showStatus ? 'block' : 'none'
  };

  return (
    <div>
      <button onClick={ show }>显示</button>
      <button onClick={ hide }>隐藏</button>
      <div style={styles}></div>
    </div>
  )
}

const mapStateToProps = state => ({
  showStatus: state.show
});

const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Modal);
import React from "react";
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as modalActions from '../store/actions/modal';

function Modal ({ showStatus, show, hide }) {
  const styles = {
    width: 200,
    height: 200,
    position: 'absolute',
    left: '50%',
    top: '50%',
    marginLeft: -100,
    marginTop: -100,
    background: 'orange',
    display: showStatus ? 'block' : 'none'
  };

  return (
    <div>
      <button onClick={ show }>显示</button>
      <button onClick={ hide }>隐藏</button>
      <div style={styles}></div>
    </div>
  )
}

const mapStateToProps = state => ({
  showStatus: state.show
});

const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Modal);

store/actions/modal.js

js
import { HIDE_MODAL, SHOW_MODAL } from "../const/modal";

export const show = () => ({ type: SHOW_MODAL });
export const hide = () => ({ type: HIDE_MODAL });
import { HIDE_MODAL, SHOW_MODAL } from "../const/modal";

export const show = () => ({ type: SHOW_MODAL });
export const hide = () => ({ type: HIDE_MODAL });

store/const/modal.js

js
export const SHOW_MODAL = 'showModal';
export const HIDE_MODAL = 'hideModal';
export const SHOW_MODAL = 'showModal';
export const HIDE_MODAL = 'hideModal';

store/reducers/counter.js

js
import { INCREMENT, DECREMENT } from "../const/counter";
import { HIDE_MODAL, SHOW_MODAL } from "../const/modal";

const initialState = {
  count: 0,
  show: false
}

export function reducer (state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + action.payload
      };
    case DECREMENT:
      return {
        ...state,
        count: state.count - action.payload
      };
    case SHOW_MODAL: 
      return {
        ...state,
        show: true
      };
    case HIDE_MODAL:
      return {
        ...state,
        show: false
      };
    default:
      return state;
  }
}
import { INCREMENT, DECREMENT } from "../const/counter";
import { HIDE_MODAL, SHOW_MODAL } from "../const/modal";

const initialState = {
  count: 0,
  show: false
}

export function reducer (state = initialState, action) {
  switch (action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + action.payload
      };
    case DECREMENT:
      return {
        ...state,
        count: state.count - action.payload
      };
    case SHOW_MODAL: 
      return {
        ...state,
        show: true
      };
    case HIDE_MODAL:
      return {
        ...state,
        show: false
      };
    default:
      return state;
  }
}

App.js

jsx
import React from "react";
import Counter from './components/Counter';
import Modal from './components/Modal';

function App () {
  return (
    <div>
      <Counter />
      <Modal />
    </div>
  )
}

export default App;
import React from "react";
import Counter from './components/Counter';
import Modal from './components/Modal';

function App () {
  return (
    <div>
      <Counter />
      <Modal />
    </div>
  )
}

export default App;

index.js

jsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './store';

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './store';

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

拆分合并 reducer

components/Counter.js

jsx
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement }) {
  return (
    <div>
      <button onClick={ () => increment(5) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count // edit
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement }) {
  return (
    <div>
      <button onClick={ () => increment(5) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count // edit
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

components/Modal.js

jsx
import React from "react";
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as modalActions from '../store/actions/modal';

function Modal ({ showStatus, show, hide }) {
  const styles = {
    width: 200,
    height: 200,
    position: 'absolute',
    left: '50%',
    top: '50%',
    marginLeft: -100,
    marginTop: -100,
    background: 'orange',
    display: showStatus ? 'block' : 'none'
  };

  return (
    <div>
      <button onClick={ show }>显示</button>
      <button onClick={ hide }>隐藏</button>
      <div style={styles}></div>
    </div>
  )
}

const mapStateToProps = state => ({
  showStatus: state.modal.show // edit
});

const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Modal);
import React from "react";
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as modalActions from '../store/actions/modal';

function Modal ({ showStatus, show, hide }) {
  const styles = {
    width: 200,
    height: 200,
    position: 'absolute',
    left: '50%',
    top: '50%',
    marginLeft: -100,
    marginTop: -100,
    background: 'orange',
    display: showStatus ? 'block' : 'none'
  };

  return (
    <div>
      <button onClick={ show }>显示</button>
      <button onClick={ hide }>隐藏</button>
      <div style={styles}></div>
    </div>
  )
}

const mapStateToProps = state => ({
  showStatus: state.modal.show // edit
});

const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Modal);

store/reducers/counter.js

js
import { INCREMENT, DECREMENT } from "../const/counter";

const initialState = {
  count: 0
}

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + action.payload
      };
    case DECREMENT:
      return {
        ...state,
        count: state.count - action.payload
      };
    default:
      return state;
  }
};

export default counterReducer;
import { INCREMENT, DECREMENT } from "../const/counter";

const initialState = {
  count: 0
}

const counterReducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return {
        ...state,
        count: state.count + action.payload
      };
    case DECREMENT:
      return {
        ...state,
        count: state.count - action.payload
      };
    default:
      return state;
  }
};

export default counterReducer;

store/reducers/modal.js

js
import { HIDE_MODAL, SHOW_MODAL } from "../const/modal";

const initialState = {
  show: false
}

const modalReducer = (state = initialState, action) => {
  switch (action.type) {
    case SHOW_MODAL: 
      return {
        ...state,
        show: true
      };
    case HIDE_MODAL:
      return {
        ...state,
        show: false
      };
    default:
      return state;
  }
};

export default modalReducer;
import { HIDE_MODAL, SHOW_MODAL } from "../const/modal";

const initialState = {
  show: false
}

const modalReducer = (state = initialState, action) => {
  switch (action.type) {
    case SHOW_MODAL: 
      return {
        ...state,
        show: true
      };
    case HIDE_MODAL:
      return {
        ...state,
        show: false
      };
    default:
      return state;
  }
};

export default modalReducer;

store/reducers/index.js

js
import { combineReducers } from 'redux';
import CounterReducer from './counter';
import ModalReducer from './modal';

export default combineReducers({
  counter: CounterReducer,
  modal: ModalReducer
});
import { combineReducers } from 'redux';
import CounterReducer from './counter';
import ModalReducer from './modal';

export default combineReducers({
  counter: CounterReducer,
  modal: ModalReducer
});

store/index.js

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

export const store = createStore(reducers);
import { createStore } from 'redux';
import reducers from './reducers';

export const store = createStore(reducers);

中间件概念

中间件本质是一个函数,redux 允许我们通过中间件的方式扩展和增强 redux 应用程序。

开发 Redux 中间件

使用方法

模板代码

js
export default store => next => action => {};
export default store => next => action => {};

注册中间件

js
import { } from 'redux';
import logger from './middlewares/logger';

createStore(reducer, applyMiddleware(
  logger
));
import { } from 'redux';
import logger from './middlewares/logger';

createStore(reducer, applyMiddleware(
  logger
));

案例

store/middleware/logger.js

jsx
const logger = store => next => action => {
  console.log(action, store);
  
  next(action);
}; 

export default logger;
const logger = store => next => action => {
  console.log(action, store);
  
  next(action);
}; 

export default logger;

store/middleware/test.js

js
const test = store => next => action => {
  console.log('test running.');
  
  next(action);
}; 

export default test;
const test = store => next => action => {
  console.log('test running.');
  
  next(action);
}; 

export default test;

store/index.js

js
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import logger from './middleware/logger';
import test from './middleware/test';

export const store = createStore(reducers, applyMiddleware(logger, test));
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import logger from './middleware/logger';
import test from './middleware/test';

export const store = createStore(reducers, applyMiddleware(logger, test));

中间件执行顺序取决于中间件注册顺序。

定义异步处理中间件

增加异步处理。

src/components/Counter.js

js
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement, increment_async }) {
  return (
    <div>
      <button onClick={ () => increment_async(5) }>+ async</button>
      <button onClick={ () => increment(5) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement, increment_async }) {
  return (
    <div>
      <button onClick={ () => increment_async(5) }>+ async</button>
      <button onClick={ () => increment(5) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

src/components/Modal.js

jsx
import React from "react";
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as modalActions from '../store/actions/modal';

function Modal ({ showStatus, show, hide, show_async }) {
  const styles = {
    width: 200,
    height: 200,
    position: 'absolute',
    left: '50%',
    top: '50%',
    marginLeft: -100,
    marginTop: -100,
    background: 'orange',
    display: showStatus ? 'block' : 'none'
  };

  return (
    <div>
      <button onClick={ show_async }>显示-async</button>
      <button onClick={ show }>显示</button>
      <button onClick={ hide }>隐藏</button>
      <div style={styles}></div>
    </div>
  )
}

const mapStateToProps = state => ({
  showStatus: state.modal.show
});

const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Modal);
import React from "react";
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as modalActions from '../store/actions/modal';

function Modal ({ showStatus, show, hide, show_async }) {
  const styles = {
    width: 200,
    height: 200,
    position: 'absolute',
    left: '50%',
    top: '50%',
    marginLeft: -100,
    marginTop: -100,
    background: 'orange',
    display: showStatus ? 'block' : 'none'
  };

  return (
    <div>
      <button onClick={ show_async }>显示-async</button>
      <button onClick={ show }>显示</button>
      <button onClick={ hide }>隐藏</button>
      <div style={styles}></div>
    </div>
  )
}

const mapStateToProps = state => ({
  showStatus: state.modal.show
});

const mapDispatchToProps = dispatch => bindActionCreators(modalActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Modal);

store/actions/counter.js

js
import { INCREMENT, DECREMENT } from "../const/counter";

export const increment = payload => ({ type: INCREMENT, payload });
export const decrement = payload => ({ type: DECREMENT, payload });

export const increment_async = payload => dispatch => {
  setTimeout(() => dispatch(increment(payload)), 2 * 1000);
}
import { INCREMENT, DECREMENT } from "../const/counter";

export const increment = payload => ({ type: INCREMENT, payload });
export const decrement = payload => ({ type: DECREMENT, payload });

export const increment_async = payload => dispatch => {
  setTimeout(() => dispatch(increment(payload)), 2 * 1000);
}

store/action/modal.js

js
import { HIDE_MODAL, SHOW_MODAL } from "../const/modal";

export const show = () => ({ type: SHOW_MODAL });
export const hide = () => ({ type: HIDE_MODAL });

export const show_async = () => dispatch => {
  setTimeout(() => dispatch(show()), 2 * 1000);
}
import { HIDE_MODAL, SHOW_MODAL } from "../const/modal";

export const show = () => ({ type: SHOW_MODAL });
export const hide = () => ({ type: HIDE_MODAL });

export const show_async = () => dispatch => {
  setTimeout(() => dispatch(show()), 2 * 1000);
}

store/middleware/thunk.js

js
const thunk = ({ dispatch }) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch);
  }
  next(action);
}

export default thunk;
const thunk = ({ dispatch }) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch);
  }
  next(action);
}

export default thunk;

store/index.js

js
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import logger from './middleware/logger';
import test from './middleware/test';
import thunk from './middleware/thunk';

export const store = createStore(reducers, applyMiddleware(logger, test, thunk));
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import logger from './middleware/logger';
import test from './middleware/test';
import thunk from './middleware/thunk';

export const store = createStore(reducers, applyMiddleware(logger, test, thunk));

redux-thunk 中间件

使用方法

js
yarn add redux-thunk
yarn add redux-thunk
js
import thunk from 'redux-thunk';
import { applyMiddleware } from 'redux';

createStore(rootRouter, applyMiddleware(thunk));
import thunk from 'redux-thunk';
import { applyMiddleware } from 'redux';

createStore(rootRouter, applyMiddleware(thunk));
js
const loadPosts = () => async dispatch => {
  const posts = await axios.get('/api/posts').then(response => response.data);
  
  dispatch({ type: LOADPOSTSUCCESS, payload: posts })
}
const loadPosts = () => async dispatch => {
  const posts = await axios.get('/api/posts').then(response => response.data);
  
  dispatch({ type: LOADPOSTSUCCESS, payload: posts })
}

案例

js
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import logger from './middleware/logger';
import test from './middleware/test';
// import thunk from './middleware/thunk';
import thunk from 'redux-thunk';

export const store = createStore(reducers, applyMiddleware(logger, test, thunk));
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import logger from './middleware/logger';
import test from './middleware/test';
// import thunk from './middleware/thunk';
import thunk from 'redux-thunk';

export const store = createStore(reducers, applyMiddleware(logger, test, thunk));

redux-saga 中间件

redux-saga 可以将异步操作从 Action Creator 文件中抽离出来,放在一个单独的文件中。

js
yarn add redux-saga
yarn add redux-saga

使用方法

创建 redux-saga 中间件

js
import createSagaMiddleware from 'redux-saga';

const sagaMiddleware = createSagaMiddleware();
import createSagaMiddleware from 'redux-saga';

const sagaMiddleware = createSagaMiddleware();

注册 sagaMiddleware

js
createStore(reducer, applyMiddleware(sagaMiddleware));
createStore(reducer, applyMiddleware(sagaMiddleware));

使用 saga 接收 action 执行异步操作

js
import { takeEntry, put } from 'redux-saga/effects';

function* load_posts () {
  const { data } = yield axios.get('/api/posts.json');
  
  yield put(load_posts_success(data));
}

export default function* postSaga () {
  yield takeEntry(LOAD_POSTS, load_posts);
}
import { takeEntry, put } from 'redux-saga/effects';

function* load_posts () {
  const { data } = yield axios.get('/api/posts.json');
  
  yield put(load_posts_success(data));
}

export default function* postSaga () {
  yield takeEntry(LOAD_POSTS, load_posts);
}

启动 saga

js
import postSaga from './store/saga/post.saga';

sagaMiddleware.run(postSaga);
import postSaga from './store/saga/post.saga';

sagaMiddleware.run(postSaga);

案例

components/Counter.js

jsx
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement, increment_async }) {
  return (
    <div>
      <button onClick={ increment_async }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement, increment_async }) {
  return (
    <div>
      <button onClick={ increment_async }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

store/actions/counter.js

js
import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter";

export const increment = payload => ({ type: INCREMENT, payload });
export const decrement = payload => ({ type: DECREMENT, payload });

// export const increment_async = payload => dispatch => {
//   setTimeout(() => dispatch(increment(payload)), 2 * 1000);
// }

export const increment_async = () => ({ type: INCREMENT_ASYNC });
import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter";

export const increment = payload => ({ type: INCREMENT, payload });
export const decrement = payload => ({ type: DECREMENT, payload });

// export const increment_async = payload => dispatch => {
//   setTimeout(() => dispatch(increment(payload)), 2 * 1000);
// }

export const increment_async = () => ({ type: INCREMENT_ASYNC });

store/actions/const/counter.js

js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
export const INCREMENT_ASYNC = 'increment_async';
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
export const INCREMENT_ASYNC = 'increment_async';

store/sagas/counter.js

js
import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter';
import { INCREMENT_ASYNC } from '../const/counter';

function* increament_async_fn () {
  yield delay(2000);
  yield put(increment(10));
}

const counterSaga = function* () {
  yield takeEvery(INCREMENT_ASYNC, increament_async_fn)
}

export default counterSaga;
import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter';
import { INCREMENT_ASYNC } from '../const/counter';

function* increament_async_fn () {
  yield delay(2000);
  yield put(increment(10));
}

const counterSaga = function* () {
  yield takeEvery(INCREMENT_ASYNC, increament_async_fn)
}

export default counterSaga;

store/index.js

js
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import createSagaMiddleware from 'redux-saga';
import counterSaga from './sagas/counter';

const sagaMiddleware = createSagaMiddleware();

export const store = createStore(reducers, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(counterSaga);
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import createSagaMiddleware from 'redux-saga';
import counterSaga from './sagas/counter';

const sagaMiddleware = createSagaMiddleware();

export const store = createStore(reducers, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(counterSaga);

redux-saga action 传参

src/components/Counter.js

jsx
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement, increment_async }) {
  return (
    <div>
      <button onClick={ () => increment_async(20) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement, increment_async }) {
  return (
    <div>
      <button onClick={ () => increment_async(20) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(5) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

store/actions/counter.js

js
import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter";

export const increment = payload => ({ type: INCREMENT, payload });
export const decrement = payload => ({ type: DECREMENT, payload });

// export const increment_async = payload => dispatch => {
//   setTimeout(() => dispatch(increment(payload)), 2 * 1000);
// }

export const increment_async = payload => ({ type: INCREMENT_ASYNC, payload });
import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter";

export const increment = payload => ({ type: INCREMENT, payload });
export const decrement = payload => ({ type: DECREMENT, payload });

// export const increment_async = payload => dispatch => {
//   setTimeout(() => dispatch(increment(payload)), 2 * 1000);
// }

export const increment_async = payload => ({ type: INCREMENT_ASYNC, payload });

store/sagas/counter.js

js
import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter';
import { INCREMENT_ASYNC } from '../const/counter';

function* increament_async_fn (action) {
  yield delay(2000);
  yield put(increment(action.payload));
}

const counterSaga = function* () {
  yield takeEvery(INCREMENT_ASYNC, increament_async_fn)
}

export default counterSaga;
import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter';
import { INCREMENT_ASYNC } from '../const/counter';

function* increament_async_fn (action) {
  yield delay(2000);
  yield put(increment(action.payload));
}

const counterSaga = function* () {
  yield takeEvery(INCREMENT_ASYNC, increament_async_fn)
}

export default counterSaga;

saga 文件的拆分与合并

src/store/action/modal.js

js
import { HIDE_MODAL, SHOW_MODAL, SHOW_MODAL_ASYNC } from "../const/modal";

export const show = () => ({ type: SHOW_MODAL });
export const hide = () => ({ type: HIDE_MODAL });

// export const show_async = () => dispatch => {
//   setTimeout(() => dispatch(show()), 2 * 1000);
// }

export const show_async = () => ({ type: SHOW_MODAL_ASYNC });
import { HIDE_MODAL, SHOW_MODAL, SHOW_MODAL_ASYNC } from "../const/modal";

export const show = () => ({ type: SHOW_MODAL });
export const hide = () => ({ type: HIDE_MODAL });

// export const show_async = () => dispatch => {
//   setTimeout(() => dispatch(show()), 2 * 1000);
// }

export const show_async = () => ({ type: SHOW_MODAL_ASYNC });

src/store/const/modal.js

js
export const SHOW_MODAL = 'showModal';
export const HIDE_MODAL = 'hideModal';

export const SHOW_MODAL_ASYNC = 'showModal_async';
export const SHOW_MODAL = 'showModal';
export const HIDE_MODAL = 'hideModal';

export const SHOW_MODAL_ASYNC = 'showModal_async';

src/store/sagas/counter.js

js
import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter';
import { INCREMENT_ASYNC } from '../const/counter';

function* increament_async_fn (action) {
  yield delay(2000);
  yield put(increment(action.payload));
}

const counterSaga = function* () {
  yield takeEvery(INCREMENT_ASYNC, increament_async_fn);
}

export default counterSaga;
import { takeEvery, put, delay } from 'redux-saga/effects';
import { increment } from '../actions/counter';
import { INCREMENT_ASYNC } from '../const/counter';

function* increament_async_fn (action) {
  yield delay(2000);
  yield put(increment(action.payload));
}

const counterSaga = function* () {
  yield takeEvery(INCREMENT_ASYNC, increament_async_fn);
}

export default counterSaga;

src/store/sagas/modal.js

js
import { takeEvery, put, delay } from 'redux-saga/effects';
import { show } from '../actions/modal';
import { SHOW_MODAL_ASYNC } from '../const/modal';

function* showModal_async () {
  yield delay(2000);
  yield put(show());
}

const modalSaga = function* () {
  yield takeEvery(SHOW_MODAL_ASYNC, showModal_async);
}

export default modalSaga;
import { takeEvery, put, delay } from 'redux-saga/effects';
import { show } from '../actions/modal';
import { SHOW_MODAL_ASYNC } from '../const/modal';

function* showModal_async () {
  yield delay(2000);
  yield put(show());
}

const modalSaga = function* () {
  yield takeEvery(SHOW_MODAL_ASYNC, showModal_async);
}

export default modalSaga;

src/store/sagas/index.js

js
import { all } from 'redux-saga/effects';
import counterSaga from './counter';
import modalSaga from './modal';

const rootSaga = function* () {
  yield all([
    counterSaga(),
    modalSaga()
  ]);
}

export default rootSaga;
import { all } from 'redux-saga/effects';
import counterSaga from './counter';
import modalSaga from './modal';

const rootSaga = function* () {
  yield all([
    counterSaga(),
    modalSaga()
  ]);
}

export default rootSaga;

src/store/index.js

js
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';

const sagaMiddleware = createSagaMiddleware();

export const store = createStore(reducers, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(rootSaga);
import { createStore, applyMiddleware } from 'redux';
import reducers from './reducers';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas';

const sagaMiddleware = createSagaMiddleware();

export const store = createStore(reducers, applyMiddleware(sagaMiddleware));

sagaMiddleware.run(rootSaga);

redux-action 中间件使用

redux 流程中存在大量的样板代码,使用 redux-actions 可以简化 Action 和 Reducer 的处理。

js
yarn add redux-actions
yarn add redux-actions

使用方法

创建 action

js
import { createAction } from 'redux-actions';

const increment_action = createAction('increment');
const decrement_action = createAction('decrement');
import { createAction } from 'redux-actions';

const increment_action = createAction('increment');
const decrement_action = createAction('decrement');

创建 reducer

js
import { handleActions as createReducer } from 'redux-actions';
import { increment_action, decrement_action } from '../actions/counter.js';

const initialState = { count: 0 };

const counterReducer = createReducer({
  [increment_action]: (state, action) => ({ count: state.count + 1 }),
  [decrement_action]: (state, action) => ({ count: state.count - 1 }),
}, initialState);

export default counterReducer;
import { handleActions as createReducer } from 'redux-actions';
import { increment_action, decrement_action } from '../actions/counter.js';

const initialState = { count: 0 };

const counterReducer = createReducer({
  [increment_action]: (state, action) => ({ count: state.count + 1 }),
  [decrement_action]: (state, action) => ({ count: state.count - 1 }),
}, initialState);

export default counterReducer;

案例

components/counter.js

js
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement}) {
  return (
    <div>
      <button onClick={ () => increment(10) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(10) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
import React from "react"
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as counterActions from '../store/actions/counter'

function Counter ({ count, increment, decrement}) {
  return (
    <div>
      <button onClick={ () => increment(10) }>+</button>
      <span>{ count }</span>
      <button onClick={ () => decrement(10) }>-</button>
    </div>
  )
}

const mapStateToProps = state => ({
  count: state.counter.count
});

const mapDispatchToProps = dispatch => bindActionCreators(counterActions, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

store/action/counter.js

js
// import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter";

// export const increment = payload => ({ type: INCREMENT, payload });
// export const decrement = payload => ({ type: DECREMENT, payload });

// // export const increment_async = payload => dispatch => {
// //   setTimeout(() => dispatch(increment(payload)), 2 * 1000);
// // }

// export const increment_async = payload => ({ type: INCREMENT_ASYNC, payload });


import { createAction } from 'redux-actions';

export const increment = createAction('inrement');
export const decrement = createAction('decrement');
// import { INCREMENT, DECREMENT, INCREMENT_ASYNC } from "../const/counter";

// export const increment = payload => ({ type: INCREMENT, payload });
// export const decrement = payload => ({ type: DECREMENT, payload });

// // export const increment_async = payload => dispatch => {
// //   setTimeout(() => dispatch(increment(payload)), 2 * 1000);
// // }

// export const increment_async = payload => ({ type: INCREMENT_ASYNC, payload });


import { createAction } from 'redux-actions';

export const increment = createAction('inrement');
export const decrement = createAction('decrement');

store/reducers/counter.js

js
// import { INCREMENT, DECREMENT } from "../const/counter";

// const initialState = {
//   count: 0
// }

// const counterReducer = (state = initialState, action) => {
//   switch (action.type) {
//     case INCREMENT:
//       return {
//         ...state,
//         count: state.count + action.payload
//       };
//     case DECREMENT:
//       return {
//         ...state,
//         count: state.count - action.payload
//       };
//     default:
//       return state;
//   }
// };

// export default counterReducer;



import { handleActions as createReducer } from 'redux-actions';
import { increment, decrement } from '../actions/counter';

const initialState = {
  count: 0
};

const handleIncrement = (state, action) => ({
  count: state.count + action.payload
});

const handleDecrement = (state, action) => ({
  count: state.count - action.payload
});

export default createReducer({
  [increment]: handleIncrement,
  [decrement]: handleDecrement
}, initialState);
// import { INCREMENT, DECREMENT } from "../const/counter";

// const initialState = {
//   count: 0
// }

// const counterReducer = (state = initialState, action) => {
//   switch (action.type) {
//     case INCREMENT:
//       return {
//         ...state,
//         count: state.count + action.payload
//       };
//     case DECREMENT:
//       return {
//         ...state,
//         count: state.count - action.payload
//       };
//     default:
//       return state;
//   }
// };

// export default counterReducer;



import { handleActions as createReducer } from 'redux-actions';
import { increment, decrement } from '../actions/counter';

const initialState = {
  count: 0
};

const handleIncrement = (state, action) => ({
  count: state.count + action.payload
});

const handleDecrement = (state, action) => ({
  count: state.count - action.payload
});

export default createReducer({
  [increment]: handleIncrement,
  [decrement]: handleDecrement
}, initialState);

二、源码实现

js
createStore(reducer, preloadedState, enhancer)

{
  getState, dispatch, subscribe
}
createStore(reducer, preloadedState, enhancer)

{
  getState, dispatch, subscribe
}

核心逻辑

redux/index.js

js
function createStore (reducer, preloadedState) {
  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}
function createStore (reducer, preloadedState) {
  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

index.html

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    function reducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }

    // 创建 store
    const store = createStore(reducer, 0);

    // 触发 action
    oIncrementBtn.onclick = function () {
      store.dispatch({ type: 'increment' });
    }
    oDecrementBtn.onclick = function () {
      store.dispatch({ type: 'decrement' });
    }

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState();

      oCount.innerHTML = count;
    });

  </script>
  
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    function reducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }

    // 创建 store
    const store = createStore(reducer, 0);

    // 触发 action
    oIncrementBtn.onclick = function () {
      store.dispatch({ type: 'increment' });
    }
    oDecrementBtn.onclick = function () {
      store.dispatch({ type: 'decrement' });
    }

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState();

      oCount.innerHTML = count;
    });

  </script>
  
</body>
</html>

参数类型约束

js
/**
 * @file 自定义 Redux
 */

function createStore (reducer, preloadedState) {
  // 约束 reducer 参数类型
  if (typeof reducer !== 'function') {
    throw new Error('reducer has to be a function.');
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}
/**
 * @file 自定义 Redux
 */

function createStore (reducer, preloadedState) {
  // 约束 reducer 参数类型
  if (typeof reducer !== 'function') {
    throw new Error('reducer has to be a function.');
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}

Enhancer

js
/**
 * @file 自定义 Redux
 */

/**
 * @descriptions 
 * @param {function} reducer 
 * @param {object} preloadedState 
 * @param {function} enhancer
 * @returns 
 */
function createStore (reducer, preloadedState, enhancer) {
  // 约束 reducer 参数类型
  if (!iSFunction(reducer)) {
    throw new Error('reducer has to be a function.');
  }

  // 判断 enhancer 参数
  if (typeof enhancer !== 'undefined') {
    if (!iSFunction(reducer)) {
      throw new Error('enhancer has to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}


function iSFunction (val) {
  return typeof val === 'function';
}
/**
 * @file 自定义 Redux
 */

/**
 * @descriptions 
 * @param {function} reducer 
 * @param {object} preloadedState 
 * @param {function} enhancer
 * @returns 
 */
function createStore (reducer, preloadedState, enhancer) {
  // 约束 reducer 参数类型
  if (!iSFunction(reducer)) {
    throw new Error('reducer has to be a function.');
  }

  // 判断 enhancer 参数
  if (typeof enhancer !== 'undefined') {
    if (!iSFunction(reducer)) {
      throw new Error('enhancer has to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}


function iSFunction (val) {
  return typeof val === 'function';
}
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    function enhancer (createStore) {
      return function (reducer, preloadedState) {
        const store = createStore(reducer, preloadedState);

        let dispatch = store.dispatch;

        function _dispatch (action) {
          if (typeof action === 'function') {
            return action(dispatch);
          }

          dispatch(action);
        }

        return {
          ...store,
          dispatch: _dispatch
        };
      }
    }

    function reducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }

    // 创建 store
    const store = createStore(reducer, 0, enhancer);

    // 触发 action
    oIncrementBtn.onclick = function () {
      // store.dispatch({ type: 'increment' });
      store.dispatch((dispatch) => {
        setTimeout(() => {
          dispatch({ type: 'increment' });
        }, 1000);
      });
    }
    oDecrementBtn.onclick = function () {
      store.dispatch({ type: 'decrement' });
    }

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState();

      oCount.innerHTML = count;
    });

  </script>
  
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    function enhancer (createStore) {
      return function (reducer, preloadedState) {
        const store = createStore(reducer, preloadedState);

        let dispatch = store.dispatch;

        function _dispatch (action) {
          if (typeof action === 'function') {
            return action(dispatch);
          }

          dispatch(action);
        }

        return {
          ...store,
          dispatch: _dispatch
        };
      }
    }

    function reducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }

    // 创建 store
    const store = createStore(reducer, 0, enhancer);

    // 触发 action
    oIncrementBtn.onclick = function () {
      // store.dispatch({ type: 'increment' });
      store.dispatch((dispatch) => {
        setTimeout(() => {
          dispatch({ type: 'increment' });
        }, 1000);
      });
    }
    oDecrementBtn.onclick = function () {
      store.dispatch({ type: 'decrement' });
    }

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState();

      oCount.innerHTML = count;
    });

  </script>
  
</body>
</html>

applyMiddleware 中间件其实就是一个内置的 Enhancer 函数。

applyMiddleware

redux/middlewares/logger.js

js
function logger (store) {
  return function (next) {
    return function (action) {
      console.log('logger');

      next(action);
    }
  }
}
function logger (store) {
  return function (next) {
    return function (action) {
      console.log('logger');

      next(action);
    }
  }
}

redux/middlewares/thunk.js

js
function thunk (store) {
  return function (next) {
    return function (action) {
      console.log('thunk');

      next(action);
    }
  }
}
function thunk (store) {
  return function (next) {
    return function (action) {
      console.log('thunk');

      next(action);
    }
  }
}

redux/index.js

js
/**
 * @file 自定义 Redux
 */

/**
 * @descriptions 
 * @param {function} reducer 
 * @param {object} preloadedState 
 * @param {function} enhancer
 * @returns 
 */
function createStore (reducer, preloadedState, enhancer) {
  // 约束 reducer 参数类型
  if (!iSFunction(reducer)) {
    throw new Error('reducer has to be a function.');
  }

  // 判断 enhancer 参数
  if (typeof enhancer !== 'undefined') {
    if (!iSFunction(reducer)) {
      throw new Error('enhancer has to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}


function iSFunction (val) {
  return typeof val === 'function';
}


function applyMiddleware (...middlewares) {
  return function (createStore) {
    return function (reducer, preloadedState) {
      const store = createStore(reducer, preloadedState);

      const middlewareAPI = {
        getState: store.getState,
        dispatch: store.dispatch
      };

      // 调用中间件函数,传递 store 对象
      const chain = middlewares.map(middleware => middleware(middlewareAPI));

      // 第一个中间件的最里层函数
      const dispatch = compose(...chain)(store.dispatch);

      return {
        ...store,
        dispatch
      }
    }
  }
}

function compose () {
  const funcs = [...arguments];

  return function (dispatch) {
    for (let i = funcs.length - 1; i >= 0; i--) {
      dispatch = funcs[i](dispatch);
    }

    return dispatch;
  }
}
/**
 * @file 自定义 Redux
 */

/**
 * @descriptions 
 * @param {function} reducer 
 * @param {object} preloadedState 
 * @param {function} enhancer
 * @returns 
 */
function createStore (reducer, preloadedState, enhancer) {
  // 约束 reducer 参数类型
  if (!iSFunction(reducer)) {
    throw new Error('reducer has to be a function.');
  }

  // 判断 enhancer 参数
  if (typeof enhancer !== 'undefined') {
    if (!iSFunction(reducer)) {
      throw new Error('enhancer has to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}


function iSFunction (val) {
  return typeof val === 'function';
}


function applyMiddleware (...middlewares) {
  return function (createStore) {
    return function (reducer, preloadedState) {
      const store = createStore(reducer, preloadedState);

      const middlewareAPI = {
        getState: store.getState,
        dispatch: store.dispatch
      };

      // 调用中间件函数,传递 store 对象
      const chain = middlewares.map(middleware => middleware(middlewareAPI));

      // 第一个中间件的最里层函数
      const dispatch = compose(...chain)(store.dispatch);

      return {
        ...store,
        dispatch
      }
    }
  }
}

function compose () {
  const funcs = [...arguments];

  return function (dispatch) {
    for (let i = funcs.length - 1; i >= 0; i--) {
      dispatch = funcs[i](dispatch);
    }

    return dispatch;
  }
}

index.html

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>
  <script src="./redux/middlewares/logger.js"></script>
  <script src="./redux/middlewares/thunk.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    // function enhancer (createStore) {
    //   return function (reducer, preloadedState) {
    //     const store = createStore(reducer, preloadedState);

    //     let dispatch = store.dispatch;

    //     function _dispatch (action) {
    //       if (typeof action === 'function') {
    //         return action(dispatch);
    //       }

    //       dispatch(action);
    //     }

    //     return {
    //       ...store,
    //       dispatch: _dispatch
    //     };
    //   }
    // }

    function reducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }

    // 创建 store
    // const store = createStore(reducer, 0, enhancer);
    const store = createStore(reducer, 0, applyMiddleware(logger, thunk));

    // 触发 action
    oIncrementBtn.onclick = function () {
      // store.dispatch((dispatch) => {
      //   setTimeout(() => {
      //     dispatch({ type: 'increment' });
      //   }, 1000);
      // });
      store.dispatch({ type: 'increment' });
    }
    oDecrementBtn.onclick = function () {
      store.dispatch({ type: 'decrement' });
    }

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState();

      oCount.innerHTML = count;
    });

  </script>
  
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>
  <script src="./redux/middlewares/logger.js"></script>
  <script src="./redux/middlewares/thunk.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    // function enhancer (createStore) {
    //   return function (reducer, preloadedState) {
    //     const store = createStore(reducer, preloadedState);

    //     let dispatch = store.dispatch;

    //     function _dispatch (action) {
    //       if (typeof action === 'function') {
    //         return action(dispatch);
    //       }

    //       dispatch(action);
    //     }

    //     return {
    //       ...store,
    //       dispatch: _dispatch
    //     };
    //   }
    // }

    function reducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }

    // 创建 store
    // const store = createStore(reducer, 0, enhancer);
    const store = createStore(reducer, 0, applyMiddleware(logger, thunk));

    // 触发 action
    oIncrementBtn.onclick = function () {
      // store.dispatch((dispatch) => {
      //   setTimeout(() => {
      //     dispatch({ type: 'increment' });
      //   }, 1000);
      // });
      store.dispatch({ type: 'increment' });
    }
    oDecrementBtn.onclick = function () {
      store.dispatch({ type: 'decrement' });
    }

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState();

      oCount.innerHTML = count;
    });

  </script>
  
</body>
</html>

bindActionCreator

redux/index.js

js
/**
 * @file 自定义 Redux
 */

/**
 * @descriptions 
 * @param {function} reducer 
 * @param {object} preloadedState 
 * @param {function} enhancer
 * @returns 
 */
function createStore (reducer, preloadedState, enhancer) {
  // 约束 reducer 参数类型
  if (!iSFunction(reducer)) {
    throw new Error('reducer has to be a function.');
  }

  // 判断 enhancer 参数
  if (typeof enhancer !== 'undefined') {
    if (!iSFunction(reducer)) {
      throw new Error('enhancer has to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}


function iSFunction (val) {
  return typeof val === 'function';
}


function applyMiddleware (...middlewares) {
  return function (createStore) {
    return function (reducer, preloadedState) {
      const store = createStore(reducer, preloadedState);

      const middlewareAPI = {
        getState: store.getState,
        dispatch: store.dispatch
      };

      // 调用中间件函数,传递 store 对象
      const chain = middlewares.map(middleware => middleware(middlewareAPI));

      // 第一个中间件的最里层函数
      const dispatch = compose(...chain)(store.dispatch);

      return {
        ...store,
        dispatch
      }
    }
  }
}

function compose () {
  const funcs = [...arguments];

  return function (dispatch) {
    for (let i = funcs.length - 1; i >= 0; i--) {
      dispatch = funcs[i](dispatch);
    }

    return dispatch;
  }
}


function bindActionCreators (actionCreators, dispatch) {
  const boundActionCreators = {};

  for (let key in actionCreators) {
    boundActionCreators[key] = function () {
      dispatch(actionCreators[key]());
    }
  }

  return boundActionCreators;
}
/**
 * @file 自定义 Redux
 */

/**
 * @descriptions 
 * @param {function} reducer 
 * @param {object} preloadedState 
 * @param {function} enhancer
 * @returns 
 */
function createStore (reducer, preloadedState, enhancer) {
  // 约束 reducer 参数类型
  if (!iSFunction(reducer)) {
    throw new Error('reducer has to be a function.');
  }

  // 判断 enhancer 参数
  if (typeof enhancer !== 'undefined') {
    if (!iSFunction(reducer)) {
      throw new Error('enhancer has to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}


function iSFunction (val) {
  return typeof val === 'function';
}


function applyMiddleware (...middlewares) {
  return function (createStore) {
    return function (reducer, preloadedState) {
      const store = createStore(reducer, preloadedState);

      const middlewareAPI = {
        getState: store.getState,
        dispatch: store.dispatch
      };

      // 调用中间件函数,传递 store 对象
      const chain = middlewares.map(middleware => middleware(middlewareAPI));

      // 第一个中间件的最里层函数
      const dispatch = compose(...chain)(store.dispatch);

      return {
        ...store,
        dispatch
      }
    }
  }
}

function compose () {
  const funcs = [...arguments];

  return function (dispatch) {
    for (let i = funcs.length - 1; i >= 0; i--) {
      dispatch = funcs[i](dispatch);
    }

    return dispatch;
  }
}


function bindActionCreators (actionCreators, dispatch) {
  const boundActionCreators = {};

  for (let key in actionCreators) {
    boundActionCreators[key] = function () {
      dispatch(actionCreators[key]());
    }
  }

  return boundActionCreators;
}

index.html

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>
  <script src="./redux/middlewares/logger.js"></script>
  <script src="./redux/middlewares/thunk.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    function reducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }
    
    // 创建 store
    const store = createStore(reducer, 0, applyMiddleware(logger, thunk));

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState();

      oCount.innerHTML = count;
    });

    const actions = bindActionCreators({ increment, decrement }, store.dispatch);

    function increment () {
      return { type: 'increment' };
    }

    function decrement () {
      return { type: 'decrement' };
    }

    // 触发 action
    oIncrementBtn.onclick = function () {
      // store.dispatch({ type: 'increment' });
      actions.increment();
    }
    oDecrementBtn.onclick = function () {
      // store.dispatch({ type: 'decrement' });
      actions.decrement();
    }
   
  </script>
  
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>
  <script src="./redux/middlewares/logger.js"></script>
  <script src="./redux/middlewares/thunk.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    function reducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }
    
    // 创建 store
    const store = createStore(reducer, 0, applyMiddleware(logger, thunk));

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState();

      oCount.innerHTML = count;
    });

    const actions = bindActionCreators({ increment, decrement }, store.dispatch);

    function increment () {
      return { type: 'increment' };
    }

    function decrement () {
      return { type: 'decrement' };
    }

    // 触发 action
    oIncrementBtn.onclick = function () {
      // store.dispatch({ type: 'increment' });
      actions.increment();
    }
    oDecrementBtn.onclick = function () {
      // store.dispatch({ type: 'decrement' });
      actions.decrement();
    }
   
  </script>
  
</body>
</html>

combineReducer

redux/index.js

js
/**
 * @file 自定义 Redux
 */

/**
 * @descriptions 
 * @param {function} reducer 
 * @param {object} preloadedState 
 * @param {function} enhancer
 * @returns 
 */
function createStore (reducer, preloadedState, enhancer) {
  // 约束 reducer 参数类型
  if (!iSFunction(reducer)) {
    throw new Error('reducer has to be a function.');
  }

  // 判断 enhancer 参数
  if (typeof enhancer !== 'undefined') {
    if (!iSFunction(reducer)) {
      throw new Error('enhancer has to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}


function iSFunction (val) {
  return typeof val === 'function';
}


function applyMiddleware (...middlewares) {
  return function (createStore) {
    return function (reducer, preloadedState) {
      const store = createStore(reducer, preloadedState);

      const middlewareAPI = {
        getState: store.getState,
        dispatch: store.dispatch
      };

      // 调用中间件函数,传递 store 对象
      const chain = middlewares.map(middleware => middleware(middlewareAPI));

      // 第一个中间件的最里层函数
      const dispatch = compose(...chain)(store.dispatch);

      return {
        ...store,
        dispatch
      }
    }
  }
}

function compose () {
  const funcs = [...arguments];

  return function (dispatch) {
    for (let i = funcs.length - 1; i >= 0; i--) {
      dispatch = funcs[i](dispatch);
    }

    return dispatch;
  }
}


function bindActionCreators (actionCreators, dispatch) {
  const boundActionCreators = {};

  for (let key in actionCreators) {
    boundActionCreators[key] = function () {
      dispatch(actionCreators[key]());
    }
  }

  return boundActionCreators;
}


function combineReducers (reducers) {
  // 检查 reducer 类型,必须是函数类型
  const reducerKeys = Object.keys(reducers);

  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i];

    if (!iSFunction(reducers[key])) {
      throw new Error('reducer has to be a function.');
    }
  }

  return function (state, action) {
    // 调用 reducer,并存储 reducer 返回的状态
    let nextState = {};

    for (let i = 0; i < reducerKeys.length; i++) {
      const key = reducerKeys[i];
      const reducer = reducers[key];
      const previousState = state[key];

      nextState[key] = reducer(previousState, action);
    }

    return nextState;
  }
}
/**
 * @file 自定义 Redux
 */

/**
 * @descriptions 
 * @param {function} reducer 
 * @param {object} preloadedState 
 * @param {function} enhancer
 * @returns 
 */
function createStore (reducer, preloadedState, enhancer) {
  // 约束 reducer 参数类型
  if (!iSFunction(reducer)) {
    throw new Error('reducer has to be a function.');
  }

  // 判断 enhancer 参数
  if (typeof enhancer !== 'undefined') {
    if (!iSFunction(reducer)) {
      throw new Error('enhancer has to be a function.');
    }

    return enhancer(createStore)(reducer, preloadedState);
  }

  // store 对象中存储的状态
  let currentState = preloadedState;
  // 存放订阅者函数
  const currentListeners = [];

  // 获取状态
  function getState () {
    return currentState;
  }

  // 触发 action
  function dispatch (action) {
    // 判断 action 是否是对象
    if (!isPlainObject(action)) {
      throw new Error('action has be a object.');
    }
    
    // 判断对象是否存在 type 属性
    if (typeof action.type === 'undefined') {
      throw new Error('the type attribute must exist.');
    }

    currentState = reducer(currentState, action);

    // 循环数据,调用订阅者
    for (let i = 0; i < currentListeners.length; i++) {
      const listener = currentListeners[i];

      listener();
    }
  }

  // 订阅状态 
  function subscribe (listener) {
    currentListeners.push(listener);
  }

  return {
    getState,
    dispatch,
    subscribe
  }
}

// 判断参数是否是对象
function isPlainObject (obj) {
  // 排除基础数据类型和 null
  if (typeof obj !== 'object' || obj === null) {
    return false;
  }

  // 区分数组和对象
  let proto = obj;

  while (Object.getPrototypeOf(proto) != null) {
    proto = Object.getPrototypeOf(proto);
  }

  return Object.getPrototypeOf(obj) == proto;
}


function iSFunction (val) {
  return typeof val === 'function';
}


function applyMiddleware (...middlewares) {
  return function (createStore) {
    return function (reducer, preloadedState) {
      const store = createStore(reducer, preloadedState);

      const middlewareAPI = {
        getState: store.getState,
        dispatch: store.dispatch
      };

      // 调用中间件函数,传递 store 对象
      const chain = middlewares.map(middleware => middleware(middlewareAPI));

      // 第一个中间件的最里层函数
      const dispatch = compose(...chain)(store.dispatch);

      return {
        ...store,
        dispatch
      }
    }
  }
}

function compose () {
  const funcs = [...arguments];

  return function (dispatch) {
    for (let i = funcs.length - 1; i >= 0; i--) {
      dispatch = funcs[i](dispatch);
    }

    return dispatch;
  }
}


function bindActionCreators (actionCreators, dispatch) {
  const boundActionCreators = {};

  for (let key in actionCreators) {
    boundActionCreators[key] = function () {
      dispatch(actionCreators[key]());
    }
  }

  return boundActionCreators;
}


function combineReducers (reducers) {
  // 检查 reducer 类型,必须是函数类型
  const reducerKeys = Object.keys(reducers);

  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i];

    if (!iSFunction(reducers[key])) {
      throw new Error('reducer has to be a function.');
    }
  }

  return function (state, action) {
    // 调用 reducer,并存储 reducer 返回的状态
    let nextState = {};

    for (let i = 0; i < reducerKeys.length; i++) {
      const key = reducerKeys[i];
      const reducer = reducers[key];
      const previousState = state[key];

      nextState[key] = reducer(previousState, action);
    }

    return nextState;
  }
}

index.html

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>
  <script src="./redux/middlewares/logger.js"></script>
  <script src="./redux/middlewares/thunk.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    function counterReducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }

    const rootReducer = combineReducers({ counter: counterReducer });
    
    // 创建 store
    const store = createStore(rootReducer, { counter: 0 }, applyMiddleware(logger, thunk));

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState().counter;

      oCount.innerHTML = count;
    });

    const actions = bindActionCreators({ increment, decrement }, store.dispatch);

    function increment () {
      return { type: 'increment' };
    }

    function decrement () {
      return { type: 'decrement' };
    }

    // 触发 action
    oIncrementBtn.onclick = function () {
      // store.dispatch({ type: 'increment' });
      actions.increment();
    }
    oDecrementBtn.onclick = function () {
      // store.dispatch({ type: 'decrement' });
      actions.decrement();
    }
   
  </script>
  
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Redux 源码实现</title>
</head>
<body>

  <button id="J-increment">+</button>
  <span id="J-count">0</span>
  <button id="J-decrement">-</button>

  <script src="./redux/index.js"></script>
  <script src="./redux/middlewares/logger.js"></script>
  <script src="./redux/middlewares/thunk.js"></script>

  <script>
    const oCount = document.getElementById('J-count');
    const oIncrementBtn = document.getElementById('J-increment');
    const oDecrementBtn = document.getElementById('J-decrement');

    function counterReducer (state, action) {
      switch (action.type) {
        case 'increment':
          return state + 1;
        case 'decrement':
          return state - 1;
        default:
          return state;
      }
    }

    const rootReducer = combineReducers({ counter: counterReducer });
    
    // 创建 store
    const store = createStore(rootReducer, { counter: 0 }, applyMiddleware(logger, thunk));

    // 订阅状态
    store.subscribe(() => {
      const count = store.getState().counter;

      oCount.innerHTML = count;
    });

    const actions = bindActionCreators({ increment, decrement }, store.dispatch);

    function increment () {
      return { type: 'increment' };
    }

    function decrement () {
      return { type: 'decrement' };
    }

    // 触发 action
    oIncrementBtn.onclick = function () {
      // store.dispatch({ type: 'increment' });
      actions.increment();
    }
    oDecrementBtn.onclick = function () {
      // store.dispatch({ type: 'decrement' });
      actions.decrement();
    }
   
  </script>
  
</body>
</html>

三、Redux Toolkit

概述

官方推出的 Redux 工具,对 Redux 进行的二次封装,用于高效 Redux 开发,使用简单。

js
yarn add @reduxjs/toolkit react-redux
yarn add @reduxjs/toolkit react-redux

创建状态切片

状态切片,可以认为它是 Redux 中一个个小的 Reducer 函数。

在 Redux 中,原本 Reducer 函数和 Action 对象需要分别创建,现在通过状态切片替代,它会返回 Reducer 函数和 Action 对象。

store/todos.js

js
import { createSlice } from '@reduxjs/toolkit';

export const TODOS_FEATURE_KEY = 'todos';

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push(action.payload)
    }
  }
});

export const { addTodo } = actions;
export default ToolsReducer;
import { createSlice } from '@reduxjs/toolkit';

export const TODOS_FEATURE_KEY = 'todos';

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: [],
  reducers: {
    addTodo: (state, action) => {
      state.push(action.payload)
    }
  }
});

export const { addTodo } = actions;
export default ToolsReducer;

创建 Store

store/index.js

js
import { configureStore } from '@reduxjs/toolkit';
import TodosReducer, { TODOS_FEATURE_KEY } from './todos';

export default configureStore({
  reducer: {
    [TODOS_FEATURE_KEY]: TodosReducer
  },
  devTools: process.env.NODE_ENV !== 'production'
});
import { configureStore } from '@reduxjs/toolkit';
import TodosReducer, { TODOS_FEATURE_KEY } from './todos';

export default configureStore({
  reducer: {
    [TODOS_FEATURE_KEY]: TodosReducer
  },
  devTools: process.env.NODE_ENV !== 'production'
});

Action 预处理

当 Action 被触发后,可以通过 prepare 方法对 Action 进行预处理,处理完成后交给 Reudcer.prepare 方法必须返回对象。

store/todos.js

js
import { createSlice } from '@reduxjs/toolkit';

export const TODOS_FEATURE_KEY = 'todos';

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: [],
  reducers: {
    // addTodo: (state, action) => {
    //   state.push(action.payload)
    // }
    addTodo: {
      reducer:  (state, action) => {
        state.push(action.payload)
      },
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    }
  }
});

export const { addTodo } = actions;
export default ToolsReducer;
import { createSlice } from '@reduxjs/toolkit';

export const TODOS_FEATURE_KEY = 'todos';

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: [],
  reducers: {
    // addTodo: (state, action) => {
    //   state.push(action.payload)
    // }
    addTodo: {
      reducer:  (state, action) => {
        state.push(action.payload)
      },
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    }
  }
});

export const { addTodo } = actions;
export default ToolsReducer;

异步操作

创建异步操作函数

js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const TODOS_FEATURE_KEY = 'todos';

export const loadTodos = createAsyncThunk(
	'todos/loadTodos',
  (payload, thunkAPI) => {
    axios.get(payload).then(response => thunkAPI.dispatch(setTodos(response.data)))
  }
)
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const TODOS_FEATURE_KEY = 'todos';

export const loadTodos = createAsyncThunk(
	'todos/loadTodos',
  (payload, thunkAPI) => {
    axios.get(payload).then(response => thunkAPI.dispatch(setTodos(response.data)))
  }
)

接收异步操作结果

js
const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: [],
  reducers: {
    // addTodo: (state, action) => {
    //   state.push(action.payload)
    // }
    addTodo: {
      reducer:  (state, action) => {
        state.push(action.payload)
      },
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    },
    setTodos: (state, action) => {
      action.payload.forEach(todo => state.push(todo));
    }
  }
});

export const { addTodo, setTodos } = actions;
export default ToolsReducer;
const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: [],
  reducers: {
    // addTodo: (state, action) => {
    //   state.push(action.payload)
    // }
    addTodo: {
      reducer:  (state, action) => {
        state.push(action.payload)
      },
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    },
    setTodos: (state, action) => {
      action.payload.forEach(todo => state.push(todo));
    }
  }
});

export const { addTodo, setTodos } = actions;
export default ToolsReducer;

实体适配器

将状态放入实体适配器,实体适配器提供操作状态的各种方法,简化操作。

js
import { createSlice, createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';

const todosAdapter = createEntityAdapter();

export const TODOS_FEATURE_KEY = 'todos';

export const loadTodos = createAsyncThunk(
	'todos/loadTodos',
  (payload, thunkAPI) => {
    axios.get(payload).then(response => thunkAPI.dispatch(setTodos(response.data)))
  }
)

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: todosAdapter.getInitialState(),
  reducers: {
    addTodo: {
      reducer:  (state, action) => {
        todosAdapter.addOne(state, action.payload);
      },
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    },
    setTodos: (state, action) => {
      todosAdapter.addMany(state, action.payload);
    }
  },
  extraReducers: {}
});

export const { addTodo, setTodos } = actions;
export default ToolsReducer;
import { createSlice, createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';

const todosAdapter = createEntityAdapter();

export const TODOS_FEATURE_KEY = 'todos';

export const loadTodos = createAsyncThunk(
	'todos/loadTodos',
  (payload, thunkAPI) => {
    axios.get(payload).then(response => thunkAPI.dispatch(setTodos(response.data)))
  }
)

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: todosAdapter.getInitialState(),
  reducers: {
    addTodo: {
      reducer:  (state, action) => {
        todosAdapter.addOne(state, action.payload);
      },
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    },
    setTodos: (state, action) => {
      todosAdapter.addMany(state, action.payload);
    }
  },
  extraReducers: {}
});

export const { addTodo, setTodos } = actions;
export default ToolsReducer;

简化实体适配器代码

todosAdapter 的方法会检测传入的参数,如果传入的是 action,会寻找 action.payload。

js
import { createSlice, createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';

const todosAdapter = createEntityAdapter();

export const TODOS_FEATURE_KEY = 'todos';

export const loadTodos = createAsyncThunk(
	'todos/loadTodos',
  (payload, thunkAPI) => {
    axios.get(payload).then(response => thunkAPI.dispatch(setTodos(response.data)))
  }
)

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: todosAdapter.getInitialState(),
  reducers: {
    addTodo: {
      reducer: todosAdapter.addOne,
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    },
    setTodos: todosAdapter.addMany
  },
  extraReducers: {}
});

export const { addTodo, setTodos } = actions;
export default ToolsReducer;
import { createSlice, createAsyncThunk, createEntityAdapter } from '@reduxjs/toolkit';

const todosAdapter = createEntityAdapter();

export const TODOS_FEATURE_KEY = 'todos';

export const loadTodos = createAsyncThunk(
	'todos/loadTodos',
  (payload, thunkAPI) => {
    axios.get(payload).then(response => thunkAPI.dispatch(setTodos(response.data)))
  }
)

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: todosAdapter.getInitialState(),
  reducers: {
    addTodo: {
      reducer: todosAdapter.addOne,
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    },
    setTodos: todosAdapter.addMany
  },
  extraReducers: {}
});

export const { addTodo, setTodos } = actions;
export default ToolsReducer;

状态选择器

提供从实体适配器中获取状态的快捷途径。

js
import { createSelector } from '@reduxjs/toolkit';

const { selectAll } = todosAdapter.getSelectors();

export const selectTodosList = createSelector(
	state => state[TODOS_FEATURE_KEY],
  selectAll
)
import { createSelector } from '@reduxjs/toolkit';

const { selectAll } = todosAdapter.getSelectors();

export const selectTodosList = createSelector(
	state => state[TODOS_FEATURE_KEY],
  selectAll
)
js
import {
  createSlice,
  createAsyncThunk,
  createEntityAdapter,
  createSelector
} from '@reduxjs/toolkit';

const todosAdapter = createEntityAdapter();

export const TODOS_FEATURE_KEY = 'todos';

export const loadTodos = createAsyncThunk(
	'todos/loadTodos',
  (payload, thunkAPI) => {
    axios.get(payload).then(response => thunkAPI.dispatch(setTodos(response.data)))
  }
)

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: todosAdapter.getInitialState(),
  reducers: {
    addTodo: {
      reducer: todosAdapter.addOne,
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    },
    setTodos: todosAdapter.addMany
  },
  extraReducers: {}
});


const { selectAll } = todosAdapter.getSelectors();

export const selectTodos = createSelector(state => state[TODOS_FEATURE_KEY], selectAll);

export const { addTodo, setTodos } = actions;
export default ToolsReducer;
import {
  createSlice,
  createAsyncThunk,
  createEntityAdapter,
  createSelector
} from '@reduxjs/toolkit';

const todosAdapter = createEntityAdapter();

export const TODOS_FEATURE_KEY = 'todos';

export const loadTodos = createAsyncThunk(
	'todos/loadTodos',
  (payload, thunkAPI) => {
    axios.get(payload).then(response => thunkAPI.dispatch(setTodos(response.data)))
  }
)

const { reducer: ToolsReducer, actions } = createSlice({
  name: TODOS_FEATURE_KEY,
  initialState: todosAdapter.getInitialState(),
  reducers: {
    addTodo: {
      reducer: todosAdapter.addOne,
      prepare: todo => {
        return {
          payload: { id: Math.random(), ...todo }
        }
      }
    },
    setTodos: todosAdapter.addMany
  },
  extraReducers: {}
});


const { selectAll } = todosAdapter.getSelectors();

export const selectTodos = createSelector(state => state[TODOS_FEATURE_KEY], selectAll);

export const { addTodo, setTodos } = actions;
export default ToolsReducer;