实现一个redux

大纲
1、store、state、action、store.dispatch、reducer的概念
2、redux 单向数据流流程说明
3、实现一个 redux
4、中间件日志打印及异步调用实现

代码地址: https://github.com/huangwenjuning/offer-coming-soon/tree/master/redux

基础概念

  • store
    整个项目只能有一个 store,store 可以看做是一个数据容器, 用于保存数据。
    redux 提供了 createStore 方法用于生成 store。
  • state
    state 表示的是当前时刻的数据。
    redux 提供了 getState 方法用于获取当前时刻的数据集合。
  • action
    action 表示的是当前要发生改变。即 view 层发出通知,表示 state 将要以某种规则发生变化。
    action 是一个对象,它具有 type 和 payload 的属性:
    const action = {
        type: 'ADD_TODO',
        payload: 'Learn'
    }
  • store.dispatch
    store.dispatch 是 view 分发 action 的唯一方法,它接收一个 action。
    store.dispatch({
        type: 'ADD_TODO',
        palyload: 'Learn'
    })
  • reducer
    store 接收到 action 后,必须返回一个新的 state, 这样 view 才会发生更新,这种 state 的计算过程就是 reducer。

    Reducer 是一个纯函数,它接收旧的 state 和 action 为参数,并返回一个新的 state。
    只要传入的参数相同,那么得到结果也必定相同。因此保持 reducer 纯净非常重要。
    我们不应该在 reducer 中做这些具有副作用的操作:
    (1)修改传⼊参数;
    (2)执⾏有副作⽤的操作,如 API 请求;
    (3)调⽤非纯函数,如 Date.now() 或 Math.random()。

redux 工作流程

redux流程图
我们简述一下 redux 完成一次数据更新的过程:

A. 首先,用户通过操作发出 Action ——> store.dispatch
B. store 会自动调用 reducer 方法,并传入当前 action 与 state,reducer 经过预定数据处理之后,会返回一个新的 state。 ——> reducer(action, state)
C. state 发生变化,store 调用监听函数。 ——> store.subscribe()

实现redux

redux 提供了以下几个 api 我们先了解一下:

  • createStore: 用于创建一个 store

    const store = createStore(reducer);
    
  • reducer: 用于数据的初始化以及变更数据状态的函数

    const reducer = (state = 0, action) => {
        switch(action.type) {
            case 'ADD':
              return state+1;
            case 'MINUS':
              return state-1;
            case 'AsyncAdd':
              return state+1;
            default:
              return 0;
        }
    };
    
  • getState(): 用于获取当前时刻的 state 数据

  • dispatch(): 用于分发 action

    store.dispatch({type: 'ADD'});
    
  • subscribe(): 用于数据订阅,数据更新时调用执行

createStore.js

我们创建一个 createStore 方法,该方法会返回 getState、dispatch 和 subscibe() 方法。

const createStore = (reducer, enhancer) => {
  // 当前数据
  let state;
  // 进行依赖收集(订阅者)
  let listeners = [];
  
  if (enhancer) {
    // 中间件,对 dispatch 的增强过程
    return enhancer(createStore)(reducer);
  }

  // 获取当前的数据
  const getState = () => {
    return state;
  };

  // 进行订阅和退订
  const subscribe = (listener) => {
    listeners.push(listener);
    return () => {
      listeners.filter((item) => item !== listener);
    }
  };

  // 分发 action
  const dispatch = (action) => {
    state = reducer(state, action);
    // state 更新,通知订阅者
    listeners.forEach((listener) => listener());
    return action;
  };

  // 手动执行一次,用于初始化
  dispatch({});

  return {
    getState,
    subscribe,
    dispatch,
  }
}

applyMiddleware.js

const applyMiddleware = (...middlewares) => {
  return createStore => reducer => {
    const store = createStore(reducer);
    let dispatch = store.dispatch;
    const midApi = {
      getState: store.getState,
      dispatch: (...arg) => dispatch(...arg),
    }
    // 每一个中间件都接收 getState 和 dispatch 方法
    const middlewareChain = middlewares.map((mid) => mid(midApi));
    
    // 将中间件组合成一个函数进行调用
    dispatch = compose(...middlewareChain)(dispatch);
    
    return {
      ...store,
      dispatch,
    }
  }
};

var compose = (...mid) => {
  if (mid.length === 0) {
    // 如果没有传入中间件,直接返回参数
    return arg => arg;
  }

  if (mid.length === 1) {
    // 如果只有一个中间件,则直接返回
    return mid[0]
  }
  // 将中间件聚合成 f1(f2(f3(args)) 形式并返回
  return mid.reduce((l, r) => (...args) => l(r(...args)));
}

combineReducers.js

const combineReducers = (reducers) => {
  return (state = {}, action) => {
    const reducerKeys = Object.keys(reducers);
    // 新的 state
    const newState = {};
    // 判断 state 是否发生变化
    let hasChanged = false;
    for (let i = 0; i < reducerKeys.length; i++) {
      const key = reducerKeys[i];
      const reducer = reducers[key];
      // 获取当前 key 旧的 state
      const preStateForKey = state[key];
      // 得到当前 key 新的 state
      const nextStateForKey = reducer(preStateForKey, action);

      if (typeof nextStateForKey !== 'undefined') {
        newState[key] = nextStateForKey;
        hasChanged = hasChanged || nextStateForKey !== preStateForKey;
      }
    }
    hasChanged = hasChanged || Object.keys(reducers).length !== Object.keys(state).length;
    return hasChanged ? newState : state;
  }
};

中间件日志记录和异步调用实现

thunk.js

进行异步调用时,我们这样分发 action:

store.dispatch((dispatch) => {
    setTimeout(() => {
        dispatch({type: 'AsyncAdd'});
    }, 3000);
});
 const thunk = ({ getState, dispatch }) =>
  (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState);
    }

    return next(action);
  }

logger.js

const logger = ({ getState }) => {
  return (next) => (action) => {
      const state = getState();
      console.log('=====');
      console.log('preState', state);
      console.log('action', action);
      next(action);
      const curState = getState();
      console.log('curState', curState);
      console.log('=====');
    }
}

参考

redux官方实现

写在后面:首发于个人博客http://agangxuezhang.cn/single-minded

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值