Redux 的理解
随着前端应用的发展,JavaScript
需要管理比以前更多的状态 (state
),这些状态包括服务器响应,缓存数据,UI 状态等。状态管理的复杂程度主要体现在数据的变化和异步,在开发中要追踪数据的传递流程,保持界面与数据的一致性,异步状态的及时更新。redux
是一个状态容器,提供可预测化的状态管理。
- 为什么需要
redux
以 react
为例 (后面的示例也是 react),在 react
中,数据的传递是单向的,正常的使用没有问题,但是当组件变得复杂后就变得难以管理了,某一个子组件的数据来源可能是其父组件的父组件,这样数据就传递了 3 层,而且中间层级的组件很可能不需要用到这样的数据,假如子组件的部分交互需要更新父组件的某些数据,那么父组件的回调函数也需要一级一级的传递下去,如果层级再多点那么多余的数据传递就变得更像明显了。
上图左边便是传统的单项数据流,而右边是 redux 的数据传递。在 redux 中,有一个 store 进行数据的管理和消息的分发,其中的数据是全局唯一的,负责整个 APP 的状态数据。在这个基础下,所有的组件都可以从 store 中获取数据,也可以发送消息更新 store 中的数据,不需要组件间的数据传递。
- 核心概念
在 redux
中 store
中保存着全局的状态数据 (本质上是一个 JavaScript
对象),通过发送 action
(携带动作类型和具体数据的普通对象) 描述将要进行的数据更新类型和具体数据,reducer
在接受到数据后更新相应的 state
,然后再根据 store
的订阅情况进行界面更新。
- 三大原则
- 单一数据源
整个应用的 state
被存在一个 Object Tree
中,并且这个 Object Tree
在存在与唯一的 store
。
- state 是只读的
唯一改变 state
的方法是触发 action
,action
是一个描述事件的普通对象。
- 使用纯函数进行修改
reducer
进行数据修改,并且 reducer 必须是一个纯函数,接受 state 和 action,返回一个新的 state。
- action
action
是一个普通的 JavaScript
对象。
{
type: 'ADD_TODO',
text: 'redux',
id: Data.now,
}
type
字段描述事件行为,其他字段为需要更新的数据。
一般在使用 action
时会搭配 action createor
使用,action creater
是一个返回 action 的函数。
function addTodo(text) {
return {
type: 'ADD_TODO',
text: 'redux',
id: Data.now,
};
}
- reducer
reducer
是一个纯函数,接收 state
和 action
并返回一个新的 state
。如果没有可以匹配的 type
就返回 state
本身。
const todosReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
text: action.text,
completed: false,
id: Date.now(),
},
];
case 'TOGGLE_TODO':
return state.map(todo => {
if (todo.id === action.id) {
return {
...todo,
completed: !todo.completed,
};
}
return todo;
});
default:
return state;
}
};
const { createStore } = Redux;
const store = createStore(todosReducer);
一个 APP 中可以有多个 reducer
,reducer
也可以调用其他 reduce
更新 state
。
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
text: action.text,
completed: false,
id: Date.now(),
};
case 'TOGGLE_TODO':
return {
...state,
completed: state.id === action.id ? !state.completed : state.completed,
};
}
};
const TodosReducer = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [...state, todoReducer(undefined, action)];
case 'TOGGLE_TODO':
return state.map(todo => {
return todoReducer(todo, action);
});
default:
return state;
}
};
const { createStore } = Redux;
const store = createStore(todosReducer);
如上述代码,将 todo
的操作拆分到 todoReducer
中,todosReducer
调用 todoReducer
更新 todo
的数据内容。
如果是使用多个 reducer 组合可以使用 combineReducers 实现。
const visibilityFilter = (state = 'all', action) => {
switch (action.type) {
case 'SET_VISIBILITY_FILTER':
return action.filter;
default:
return state;
}
};
const { createStore, combineReducers } = Redux;
const store = createStore(
combineReducers({
todos: todosReducer,
visibilityFilter: visibilityFilter,
}),
);
- store
store 是将 action 和 reucer 连接在一起的对象,在 redux 中,发送 action 和监听刷新都是 store 的职责,
getState
获取当前状态;dispatch
发送 action;subscribe
监听状态更新,调用返回的函数可以取消监听;
// 发送action
store.dispatch({
type: 'ADD_TODO',
text: input.value,
});
// 设置监听
this.unsubscribe = store.subscribe(() => {
// 监听回调
});
// 取消监听
this.unsubscribe();
// createStore的简易实现
const createStore = reducer => {
let state = undefined;
let listeners = [];
const getState = () => state;
const dispatch = action => {
state = reducer(state, action);
listeners.forEach(listenter => listenter());
};
const subscribe = listener => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
};
};
dispatch({});
return { getState, dispatch, subscribe };
};
- 中间件
在 redux
的状态更新过程中都是同步的,如果要处理异步消息怎么办呢?答案是中间件 (middleware
)。
中间件本质上就是对 store
进行增强,store
发起 action
,在到达 reducer
之前进行拦截并做相应的处理 (打印日志,网络请求等),然后再重新 dispatch 处理后的数据,使其到达 reducrer
进行状态更新。
// 简易的日志中间件
const logger = (store, action) => {
console.log('dispatch: ', action);
store.dispatch(action);
console.log('nextState: ', store.getState());
};
如果有多个中间件进行使用,道理是一样的。
// 修改后的middleware实现
const logger = store => next => action => {
console.log('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
return result;
};
const crashReporter = store => next => action => {
try {
return next(action);
} catch (err) {
console.error('Caught an exception!', err);
Raven.captureException(err, {
extra: {
action,
state: store.getState(),
},
});
throw err;
}
};
// middleware的应用实现
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice();
middlewares.reverse();
let dispatch = store.dispatch;
middlewares.forEach(middleware => (dispatch = middleware(store)(dispatch)));
return Object.assign({}, store, { dispatch });
}