
原文发布在我的个人博客 解读只有99行的Redux(一) | 以太空间
一、概述
随着React这个极具革命式函数式思想的前端框架的诞生,Flux模式的前端状态管理框架也随之出现,其中比较著名的就是Flux、Redux和Mbox。Flux是Facebook开发的一种设计模式,旨在保持数据单向流动,当然Flux也存在一些小问题,所以Redux和其他的类Flux库应运而生,它们在实现了Flux思想的同时又具备了自己的特点。
Redux由Dan Abramov和Andrew Clark一起开发,因为Redux的缘故,它们都被邀请加入了Facebook的React团队。在Dan Abramov的github gist页面上有一个slim-redux(代码附在本文末尾),去除了一些复杂的类型判断和错误处理代码,但完整地实现了Redux的所有功能,我们下面就来对这个只有99行的Redux进行解读。
二、createStore
解读
我们使用Redux时候最频繁使用的就是这个createStore
函数了,一般我们都会这样使用
import { createStore } from 'redux';
const store = createStore(reducer);
reducer
这个参数是用户事先定义的数据状态处理函数,一般会以类似下面的方式声明:
// reducer接受state和action并返回新的state
function reducer (state = [], action) {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
];
default:
return state;
}
};
reducer
的主体是一个switch
结构的运算,它只是根据传入的状态数据state
和action
来判断返回一个新的state
。reducer
必须是一个纯函数,纯函数主要的含义就是它不可以修改影响输入值,并且没有副作用,副作用指的是例如函数中一些异步调用或者会影响函数作用域之外的变量一类的操作。
另外,注意我们返回新的state时使用的展开操作符的方法,在上面的示例中,这样返回的是一个全新的数组,而不是修改了传入的数组。这也就是我们使用 React 技术栈时尤其需要注意的一点:保证数据的immutability不可变性。
相信大家也知道,createStore
这个函数还有第二个的参数,是store
的初始状态,也就是说craeteStore
函数原型是这样的
function createStore(reducer, initialState) {...}
然后再来看createStore
内部这段代码:
var currentReducer = reducer;
var currentState = initialState;
var listeners = [];
var isDispatching = false;
function getState() {
return currentState;
}
前两行就是将传入的reducer
参数和state
参数进行保存,第三行声明了保存监听函数的数组,第四行的isDispatching
是个标识型变量,具体用途下面会说到。
我们知道,当我们调用createStore
函数之后,其返回结果(一般写作store
)有个获取当前store
内部数据状态的成员函数getState
,该成员函数实现方法就是直接return内部的currentState
,so easy~
接着看subscribe
函数的实现:
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
当开发者对store
调用subscribe
函数的时候,其内部会把传入的监听函数listener
参数push到专门存放监听函数的数组listeners
里,然后返回一个能取消传入的监听函数的函数unsubscribe
作为返回结果,这个unsubscribe
实现原理就是将listener
记录下来,然后在调用的时候将其从listeners
数组里面剔除。
接下来是最重量级选手dispatch
函数的实现代码:
function dispatch(action) {
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
listeners.slice().forEach(listener => listener());
return action;
}
首先来解释一下前面提到的isDispatching
,当开发者对store
派发(dispatch
)一个action
时,dispatch
内部会调用reducer
(currentReducer
)并利用传入的action
对store
内部的状态数据(currentState
)进行处理,但是这个过程可能是比较耗时的,所以为了避免对正在进行reducer
处理的store
再次进行reducer
处理,专门用isDispatching
来标识当前store
内部是否正在进行reducer
处理,如果isDispatching
为true
的话,就不会再对store
进行reducer
处理,起到一个加锁的作用。
所以disptach
内部会首先检测isDispatching
是否正处于一个锁死的状态,如果isDispatching
为true
,说明锁死,正在进行reducer
处理,就直接抛出错误。然后在try
块内部,首先将isDispatching
置为true
,对其加锁,再对store
内部的状态数据进行处理,处理完后再把isDispatching
置为false
,将锁打开。
最后遍历listeners
,调用所有的监听函数,并返回action
。
下面这部分是createStore
函数的剩余部分:
function replaceReducer(nextReducer) {
currentReducer = nextReducer;
dispatch({ type: '@@redux/INIT' });
}
dispatch({ type: '@@redux/INIT' });
return { dispatch, subscribe, getState, replaceReducer };
在调用createStore
的时候,其内部就派发(dispatch
)一个初始action({ type: '@@redux/INIT' }
),进行一些初始化的操作。此外,store
还有一个用的比较少的成员函数replaceReducer
,作用是替换reducer
函数,原理很简单,就是将currentReducer
指向新的reducer
函数,并再次派发一个初始action
。
上面这部分代码的最后一行罗列出了store
所有成员函数。
以下是解读只有99行的Redux系列的其他两篇文章
网友评论