手写redux源码及其中间件

一、redux的使用 

1.redux的工作流程:

  1. 通过createStore创建一个store仓库,用来存储状态;
  2. store接收的第一个参数reducer,初始化state并定义state修改规则;
  3. 页面中通过dispatch⼀个action来提交对数据的修改;
  4. action提交到reducer函数⾥,根据传⼊的action的type,返回新的 state。

2.相关代码:

创建项目,安装redux

npx create-react-app write-redux
npm i --save redux

 创建./src/store/index.js来创建store:

import { createStore } from 'redux';

// 创建reducer
function reducer(state = 0, action) {
  switch(action.type) {
    case "ADD": 
      return state + 1;
    case "MINUS": 
      return state - 1;
    default:
      return state;
  }
}
// 创建store仓库
const store = createStore(reducer);
export default store;

创建./src/views/ReduxPage.js使用redux:

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

class ReduxPage extends Component {
  add = () => {
    // dispatch分发action
    store.dispatch({ type: 'ADD' })
  }
  minus = () => {
    store.dispatch({ type: 'MINUS' })
  }
  render(){
    return (
      <div>
        <h1>{store.getState()}</h1>
        <button onClick={this.add}>add</button>
        <button onClick={this.minus}>minus</button>
      </div>
    )
  }
}
export default ReduxPage;

 在App.js中引入组件:

import logo from './logo.svg';
import './App.css';
import ReduxPage from './views/ReduxPage'

function App() {
  return (
    <div className="App">
      <ReduxPage />
    </div>
  );
}

export default App;

在index.js中订阅更新:

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

const render = () => {
  ReactDOM.render(
    <React.StrictMode>
      <App />
    </React.StrictMode>,
    document.getElementById('root')
  );
}
render();
store.subscribe(render);
reportWebVitals();

 界面:

二、手写redux源码

1. 根据上面例子,我们可以发现createStore方法返回常用的几个方法dispatch、subscribe、getState。

  • getState:直接返回当前state。

  • dispatch:接受一个action,执行reducer返回新的state,再遍历执行监听数组。

  • subscribe:将订阅的监听添加到监听数组中, 返回一个取消订阅的方法。

相关代码:

创建./src/redux/index.js:

export function createStore(reducer) {
  // 创建state
  let currentState;
  // 创建监听数组
  let currentListeners = [];

  // getState直接返回当前state
  function getState() {
    return currentState;
  }
  // dispatch接受一个action,执行reducer返回新的state,再遍历执行监听数组
  function dispatch(action) {
    currentState = reducer(currentState, action);
    currentListeners.forEach(listener => listener())
  }
  // subscribe:将订阅的监听添加到监听数组中, 返回一个取消订阅的方法
  function subscribe(listener) {
    currentListeners.push(listener);
    return () => {
      // 取消订阅:删除监听数组中对应的监听
      const index = currentListeners.indexOf(listener);
      currentListeners.splice(index, 1);
    }
  }

  // 处理reducer中的默认值
  // 由于用户对于找不到的type都会做默认处理,
  // 所以redux中通过dispatch自执行一次reducer来返回默认值,
  // 注意执行的type不能与用户定义的type相同
  dispatch({type: new Date()})

  return  {
    getState,
    dispatch,
    subscribe
  }
}

三、redux中间件的使用

Redux只是个纯粹的状态管理器,默认只⽀持同步,实现异步任务 ⽐如延迟,⽹络请求,需要中间件的⽀持,⽐如我们使⽤最简单的redux-thunk和 redux-logger 。 l

中间件就是⼀个函数,对 store.dispatch ⽅法进⾏改造,在发出 Action 和执⾏ Reducer 这两步之间,添加了其他功能。

安装中间件redux-thunk和 redux-logger:

npm i --save redux-thunk redux-logger

在 ./src/store/index.js使用中间件:

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

// 创建reducer
function reducer(state = 0, action) {
  switch(action.type) {
    case "ADD": 
      return state + 1;
    case "MINUS": 
      return state - 1;
    default:
      return state;
  }
}
// 创建store仓库
const store = createStore(reducer, applyMiddleware(thunk, logger));
export default store;

在./src/views/ReduxPage.js中添加异步方法:

// 异步任务
addSync = () => {
    store.dispatch((dispatch, getState) => {
      setTimeout(() => {
        store.dispatch({ type: 'ADD'})
      }, 1000)
    })
}

四、手写中间件 

1.applyMiddleware:给只能处理普通对象的dispatch添加传入的中间件的功能。

如上面例子中给dispatch添加thunk来处理函数,添加logger来打印日志。

过程:

  1. 执行时传入中间件作为参数,返回一个函数(enhancer),作为createStore的第二个参数,在createStore中传入createStore和reducer并执行;
  2. 这个函数中创建一个store,将store中的getState和dispatch传递给中间件并执行,每个中间件执行后返回的方法组成一个数组,按顺序执行这个数组中的每个函数;
  3. 由于applyMiddleware是用来加强dispatch,所以enhancer执行后最终返回新的store和dispatch。

 ./src/redux/applyMiddleware.js:

export function applyMiddleware(...middlewares) {
  // 返回一个函数作为createStore的第二个参数,在createStore中传入createStore和reducer并执行
  return (createStore) => (reducer) => {
    let store = createStore(reducer);
    let dispatch = store.dispatch;

    // 将store和dispatch传给中间件,执行中间件
    const midApi = {
      getState: store.getState,
      dispatch: (action, ...arg) => dispatch(action, ...arg) 
    }

    // 将midApi传递给中间件并执行,每个中间件执行后返回一个函数,所有返回的函数组成数组middlewareChain
    let middlewareChain = middlewares.map(middleware => {
      return middleware(midApi);
    });
    console.log(middlewareChain);
    // 合成函数:按顺序执行中间件
    // 获取到功能更强大的dispatch,如thunk返回的dispatch能执行函数形式的action
    // dispatch当做参数传给中间件,中间件中对应的参数是next
    dispatch = compose(...middlewareChain)(store.dispatch);

    // 最终返回最新的store和dispatch
    return {...store, dispatch}
  }
}

