Middleware
作用就相当于后端的过滤器,可以被链式组合,它提供的是位于action被发起后,到达reducer之前的扩展点,可以进行日志记录,创建崩溃报告,调用异步接口或路由等。
模拟几个中间件
柯里化:
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
}
上述代码是箭头函数写法等同于
function logger(store) {
return function wrapDispatchToAddLogging(next) {
return function dispatchAndLog(action) {
console.log('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
return result;
}
}
}
把各个中间件捏在一起:这也是定义中间件时为什么要柯里化的原因:store一直是一样的,而dispatch需要使用上次包装后的新的dispatch,action则是在最终业务代码中传入的。
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})
}
// 在每一个 middleware 中变换 dispatch 方法。
middlewares.forEach(middleware => (store.dispatch = middleware(store)))
要将用dispatch参数保存下当前的store.dispatch,在每一个 middleware 中变换 dispatch 方法。而不是好像是为了 =====>>>>
它用了一个非常巧妙的方式,以确保如果你在 middleware 中调用的是 store.dispatch(action) 而不是 next(action),那么这个操作会再次遍历包含当前 middleware 在内的整个 middleware 链。这对异步的 middleware 非常有用,正如我们在之前的章节中提到的。在创建阶段调用 dispatch 时你需要特别注意,详见下方警告。
暂时看不太懂。。。
实际使用:
import { createStore, combineReducers, applyMiddleware } from 'redux';
const todoApp = combineReducers(reducers);
const store = createStore(
todoApp,
applyMiddleware(logger, crashReporter)
)
//疑问: 实际使用中,并没有传入store, 直接传入的就是各个中间件,那上述我们自己写的applyMiddleware方法中的第一个参数是哪里来的。
结合Router
在使用router时候仍然使用Provider包裹传入store
import { Provider } from 'react-redux';
...
const Root = ({store}) => (
<Provider store={store}>
<Router>
<Route path="/:id" component={App} />
</Router>
</Provider>
);
...
const store = createStore(todoApp);
render(<Root store={store} />, document.getElementById('root'));
mapStateToProps方法的第一个参数state就是store, 第二个参数ownProps是其父组件传递给他的props;
拓展Redux功能
使用middleware (拓展dispatch函数的功能) 或 enhancer(拓展store的功能) 来拓展Redux store的功能。
封装为configureStore.js :
import { applyMiddleware, compose, createStore } from 'redux';
import thunkMiddleware from 'redux-thunk';
import monitorReducersEnhancer from './enhancers/monitorReducers';
import loggerMiddleware from './middleware/logger';
import rootReducer from './reducers';
export default function configureStore(preloadedState) {
const middlewares = [loggerMiddleware, thunkMiddleware];
const middlewareEnhancer = applyMiddleware(...middlewares);
const enhancers = [middlewareEnhancer, monitorReducerEnhancer];
const composedEnhancers = compose(...enhancers);
const store = createStore(rootReducer, preloadedState, composeEnhancers);
return store;
}
计算衍生数据
Reselect库可以创建可记忆的、可组合的selector函数。Reselect selectors可以用来高效的计算Redux store里的衍生数据。(比如,TodoList筛选have done的item)
Reselect 提供 createSelector 函数来创建可记忆的 selector。createSelector 接收一个 input-selectors 数组和一个转换函数作为参数。如果 state tree 的改变会引起 input-selector 值变化,那么 selector 会调用转换函数,传入 input-selectors 作为参数,并返回结果。如果 input-selectors 的值和前一次的一样,它将会直接返回前一次计算的数据,而不会再调用一次转换函数。
import { createSelector } from 'reselect;
const getVisibilityFilter = (state, props) => state.todoLists[props.listId].visibilityFilter;
const getTodos = state => state.todos;
export const getVisibleTodos = createSelector(
[getVisibilityFilter, getTodos], //由方法组成的数组
(visibilityFilter, todos) => {
switch(visibilityFilter) {
case 'SHOW_ALL':
return todos
case 'SHOW_DONE':
return todos.filter(t => t.completed)
//...
}
}
)
使用:
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
可以组合: 把getVisibleTodos当作input-selectors的其中一个。
问题: 上述如果交换渲染listId=1和listId=2就会导致每次传入的input-selectors不一样,因此会重新计算而不是取缓存,这样会降低性能。解决方案: 每一个组件实例都有一个自己的selector。原理:如果 connect 的 mapStateToProps 返回的不是一个对象而是一个函数,他将被用做为每个容器的实例创建一个单独的 mapStateToProps 函数。
const makeGetVisibleTodos = () => {
return createSelector(
[getVisibilityFilter, getTodos],
(visibilityFilter, todos) => {
switch (visibilityFilter) {
case 'SHOW_DONE':
return todos.filter(todo => todo.completed)
//...
}
}
)
}
const makeMapStateToProps = () => {
const getVisibleTodos = makeGetVisibleTodos();
const mapStateToProps = (state, props) => {
return {
todos: getVisibleTodos(state, props)
}
}
return mapStateToProps
}
const VisibleTodoList = connect(
makeMapStateToProps,
mapDispatchToProps
)(TodoList)
撤销重做
其实就是多定义past和future数组,类似浏览器history前进或回退一样进出栈。