您的位置:首页 > Web前端 > React

理解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
返回一个键值对。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  react redux 源码解读