摘要:假如说是士兵的话那么就是将军,来管理士兵的状态。对于来说,也就是一个最外层的容器,全部作为该容器组件的类似于,这样一来发生变化,就会重新渲染整个页面,从而达到更新的效果。得益于,每次并不用更新整个树。规定,对外不暴露任何修改数据的接口。
redux 为什么引入redux
以react来说,state可以包含内部的状态以及业务数据,对于一个复杂的应用来说,state非常难以管理,一个model的变化可能引起另一个model的变化...依次下去,造成了难以维护的情况,别人很难一下子摸透你的state到底是怎么变得?所以需要引入一个东西来做数据管理,使数据变化变得清晰,redux做的就是这件事情。假如说react是士兵的话那么redux就是将军,来管理士兵的状态。
Flux与redux FluxFlux是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(类似于
flux规定store,store对外不暴露任何修改数据的接口。
reduxfacebook提供的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的实现:
composecompose源自于函数式编程,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比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可以使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返回一个键值对。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/81465.html
摘要:使得在变化和异步中可预测。它是数据的唯一来源。指定了应用状态的变化如何响应并发送到的,只是描述了有事情发生了这一事实,并没有描述应用如何更新。更新的函数称为,它是一个纯函数,接受旧的和,返回新的。是和之间的桥梁,是把它们联系到一起的对象。 为什么使用redux 随着前端单页面开发越来越复杂,javascript需要管理越来越多的状态state。如果一个model的变化引起另一个mode...
摘要:调用链中最后一个会接受真实的的方法作为参数,并借此结束调用链。总结我们常用的一般是除了和之外的方法,那个理解明白了,对于以后出现的问题会有很大帮助,本文只是针对最基础的进行解析,之后有机会继续解析对他的封装 前言 虽然一直使用redux+react-redux,但是并没有真正去讲redux最基础的部分理解透彻,我觉得理解明白redux会对react-redux有一个透彻的理解。 其实,...
摘要:本周精读内容是重新思考。数据流对数据缓存,性能优化,开发体验优化都有进一步施展的空间,拥抱插件生态是一个良好的发展方向。 本周精读内容是 《重新思考 Redux》。 1 引言 《重新思考 Redux》是 rematch 作者 Shawn McKay 写的一篇干货软文。 dva 之后,有许多基于 redux 的状态管理框架,但大部分都很局限,甚至是倒退。但直到看到了 rematch,总算...
阅读 3375·2023-04-26 02:04
阅读 1520·2021-11-04 16:07
阅读 3999·2021-09-22 15:09
阅读 957·2019-08-30 15:54
阅读 2133·2019-08-29 14:11
阅读 2797·2019-08-26 12:19
阅读 2505·2019-08-26 12:00
阅读 1060·2019-08-26 10:27