一 redux 思想

  首先,每一个webApp有且只有一个state tree,为方便管理和跟踪state的变化,也为了减少混乱,redux只允许通过发送(dispatch)action的方式来改变state。旧state在action的作用下产生新state,这个过程叫做reduce,具体逻辑由用户自定义,称为reducer函数。

  另外,redux允许在dispatch action与action到达reducer之间拦截action,做更多的动作,即中间件(middleware),这就像插件机制一样,允许多样的扩展,其中一类重要中间件的功能就是处理异步(与服务端通信)。

二、redux使用

  redux用起来是这样的:用户先写好处理state各个子部分的多个reducers,通过redux提供的combineReducers函数合并成一个rootReducer;然后选好需要的中间件;reducer和中间件作为参数,通过createStore函数创建Store对象。Store是redux运用起来的核心对象,通过store可以获取state--store.getState(), dispatch action以及订阅state变化store.subscribe()。

 const rootReducer = combineReducers(reducers);
const store = createStore(rootReducer, applyMiddleware(...middlewares)) const state0 = store.getState(); const action0 = actionCreator('do something');
store.dispatch(action0); const unSubscribeHandler = store.subscribe(callback);

三、redux代码解析

  参考文章(2)以及源代码(3),实现redux核心逻辑的代码如下(自己掰的):

 const createStore = (reducer, enhancer) => {
if (typeof enhancer === 'function') {
return enhancer(createStore)(reducer);
} let currentState = undefined;
let currentReducer = reducer;
let subscribers = []; function dispatch(action) {
if (typeof currentReducer === 'function') {
currentState = currentReducer(currentState, action);
subscribers.forEach(sfn => sfn());
}
return action;
} function getState() {
return currentState;
} function unSubscribe(fn) {
subscribers = subscribers.filter(sfn => sfn === fn);
} function subscribe(fn) {
if (typeof fn !== 'function') {
throw new Error('subscriber must be a function!');
}
const oldFn = subscribers.find(fnx => fnx === fn);
if (!oldFn) {
subscribers.push(fn);
}
return () => unSubscribe(fn);
} dispatch({ type: 'init' });
return { dispatch, getState, subscribe };
}; // combine multiple reducers into one.
// const rootReducer = combineReducers(reducerX, reducerY, reducerZ);
// const store = createStore(rootReducer);
const combineReducers = reducers => (state = {}, action) => {
const currentState = state;
reducers.forEach((reducer) => {
const partialStateName = reducer.name;
currentState[partialStateName] = reducer(currentState[partialStateName], action);
});
return currentState;
}; // const actionA = ActionCreators.doA('xxx');
// dispatch(actionA);
// const actionB = ActionCreators.doB('yyy');
// dispatch(actionB);
// -->
// const Action = bindActionCreators(ActionCreators, dispatch);
// Action.doA('xxx');
// Action.doB('yyy');
const bindActionCreators = (actions, dispatch) => {
const newActions = {};
for (const key of Object.getOwnPropertyNames(actions)) {
newActions[key] = args => dispatch(actions[key](args));
}
return newActions;
}; // funcs = [fa, fb, fc]
// compose(...funcs)(...args) <=> fa(fb(fc(...args)))
const compose = (...funcs) => {
return funcs.reduce((a, b) => (...args) => a(b(...args)));
}; // 返回一个enhancer: enhancer(createStore)(reducer)
const applyMiddleware = (...middlewares) => {
return createStore => reducer => {
const store = createStore(reducer);
let dispatch = store.dispatch;
//包装dispatch
const middlewareAPI = {
getState: store.getState,
dispatch: action => dispatch(action)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
const enhancedDispatch = compose(...chain)(dispatch);
return { ...store, dispatch: enhancedDispatch };
};
}; const logger = ({ getState, dispatch }) => next => action => {
console.log('logger: before action dispatch: ', getState());
console.log('logger: action: ', action);
const result = next(action);
console.log('logger: after action dispatch: ', getState());
return result;
}; const logger1 = ({ getState, dispatch }) => next => action => {
console.log('logger1: before action dispatch: ', getState());
console.log('logger1: action: ', action);
const result = next(action);
console.log('logger1: after action dispatch: ', getState());
return result;
};

  主要实现这几个函数:

  createStore(reducer, enhancer);  以及store.dispatch()  store.getState()

  combineReducers(reducers);

  applyMiddleware(...middlewares); 以及 compose(...funcs);

  (1) createStore(reducer, enhancer)

  关注enhancer这个参数,enhancer是中间件经过applyMiddleware之后的函数,其实是参数版的装饰器。

enhancer作用在createStore函数上,就是在原来的创建store之外还做了些事情,具体就是改造了store的dispatch函数,加入了中间件。

参考以下代码:比较装饰器decorator直接作为装饰器使用以及作为参数的装饰器。

 function funcA(x, enhancer) {
if (typeof enhancer === 'function') {
return enhancer(funcA)(x);
} console.log('in funcA');
return 2 * x;
} const decorator = (fn) => (x) => {
console.log('before');
let result = fn(x);
console.log('after');
return result * 10;
}; // commonly used decorator
console.log(decorator(funcA)(5));
console.log('===========');
// decorator as argument
console.log(funcA(5, decorator));

  (2)compose(...funcs)

  这个函数的作用是把多个函数(funcs)连成一个函数,其效果等同于逆序地逐个把函数作用于参数上:

    compose(fa, fb, fc)(x)   等价于    fa(fb(fc(x)))

这里的技巧是使用reduce函数:通常的reduce是数据与数据之间reduce,这里用在了函数与函数之间,

注意reduce里面的函数的返回值也是函数,体味一下。

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

  (3)applyMiddleware(...middlewares)

  applyMiddleware(...middlewares)的结果是产生createStore函数的装饰器enhancer。

具体的实现是先创建原生的store,然后增强dispatch函数。

 const applyMiddleware = (...middlewares) => {
return createStore => reducer => {
const store = createStore(reducer);
let dispatch = store.dispatch;
//包装dispatch
const middlewareAPI = {
getState: store.getState,
dispatch: action => dispatch(action)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
const enhancedDispatch = compose(...chain)(dispatch);
return { ...store, dispatch: enhancedDispatch };
};
};

中间件的实际形式是生成器(creator),它接收一个包含dispatch和getState方法的对象,

返回next函数的装饰器(decorator):

  someMiddleware = ({ getState, dispatch }) => next => action => {};

这里面的next,就是下一个next函数的装饰器,前一层装饰后一层,层层装饰直到最后一个next,就是原生的dispatch函数本身了。具体例子参考代码中的logger中间件。

  上面代码中chain中的元素就都是装饰器了,然后经过compose,再作用到dispatch上就产生了增强版的dispatch,

用这个enhancedDispatch替换原生store中的dispatch就大功告成了

  尾声

  为了验证上述逻辑理解的是否正确,加几个middleware玩玩。

const interceptor = ({ getState, dispatch }) => next => action => {
if (action.type === 'DECREMENT') {
console.log(`action: ${action.type} intercepted!`);
return null;
} else {
return next(action);
}
}; const actionModifier = ({ getState, dispatch }) => next => action => {
if (action.type === 'DECREMENT') {
console.log(`action: ${action.type} got and modified to type = INCREMENT!`);
action.type = 'INCREMENT';
}
return next(action);
}; const useNativeDispatch = ({ getState, dispatch }) => next => action => {
if (action.type === 'DECREMENT') {
console.log('shortcut to native dispatch!');
return dispatch(action);
} else {
return next(action);
}
};

  interceptor 会拦截特定类型的action,然后不再向后传播,结果就是之前的中间件还能执行,后面的中间件就不执行了;

  actionModifier 会修改特定类型action的数据,再向后传播,所以前后中间件会看到不同的action;

  useNativeDispatch 在遇到特定类型的action时,会跳过后面所有中间件,直接调用原生dispatch。

完毕!

参考文章:

(1)redux官方basic&advanced tutorial

(2)一起学习造轮子(二):从零开始写一个Redux

(3)redux github

浅析redux的更多相关文章

  1. redux 源码浅析

    redux 源码浅析 redux 版本号: "redux": "4.0.5" redux 作为一个十分常用的状态容器库, 大家都应该见识过, 他很小巧, 只有 ...

  2. 使用Redux管理React数据流要点浅析

    在图中,使用Redux管理React数据流的过程如图所示,Store作为唯一的state树,管理所有组件的state.组件所有的行为通过Actions来触发,然后Action更新Store中的stat ...

  3. redux:applyMiddleware源码解读

    前言: 笔者之前也有一篇关于applyMiddleware的总结.是applyMiddleware的浅析. 现在阅读了一下redux的源码.下面说说我的理解. 概要源码: step 1:  apply ...

  4. [Redux] redux之combineReducers

    combineReducers combineReducer 是将众多的 reducer 合成通过键值映射的对象,并且返回一个 combination 函数传入到 createStore 中 合并后的 ...

  5. Redux,基础

    在学习了React之后, 紧跟着而来的就是Redux了~ 在系统性的学习一个东西的时候, 了解其背景.设计以及解决了什么问题都是非常必要的. 接下来记录的是, 我个人在学习Redux时的一些杂七杂八~ ...

  6. javascript订阅模式浅析和基础实例

    前言 最近在开发redux或者vux的时候,状态管理当中的createStore,以及我们在组件中调用的dispatch传递消息给状态管理中心,去处理一些操作的时候,有些类似我们常见到订阅模式 于是写 ...

  7. RxJS + Redux + React = Amazing!(译一)

    今天,我将Youtube上的<RxJS + Redux + React = Amazing!>翻译(+机译)了下来,以供国内的同学学习,英文听力好的同学可以直接看原版视频: https:/ ...

  8. 通过一个demo了解Redux

    TodoList小demo 效果展示 项目地址 (单向)数据流 数据流是我们的行为与响应的抽象:使用数据流能帮我们明确了行为对应的响应,这和react的状态可预测的思想是不谋而合的. 常见的数据流框架 ...

  9. SQL Server on Linux 理由浅析

    SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...

随机推荐

  1. Chapter10(泛型算法)--C++Prime笔记

    关键:算法通过在迭代器上进行操作来实现类型无关.算法不改变所操作序列的大小. 1.算法大多都定义在algorithm头文件中,标准库还在头文件numeric中定义了一组数值泛型算法. 2.泛型算法永远 ...

  2. linux那些事

    useradd -m -d /home/changp -Gusers,dialout,video account_name 创建新的账号 passwd account_name 修改指定账号的密码

  3. svnsync备份

    参考:https://www.cnblogs.com/zz0412/p/svnsync.html https://blog.csdn.net/windone0109/article/details/4 ...

  4. tomcat修改java不重启

    修改tomcat   server.xml 找到项目的Context配置 <Context docBase="项目名" path="/项目路径" relo ...

  5. java selenium常用API(WebElement、iFrame、select、alert、浏览器窗口、事件、js) 一

     WebElement相关方法 1.点击操作 WebElement button = driver.findElement(By.id("login")); button.clic ...

  6. R8—批量生成文件夹,批量读取文件夹名称+R文件管理系统操作函数

    一. 批量生成文件夹,批量读取文件夹名称 今日,工作中遇到这样一个问题:boss给我们提供了200多家公司的ID代码(如6007.7920等),需要根据这些ID号去搜索下载新闻,从而将下载到的新闻存到 ...

  7. 分享自己新做的vim colorscheme

    把下面的内容保存成darkslategrey.vim,放入~/.vim/colors目录即可. " Vim color file " Maintainer: jiqing() &q ...

  8. EOJ Monthly 2019.2 (based on February Selection) D.进制转换

    题目链接: https://acm.ecnu.edu.cn/contest/140/problem/D/ 题目: 思路: 我们知道一个数在某一个进制k下末尾零的个数x就是这个数整除kx,这题要求刚好末 ...

  9. Oracle错误: ORA-01722 无效数字

    ORA-01722: 无效数字 主要原因是: 1.对于两个类型不匹配(一个数字类型,一个非数字类型,同下)的值进行赋值操作; 2.两个类型不匹配的值进行比较操作(例如,"="); ...

  10. 【数据库】软件安全测试之SQL注入

    这些年我们发现越来越多的公司开始注重安全测试了,为什么?因为安全测试可以在某种程度上可以排查掉你项目的一些安全漏洞,这样你的系统上线后才会相对安全,才有可能尽量避免来自外部的攻击.每一年互联网都会发生 ...