概述
随着应用程序单页面需求的越来越复杂,应用状态的管理也变得越来越混乱,而Redux的就是为解决这一问题而出现的。在一个大型的应用程序中,应用的状态不仅包括从服务器获取的数据,还包括本地创建的数据,以及反应本地UI状态的数据,而Redux正是为解决这一复杂问题而存在的。
redux作为一种单向数据流的实现,配合react非常好用,尤其是在项目比较大,逻辑比较复杂的时候,单项数据流的思想能使数据的流向、变化都能得到清晰的控制,并且能很好的划分业务逻辑和视图逻辑。下图是redux的基本运作的流程。
如上图所示,该图展示了Redux框架数据的基本工作流程。简单来说,首先由view dispatch拦截action,然后执行对应reducer并更新到store中,最终views会根据store数据的改变执行界面的刷新渲染操作。
同时,作为一款应用状态管理框架,为了让应用的状态管理不再错综复杂,使用Redux时应遵循三大基本原则,否则应用程序很容易出现难以察觉的问题。这三大原则包括: • 单一数据源 整个应用的State被存储在一个状态树中,且只存在于唯一的Store中。 • State是只读的 对于Redux来说,任何时候都不能直接修改state,唯一改变state的方法就是通过触发action来间接的修改。而这一看似繁琐的状态修改方式实际上反映了Rudux状态管理流程的核心思想,并因此保证了大型应用中状态的有效管理。 • 应用状态的改变通过纯函数来完成 Redux使用纯函数方式来执行状态的修改,Action表明了修改状态值的意图,而真正执行状态修改的则是Reducer。且Reducer必须是一个纯函数,当Reducer接收到Action时,Action并不能直接修改State的值,而是通过创建一个新的状态对象来返回修改的状态。
redux的三大元素
和Flux框架不同,Redux框架主要由Action、Reducer和Store三大元素组成。
Action
Action是一个普通的JavaScript对象,其中的type属性是必须的,用来表示Action的名称,type一般被定义为普通的字符串常量。为了方便管理,一般通过action creator来创建action,action creator是一个返回action的函数。
在Redux中,State的变化会导致View的变化,而State状态的改变是通过接触View来触发具体的Action动作的,根据View触发产生的Action动作的不同,就会产生不同的State结果。可以定义一个函数来生成不同的Action,这个函数就被称为action creator。例如:
代码语言:javascript复制const ADD_TODO = '添加事件 TODO';
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
const action = addTodo('Learn Redux');
上面代码中,addTodo就是一个action creator。但当应用程序的规模越来越大时,建议使用单独的模块或文件来存放action。
Reducer
当Store收到action以后,必须返回一个新的State才能触发View的变化,State计算的过程即被称为Reducer。Reducer本质上是一个函数,它接受Action和当前State作为参数,并返回一个新的State。格式如下:
代码语言:javascript复制const reducer = function (state, action) {
// ...
return new_state;
};
为了保持reducer函数的纯净,请不要在reducer中执行如下的一些操作: • 修改传入参数; • 执行有副作用的操作,如API请求和路由跳转; • 调用非纯函数,如 Date.now() 或 Math.random()
对于Reducer来说,整个应用的初始状态就可以直接作为State的默认值。例如:
代码语言:javascript复制const defaultState = 0;
const reducer = (state = defaultState, action) => {
switch (action.type) {
case 'ADD':
return state action.payload;
default:
return state;
}
};
//手动调用
const state = reducer(1, {
type: 'ADD',
payload: 2
});
不过,在实际使用过程中,reducer函数并不需要像上面那样进行手动调用,Store的store.dispatch方法会触发Reducer的自动执行。为此,只需要在生成Store的时候将Reducer传入createStore方法即可。例如:
代码语言:javascript复制import { createStore } from 'redux';
const store = createStore(reducer);
在上面的代码中,createStore函数接受Reducer作为参数,该函数返回一个新的Store,以后每当store.dispatch发送过来一个新的Action,就会自动调用Reducer得到新的State。
Store
Store就是数据保存的地方,可以把它看成一个容器,整个应用中只能有一个Store。同时,Store还具有将Action和Reducers联系在一起的作用。Store具有以下的一些功能: • 维持应用的 state; • 提供getState()方法获取state; • 提供dispatch(action)方法更新state; • 通过subscribe(listener)注册监听器; • 通过subscribe(listener)返回的函数注销监听器。
根据已有的Reducer来创建Store是一件非常容易的事情,例如Redux提供的createStore函数可以很方便的创建一个新的Store。
代码语言:javascript复制import { createStore } from 'redux'
import todoApp from './reducers'
// 使用createStore函数创建Store
let store = createStore(todoApp)
其中,createStore函数的第二个参数是可选的,该参数用于设置state的初始状态。而这对于开发同构应用时非常有用的,可以让服务器端redux应用的state与客户端的state保持一致,并用于本地数据初始化。
代码语言:javascript复制let store = createStore(todoApp, window.STATE_FROM_SERVER)
Store对象包含所有数据,如果想得到某个时刻的数据,则需要利用state来获取。例如:
代码语言:javascript复制import { createStore } from 'redux';
const store = createStore(fn);
//利用store.getState()获取state
const state = store.getState();
Redux规定,一个state只能对应一个view,只要state相同得到的view就相同,这也是Redux框架的重要特性之一。
到此,关于Redux的运作流程就非常的清晰了,下面总结下Redux的运作流程。
- 当用户触摸界面时,调用store.dispatch(action)捕捉具体的action动作。
- 然后Redux的store自动调用reducer函数,store传递两个参数给reducer函数:当前state和收到的action。其中,reducer函数必须是一个纯函数,该函数会返回一个新的state。
- 根reducer会把多个子reducer的返回结果合并成最终的应用状态,在这一过程中,可以使用Redux提供的combineReducers方法。使用combineReducers方法时,action会传递给每个子的reducer进行处理,在子reducer处理后会将结果返回给根reducer合并成最终的应用状态。
- store调用store.subscribe(listener)监听state的变化,state一旦发生改变就会触发store的更新,最终view会根据store数据的更新刷新界面。
Redux实现
1,创建store
store就是redux的一个数据中心,简单的理解就是我们所有的数据都会存放在里面,然后在界面上使用时,从中取出对应的数据。因此首先我们要创建一个这样的store,可以通过redux提供的createStore方法来创建。
代码语言:javascript复制xport default function createStore(reducer, preloadedState, enhancer) {
...
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
可以看到createStore有三个参数,返回一个对象,里面有我们常用的方法,下面一一来看一下。
getState
getState用于获取当前的状态,格式如下:
代码语言:javascript复制function getState() {
return currentState
}
Redux内部通过currentState变量保存当前store,变量初始值即我们调用时传进来的preloadedState,getState()就是返回这个变量。
subscribe
代码本身也不难,就是通过nextListeners数组保存所有的回调函数,外部调用subscribe时,会将传入的listener插入到nextListeners数组中,并返回unsubscribe函数,通过此函数可以删除nextListeners中对应的回调。以下是该函数的具体实现:
代码语言:javascript复制var currentListeners = []
var nextListeners = currentListeners
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice() //生成一个新的数组
}
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
var isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
var index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
可以发现,上面的源码使用currentListeners和nextListeners两个数组来保存,主要原因是在dispatch函数中会遍历nextListeners,这时候可能会客户可能会继续调用subscribe插入listener,为了保证遍历时nextListeners不变化,需要一个临时的数组保存。
dispatch
当view dispatch一个action后,就会调用此action对应的reducer,下面是它的源码:
代码语言:javascript复制function dispatch(action) {
...
try {
isDispatching = true
currentState = currentReducer(currentState, action) //调用reducer处理
} finally {
isDispatching = false
}
var listeners = currentListeners = nextListeners
for (var i = 0; i < listeners.length; i ) {
var listener = listeners[i]
listener()
}
...
}
从上面的源码可以发现,dispatch函数在调用了currentReducer以后,遍历nextListeners数组,回调所有通过subscribe注册的函数,这样在每次store数据更新,组件就能立即获取到最新的数据。
replaceReducer
replaceReducer是切换当前的reducer,虽然代码只有几行,但是在用到时功能非常强大,它能够实现代码热更新的功能,即在代码中根据不同的情况,对同一action调用不同的reducer,从而得到不同的数据。
代码语言:javascript复制function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
bindActionCreators
bindActionCreators方法的目的就是简化action的分发,我们在触发一个action时,最基本的调用是dispatch(action(param))。这样需要在每个调用的地方都写dispatch,非常麻烦。bindActionCreators就是将action封装了一层,返回一个封装过的对象,此后我们要出发action时直接调用action(param)就可以了。
react-redux
redux作为一个通用的状态管理库,它不只针对react,还可以作用于其它的像vue等。因此react要想完美的应用redux,还需要封装一层,react-redux就是此作用。react-redux库相对简单些,它提供了一个react组件Provider和一个方法connect。下面是react-redux最简单的写法:
代码语言:javascript复制import { Provider } from 'react-redux'; // 引入 react-redux
……
render(
<Provider store={store}>
<Sample />
</Provider>,
document.getElementById('app'),
);
connect方法复杂点,它返回一个函数,此函数的功能是创建一个connect组件包在WrappedComponent组件外面,connect组件复制了WrappedComponent组件的所有属性,并通过redux的subscribe方法注册监听,当store数据变化后,connect就会更新state,然后通过mapStateToProps方法选取需要的state,如果此部分state更新了,connect的render方法就会返回新的组件。
代码语言:javascript复制export default function connect(mapStateToProps, mapDispatchToProps, mergeProps, options = {}) {
...
return function wrapWithConnect(WrappedComponent) {
...
}
}
本文不详细介绍React-Redux,可以访问下面的链接React-Redux简介及应用。