// 合成函数
function compose(...funcs) {
  console.log(funcs);
  if (funcs.length === 0) {
    return arg => arg;
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

./src/redux/index.js添加createStore的第二个参数enhancer:

export function createStore(reducer, enhancer) {
   if(enhancer) {
     return enhancer(createStore)(reducer)
   }
  // 创建state
  let currentState;
  // 创建监听数组
  let currentListeners = [];

  // getState直接返回当前state
  function getState() {
    return currentState;
  }
  // dispatch接受一个action,执行reducer返回新的state,再遍历执行监听数组
  function dispatch(action) {
    currentState = reducer(currentState, action);
    currentListeners.forEach(listener => listener())
  }
  // subscribe:将订阅的监听添加到监听数组中, 返回一个取消订阅的方法
  function subscribe(listener) {
    currentListeners.push(listener);
    return () => {
      // 取消订阅:删除监听数组中对应的监听
      const index = currentListeners.indexOf(listener);
      currentListeners.splice(index, 1);
    }
  }

  // 用户会在reducer中设置默认state值,由于用户对于找不到的type都会做默认处理,
  // 所以redux中通过dispatch自执行一次reducer来返回默认值,
  // 注意执行的type不能与用户定义的type相同
  dispatch({type: new Date()})

  return  {
    getState,
    dispatch,
    subscribe
  }
}

2.redux-thunk

作用:判断用户传入的action是否为函数,若为函数则执行函数,否则使用原本的dispatch执行action。

实现:在applyMiddleware中执行时传入getState和dispatch,返回一个函数,跟其他中间件组成返回的函数组成数组,根据传入的dispatch链式执行后返回新的dispatch。

./src/redux/thunk.js:

export function thunk({getState, dispatch}) {
  return (next) => {
    console.log(next); // dispatch
    return (action) => {
      console.log(action);
      if(typeof action === 'function') {
        return action(dispatch, getState);
      }
      return next(action)
    }
  }
}

3.redux-logger

作用:打印日志。

注意:logger中间件应该放在最后,因为前面的中间件处理完dispatch之后再进行打印,否则当logger遇到特殊的action时将无法打印。

./src/redux/logger.js:

export function logger({getState, dispatch}) {
  return (next) => {
    return (action) => {
      console.log("----------------");
      console.log("prev state", getState());
      const rerutnValue = next(action);
      console.log("next state", getState());
      console.log("rerutnValue", rerutnValue);
      console.log("----------------");
      return rerutnValue;
    }
  }
}

4.redux-promise

作用:解决dispatch传入的参数为promise。

在./src/views/ReduxPage.js中添加promise方法:

addPromise = () => {
    store.dispatch(Promise.resolve({ type: 'ADD'}))
}

安装依赖 is-promise:

npm i --save is-promise

创建./src/redux/promiseMiddleware.js:

import isPromise from 'is-promise';
export function promiseMiddleware({getState, dispatch}) {
  return (next) => (action) => 
    isPromise(action) ? action.then(dispatch) : next(action);
}

五、combineReducers的使用

当有多个reducer模块时,可以使用combineReducer将多个reducer组合成一个reducer,再传给createStore。

在./src/redux/index.js中使用combineReducer合并reducer和reducer2:

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

import { createStore} from '../redux';
import { applyMiddleware } from '../redux/applyMiddleware';
import { thunk } from '../redux/thunk';
import { logger } from '../redux/logger';
import { promiseMiddleware } from '../redux/promiseMiddleware';

// 创建reducer
function reducer(state = 0, action) {
  switch(action.type) {
    case "ADD": 
      return state + 1;
    case "MINUS": 
      return state - 1;
    default:
      return state;
  }
}
function reducer2(state = 100, action) {
  switch(action.type) {
    case "ADD": 
      return state + 100;
    default:
      return state;
  }
}
// 创建store仓库
const store = createStore(
  // reducer,
  combineReducers({count: reducer, count2: reducer2}), 
  applyMiddleware(thunk, promiseMiddleware, logger)
);
export default store;

 ./src/views/ReduxPage.js中获取state:

<h1>{store.getState().count}</h1>
<h1>{store.getState().count2}</h1>

六、combineReducers源码

思路:传入的多个reducer组合成的对象reducers,返回一个新的reducer函数等待执行,当这个函数被执行时,遍历并执行之前传进来的reducers对象,将返回的新的state放到nextState中,最终返回nextState对象。

./src/redux/combineReducers.js:

export function combineReducers(reducers) {
  // 返回新的reducer,接收参数初始值和action
  return (state = {}, action) => {
    // 将多个reducer综合成一个nextState,最后返回nextState
    // 遍历所有reducer,将所有reducer放到nextState,
    let nextState = {};
    let hasChanged = false;
    for(let key in reducers) {
      const reducer = reducers[key];
      console.log(reducer);
      nextState[key] = reducer(state[key], action);
      console.log(nextState);
      // 判断nextState是否有改变
      hasChanged = hasChanged || nextState[key] !== state[key];
    }
    hasChanged = Object.keys(nextState).length !== Object.keys(state);
    return hasChanged ? nextState : state;
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值