理解 Redux 的最好方式,是自己写一个

react-redux 是 React 生态中比较(如果不是最的话)流行的一种状态管理方式,而它所依托的 redux,继承了 flux 的衣钵,并且引入了单一数据源、state 为只读、只能通过纯函数修改三大原则。一个最简单的 redux 应用示例:

// https://cn.redux.js.org/
import { createStore } from 'redux'

/**
 * 这是一个 reducer,形式为 (state, action) => state 的纯函数。
 * 描述了 action 如何把 state 转变成下一个 state。
 *
 * state 的形式取决于你,可以是基本类型、数组、对象、
 * 甚至是 Immutable.js 生成的数据结构。惟一的要点是
 * 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
 *
 * 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
 * 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
 */
function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1
    case 'DECREMENT':
      return state - 1
    default:
      return state
  }
}

// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter)

// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() => console.log(store.getState()))

// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' })
// 1
store.dispatch({ type: 'INCREMENT' })
// 2
store.dispatch({ type: 'DECREMENT' })
// 1
复制代码

createStore 是 redux 中最基础的 API,它的作用就是返回一个 store;而这个 store 有三个方法 —— dispatchsubscribegetState。其实从方法名上,我们大致可以推测出 redux 应用了订阅发布模式;该模式可以直接套用下面的模板代码:

// 开始
// createStore 是一个闭包,维护了 currentState, listeners 私有性;
// 外层想要获取 state,只能通过 getState;
// listeners 无法从外层修改
function createStore(reducer, preloadedState) {
  // 每次调用 subscribe,就会向 listeners 数组 push 一个 listener;
  // 每次调用 dispatch,listeners 数组中的函数会依次执行;
  const listeners = [];
  let currentReducer = reducer;
  let currentState = preloadedState;

  function dispatch(action) {
    // 正如示例代码中所展现的,reducer(即 counter) 函数接受 state,action 两个参数
    // action 是一个简单对象,有一个 type 属性;
    // reducer 中根据 type 执行对应操作,返回新的 state;
    currentState = currentReducer(currentState, action);
    // 执行 listeners 数组中的所有函数
    listeners.forEach(listener => listener());
  }

  function subscribe(listener) {
    listeners.push(listener);
    // 返回一个取消订阅的函数
    return function unsubscribe() {
      let index = listeners.indexOf(listener);
      listeners.splice(index, 1);
    };
  }

  // 这个函数的作用就是返回 state
  function getState() {
    return currentState;
  }

  return {
    dispatch,
    subscribe,
    getState
  };
}

function counter(state = 0, action) {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
}

// 测试一下
let store = createStore(counter);
store.subscribe(() => console.log(store.getState()));
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
复制代码

上面的代码只是为了大致解释一下 redux 的运行机制,其实是有不少问题的,比如 action 只能是一个简单对象,但我们的代码中并没有进行检测;我们暂且不去关注这些问题,而是先去实现功能。接下来,修改 dispatch,使它支持函数参数:

function dispatch(action) {
  // 如果传入一个函数,则为这个函数注入 dispatch 和 getState 方法,
  // 并且执行这个 action
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }
  // 正如示例代码中所展现的,reducer(即 counter) 函数接受 state,action 两个参数
  // action 是一个简单对象,有一个 type 属性;
  // reducer 中根据 type 执行对应操作,返回新的 state;
  currentState = currentReducer(currentState, action);
  // 执行 listeners 数组中的所有函数
  listeners.forEach(listener => listener());
  return action;
}
复制代码

用异步的方式,测试一下:

function counter(state = 0, action) {
 switch (action.type) {
   case 'INCREMENT':
     return state + 1;
   case 'DECREMENT':
     return state - 1;
   default:
     return state;
 }
}

function asyncAdd(dispatch) {
 return new Promise((resolve, reject) => {
   setTimeout(() => {
     resolve(
       dispatch({
         type: 'INCREMENT'
       })
     );
   }, 1000);
 });
}
// 测试一下
let store = createStore(counter);
store.subscribe(() => console.log(store.getState()));
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch(asyncAdd).then(() => store.dispatch(asyncAdd)); // 1s 后 3, 2s 后 4
复制代码

现在,我们的代码已经具备了 redux + redux-thunk 的功能,而且没有增加新的 API。但我们是用入侵的方式修改了 dispatch,如果要增加其他新功能怎么办?继续修改 dispatch 么?显然不能这么干。redux 使用中间件的机制,来修改 dispatch:

function compose(...funcs) {
  if (funcs.length === 0) {
    return args => args;
  }
  if (funcs.length === 1) {
    return funcs[0];
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args);
    let dispatch = () => {
      throw new Error('not valid');
    };

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    };

    const chain = middlewares.map(middleware => middleware(middlewareAPI));
    // applyMiddleware 本质上,还是需要去覆盖原来的 dispatch 方法
    dispatch = compose(...chain)(store.dispatch);

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

// 我们的 thunk
const thunk = ({ dispatch, getState }) => next => action => {

  // 这段代码,和我们上面暴力修改 dispath 的方法,其实是一样的
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }

  return next(action);
};
复制代码

以上就是 redux 以及 redux-thunk 的简单实现,有不少 bug,希望对大家理解 redux 有点帮助;也建议大家去看看 redux 的源码,其实源码也很短,并且注释很完整。

转载于:https://juejin.im/post/5cd0d90d51882541ad5a464f

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值