理解redux
2017-02-14 20:40
399 查看
redux
为什么引入redux
以react来说,
state可以包含内部的状态以及业务数据,对于一个复杂的应用来说,
state非常难以管理,一个
model的变化可能引起另一个
model的变化…依次下去,造成了难以维护的情况,别人很难一下子摸透你的
state到底是怎么变得?所以需要引入一个东西来做数据管理,使数据变化变得清晰,
redux做的就是这件事情。假如说
react是士兵的话那么
redux就是将军,来管理士兵的状态。
Flux与redux
Flux
Flux是facebook提出的一种架构思想,与react一样,强调的是单向数据流,由三大部分组成:
-
store来保存数据,一个数据对应一个
store,每一个
store有一个监听器
-
dispatcher,含有两个方法:
-
register,注册事件
-
dispatch,分发一个
action使
store变化
-
view,与
flux搭配不仅仅是
react还可以是
vue等,为当前页面数据的一个展现,与数据保持一致
flux并不是一个
mvc框架,
flux没有明确的
contoller,但是却有着一个
controller-view,类似于
mvvm中的
vm(
view=vm(model))。对于
react来说,也就是一个最外层的容器,
store全部作为该容器组件的
state(类似于
<Provider>),这样一来
state发生变化,就会重新渲染整个页面,从而达到更新
view的效果。得益于
virtual dom,每次并不用更新整个
dom树。(每次触发重绘会先在内存中对新的
dom树和老的
dom树进行
diff,把变化放入到
patches中,最后统一更改)。
flux规定
store,
store对外不暴露任何修改数据的接口。
redux
facebook提供的flux包基本只有一个
dispatcher的实现,意味着我们需要为对每一个
store进行一次定义并且创建一个
dispatcher实例,需要
register一次,
dispatch时也需要区分不同的
store(听着就麻烦)。基于
flux思想的
redux为我们做了简化。
redux与Flux的区别
redux提倡的是单一数据源,也就是一个应用就有一个
store,包含着许多的
reducer,
reducer根据传入的
action来决定怎么改变当前状态。关于
redux,大家可以直接去看文档,说的很清楚详细,下面来看一下
redux的源码:
入口
index.js为
redux的入口文件,暴露出来了
redux所提供的
API:
export { createStore, // 创建store combineReducers, // 合并reducer bindActionCreators, applyMiddleware, compose }
其中还有一个
isCrushed函数,其作用就是做环境的检测,假如在非生产环境中使用了压缩的
redux,则提出警告,判断方式也很简单:
isCrushed.name !== 'isCrushed' // 压缩后函数名字会变短
下面来一个一个的看
redux暴露出
API的实现:
compose
compose源自于函数式编程,
redux将其用于用于
store的增强,将传入的函数从右到左的执行,避免了层层嵌套,举个例子:
const x = 10, add = x => x + 10, sub = x => x - 20, sup = x => x * 10; // 原生方式嵌套实现 add( sup( sub( add(x) ) ) ); // 利用compose改进 const fn = compose(add, sup, sub, add); fn(x);
对比上面代码,利用
compose会使代码变得清晰,
compose就是函数式编程中的组合:
export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg; } if (funcs.length === 1) { return funcs[0]; } return funcs.reduce( (a, b) => ( (...args) => a(b(...args)) ) ); };
createStore
什么是store
redux强调的是单一数据源,把所有的
state放入到一棵
object tree中,这棵树只能唯一的存在于一个
store中,也就是说
redux强调整个应用只有一个
store。
store可以包含
解析依赖函数
该模块依赖了lodash的
isPlainObject,该函数用来判断对象是否继承了自定义类,实现起来非常简单:
function isPlainObject(val) { // 非对象的情况直接返回false if (!isObjectLike(value) || baseGetTag(value) != '[object Object]') { return false } const proto = Object.getPrototypeOf(value) // 针对Object.create(null)创建出来的对象 if (proto === null) { return true } const Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor // prototype.constructor === Object return typeof Ctor == 'function' && Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString }
主体函数
// 内部的action,用于reset export const ActionTypes = { INIT: '@@redux/INIT' }; /* * 创建store * reducer reducer函数 * preloadedState 初始状态 * enhancer 增强函数,对createStoren能力进行增强,如devtools */ export default function createStore(reducer, preloadedState, enhancer) { // 参数修正 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState; preloadedState = undefined; } if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.'); } // 返回已经增强后的store return enhancer(createStore)(reducer, preloadedState); } if (typeof reducer !== 'function') { throw new Error('Expected the reducer to be a function.'); } // 记录当前值 let currentReducer = reducer; let currentState = preloadedState; // 监听store变化的监听器(一些回调函数) let currentListeners = []; let nextListeners = currentListeners; // 是否处于dispatch的过程中 let isDispatching = false; /**** 看下面文字解释 ****/ function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice(); } } // 给store新增一个监听器 function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected listener to be a function.'); } let isSubscribed = true; ensureCanMutateNextListeners(); nextListeners.push(listener); // 返回一个卸载方法 return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false; ensureCanMutateNextListeners(); const index = nextListeners.index(listener); nextListeners.splice(index, 1); }; } // 返回当前的状态 function getState() { return currentState; } function dispatch(action) { // 一些类型检测 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true; // 根据recuder来更新当前状态 currentState = currentReducer(currentState, action); } finally { isDispatching = false; } const listeners = currentListeners = nextListeners; // 发布事件 for (let i = 0; i < listeners.length; i++) { const listener = listeners; listener(); } return action; } // 更新当前reducer,重置store function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.'); } currentReducer = nextReducer; dispatch({ type: ActionTypes.INIT }); } // 初始化store dispatch({ type: ActionTypes.INIT }); // 与Rxjs这种交互,根本看不懂-- function observable() { ... } return { getState, subscribe, dispatch, replaceReducer, [$$observable]: observable }; };
需要注意的有以下几点:
- ##### 为什么需要两个数组来保存监听函数
观察源码可以发现,
currentListeners与
nextListeners存储的都是监听函数,这样做的目的是保证
dispatch的过程不发生错误,加入使用一个队列的话,当执行过程中有监听函数被移除时,则会发生错误,如:当前监听函数队列为:
[a, b, c],当执行回调函数
a后将其移除,则队列发生改变
[b, c],而利用
for循环来执行回调,执行到
i = 2时会抛出错误。
##### 为什么不用
forEach
forEach比
for差在:
ie8不兼容
forEach(ie8 is out)
forEach不能提前终止,但是在
dispatch中无此问题
性能,这是
forEach最大的问题,
for要比
forEach快的多的多(差不多一倍左右),查看v8代码也可以感觉出,
forEach本质上做的还是
for循环,每次loop进行一次判断和函数调用,自然速度会慢。
applyMiddleware
中间件,express与
koa也就中间件,
express中的中间件处理的请求到响应的这一过程,
redux中的中间件处理的是从
action发出到
reducer接收到
action这一过程,在这个过程中可以利用中间件进行写日志,处理异步操作等过程,作用就是来增强
createStore,给其添加更多的功能。先来看下下面这个简化的例子:
const calc = (obj) => obj.value + 20; // 简单的模拟下stroe const obj = { calc }; // 加强fn函数 function applyMiddleware(fn, middlewares) { middlewares = middlewares.slice(); middlewares.reverse(); // 每次改变fn,一次扩展一个功能 let { calc } = obj; middlewares.forEach( middleware => calc = middleware(obj)(calc) ); return calc; } // arrow function is cool!!! const logger = obj => next => num => { console.log(`num is ${num.value}`); let result = next(num); console.log(`finish calc, result is ${result}`); return result; }; const check = obj => next => num => { console.log(`typeof num.value is ${typeof num.value}`); let result = next(num); return result; }; const fn = applyMiddleware(obj, [check, logger]); fn({ value: 50 });
在上面简单的例子中为
calc做了两个功能扩展,执行起来就是一个高阶函数
check->logger->calc,理解了这个再来看看
redux的
applyMiddleware的实现:
export default function applyMiddleware(...middlewares) { // 利用闭包保存下来了middlewares return (createStore) => (reducer, preloadadState, enhancer) => { const store = createStore(reducer, preloadadState, enhancer); let dispatch = store.dispatch; let chain = []; // middleware不会改变store,利用闭包保存 const middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) }; // chain中的元素仍未函数 // 接受的参数为`next` => 下一个中间件 chain = middlewares.map(middleware => middleware(middlewareAPI)); // 本身的dispatch放在最后执行 // dispatch仍未函数,接受的参数为`action` // 返回的是一个高阶函数,需要注意的是中间件并不会改变原本的action // dispatch变成了一个升级版 dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch }; }; };
附一张图:
applyMiddleware代码虽少,但是却是最难理解的部分
redux-thunk
redux-thunk是一个十分剪短有用的中间件,尤其是在异步应用中,利用
redux-thunk可以使
action变为一个函数,而不仅仅是一个对象,并且在该函数中仍然可以触发
dispatch:
// 让action可以成为函数 function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { // action为函数类型,执行action,dispatch结束 if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; }
combineReducers
combineReducers用来合并多个
reducer:
assertReducerSanity来验证reducer
利用createStore文件中定义的
ActionTypes来进行初始化
state,也就是定义的
reducer会在一开始执行一遍,而后对得到
state进行检测,为
undefined抛出异常,同时利用随机字符串测试,防止其处理
type为
@@redux/INIT而未处理
default的情况。
combineReducers
将我们所写的reducer进行合并,返回一个函数,每次
dispatch时,执行函数,遍历所有的
reducer,计算出最终的
state:
export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) const finalReducers = {} // 有效reducer集合 // 验证reducer,必须是纯函数才有效 for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } } if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (NODE_ENV !== 'production') { unexpectedKeyCache = {} } let sanityError try { // 检测reducer assertReducerSanity(finalReducers) } catch (e) { sanityError = e } return function combination(state = {}, action) { if (sanityError) { throw sanityError } // getUnexpectedStateShapeWarningMessage // 检测state类型是否为0继承对象 // 用于找出多余的redcuer并给出警告 if (NODE_ENV !== 'production') { const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } let hasChanged = false const nextState = {} // 每次发出一个action会遍历所有的reducer for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] // 保留之前的state与新生成的state做对比 const reducer = finalReducers[key] const previousStateForKey = state[key] const next StateForKey = reducer(previousStateForKey, action) // state为undefined抛出异常 if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey // 比较是否数据发生变化 hasChanged = hasChanged || nextStateForKey !== previousStateForKey } // 未发生变化返回之前的数据 return hasChanged ? nextState : state } }
bindActionCreators
这个基本上的作用不大,唯一运用的情况就是将其作为props传入子组件,对于子组件来说可以全然不知
redux的存在。如果利用
UI库的组件来操作页面状态,
bindActionCreators是一个很好的选择,实现很简单:
function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) }
bindActionCreators仅仅是遍历所有的
actions返回一个键值对。
相关文章推荐
- redux深入理解
- 理解 React,但不理解 Redux,该如何通俗易懂的理解 Redux?
- redux教程(一)——理解redux
- 初识React-Redux之粗暴理解入门
- Redux入门者对Redux的理解和使用
- 理解Javascript的状态容器Redux
- redux-applyMiddleware实现理解+自定义中间件
- 一个不错的redux教程, 通俗理解redux意义
- react-redux的理解
- 理解Redux
- 如何快速学习理解Redux
- 对于 Redux 的理解
- 新手关于 export default connect react-redux 的理解
- 对于Redux的理解
- redux深入理解之中间件(middleware)
- 深入理解react-redux
- 深度理解redux(一)
- 深入理解Redux:10个来自专家的Redux实践建议
- 理解 React,但不理解 Redux,该如何通俗易懂的理解 Redux?
- react native 中的redux 理解