Redux入门
几个重要概念
Action
- action从字面意思来看,表示行为,动作,它是对行为的抽象;
- 它的作用是将数据从应用传递的store,它是store获取数据的唯一来源;
- 它的本质是JS普通对象,redux约定action内必须使用一个字符串类型的type字段来表示将要执行的动作。一般情况下,type会被定义成字符串常量,当应用达到一定的规模,建议使用单独的模块或文件夹来存放action;
- 除type字段外,action对象的结构完全自由发挥;
- 我们应该尽量减少在action中传递数据,实际操作中,可以使用唯一ID作为数据的引用标志。
- 一般来说,我们会用action创建函数来生成action,这样可以方便复用;
const addTodo = (text) => {
return {
type: 'ADD_TODO',
text
}
}
Reducer
- 它作用是根据action来更新state;
- 它是一个纯函数,它接收两个参数,action和旧的state,然后返回新的state,即
(previousState,action) => newState - 它的基本写法
const todo = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false //刚传入的待办项未完成
}
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state
}
// 把state和completed合并成一个对象返回
return Object.assign({}, state, {
completed: !state.completed
})
default:
return state;
}
}
- 可组合,redux中提供了combineReducers()工具类来组合多个子reducer;
import { combineReducers } from 'redux';
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp;
Store
- 根据字面意思解释,表示“存储,储藏”,实际上它就是用来存储数据的,也就是整个应用的state;
- redux应用中只用一个单一的store,当需要拆分数据时处理逻辑时,应该使用reducer组合,而不是创建多个store;
- 它返回的是一个JS对象,也就是状态树(state tree),这里所说的状态树,实际和根级reducer的返回值相同;
- store实际上的一个实例(instance);
import { createStore } from 'redux'
// createStore第一个参数是reducer,第二个参数是可选的,
// 用于设置state的初始状态
let store = createStore(reducer,[args])
- 它是将action和reducer联系到一起的对象,主要有以下职责:
- 维持应用的state;
- 提供
getState()方法获取state; - 提供
dispatch(action)方法更新state,当调用store.dispatch(action)方法时,会触发reducer的自动执行; - 通过
subscribe(listener)注册监听器,当state发生改变时,会自动执行该函数; - 通过
subscribe(listener)返回的函数注销监听器
- store是只读的,其所有状态的改变,必须通过触发action来实现,不能直接修改state;
- 在react-redux中,store中状态的改变会重新渲染DOM;
store.dispatch源码如下
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;
// 这里会执行reducer方法,
// 即执行创建store时中的reducer方法
// store=createStore(reducer,[args])
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = currentListeners = nextListeners;
for (var i = 0; i < listeners.length; i++) {
listeners[i]();
}
return action; // 返回值依然是action
}
数据流(单向的)
- 说明:
第二幅图表明组件之间彼此通信都是通过store来交换信息,store的改变也会引起view的重新渲染,数据永远都是从store->view或view->store单向流动的,第一幅图详细展示了数据单向流动的细节部分。
中间件(middleware)
- 原型
- redux中间件就是一个函数,将
store.dispatch进行改造,在发出action和执行reducer这两步之间添加其他功能 - 中间件的基本实现过程(理解中间件也是理解函数式编程)
- 直接替换`store.dispatch
let next= store.dispatch;
store.dispatch = function dispatchAddLog(action) {
// 打印action
console.log('dispatch',action);
// 在发出action之前进行其他操作
let result = next(action);
// 打印state
console.log('next state',store.getState());
return result;
}
// 当调用store.dispatch(action),
// 就会执行dispatchAddLog方法,
// 就能打印日志(action和state)
- 将第一步的改写进行封装,方便使用,而且还能实现多个不同功能的模块
function patchStoreToAddLogging(store) {
let next= store.dispatch;
store.dispatch = function dispatchAddLog(action){
// 打印action
console.log('dispatch',action);
// 在发出action之前进行其他操作
let result = next(action);
// 打印state
console.log('next state',store.getState());
return result;
}
}
// 使用时,先调用patchStoreToAddLogging(store),
// 由于patchStoreToAddLogging中已经改造了
// store.dispatch,
// 然后调用store.dispatch(action)时,
// 依然可以实现打印日志(action和state)的功能
- 将上一步的改写中,我们用自己的方法替换了
store.dispatch,下面可以尝试在函数中返回新的dispatch,然后我们在redux内部提供一个可以将上面改造的方法应用到store.dispatch中的辅助方法
function logger(store) {
let next = store.dispatch;
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action);
console.log('next state', store.getState())
return result
}
}
// redux内部的辅助方法
function applyMiddlewareByMonkeypatching(store, middlewares) {
middlewares = middlewares.slice();
middlewares.reverse();
// 后添加的中间件先执行
// 在每一个 middleware 中变换 dispatch 方法。
middlewares.forEach(middleware =>
store.dispatch = middleware(store)
// middleware实际就是上面logger方法,
// 此时store.dispatch就是返回的dispatchAndLog
)
}
// 现在我们可以用该方法应用多middleware
// applyMiddlewareByMonkeypatching(store,[logger [,otherMiddleware]])
// 然后我们使用store.dispatch(action)时,
// 就能实现middleware中的功能
- 让middleware以方法参数的形式接受一个
next()方法,而不是通过store的实例去获取,这种方法不仅避免了在执行多个middleware时,store.dispatch总是会指向原始的dispatch方法,而且还能实现链式调用效果
function logger(store){
return function wrapDispatchToAddLogging(next) {
return function dispatchAddLog(action) {
console.log('dispatching', action)
let result = next(action);
console.log('next state', store.getState())
return result;
}
}
}
// 下面是用es6箭头函数使其柯里化
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action);
console.log('next state', store.getState())
return result;
}
// 这里我们还是使用上面redux内部的应用middleware到store.dispatch的辅助方法
// 首先还是执行var wrapDispatchToAddLogging =
// applyMiddlewareByMonkeypatching(store,[logger [,otherMiddleware]])
// wrapDispatchToAddLogging会接收一个dispatch函数,返回一个新的disptch
// 这时applyMiddlewareByMonkeypatching中 store.dispatch实际上是
// wrapDispatchToAddLogging方法
// 然后执行wrapDispatchToAddLogging(store.dispatch)
// 最后执行store.dispatch(action)就能实现中级那件的功能
- 改写
applyMiddlewareByMonkeypatching()方法,得到新的applyMiddleWare(),在新的方法中,我们取得最终完整的被包装过的disptch()方法,并返回一个store的副本,下面改写的方法和redux中applyMiddleWare()的实现已经很接近
function applyMiddleware(store,middlewares) {
middlewares = middlewares.slice();
middlewares.reverse(); // 后添加的中间件先执行
let dispatch = store.dispatch;
// 在每一个 middleware 中变换 dispatch 方法。
middlewares.forEach(middleware =>
store.dispatch = middleware(store)(dispatch)
// middleware实际就是上面logger方法,
//此时store.dispatch就是返回的dispatchAndLog
)
// 返回一个新的store,并且store.dispatch已经被改造
return Object.assign({ },store,{ dispatch }
}
// 相比applyMiddlewareByMonkeypatching()方法,使用时减少一个步骤
// 首先调用applyMiddleware(store,[logger [,otherMiddleware]]),
// 然后直接使用store.dispatch(action)即可
- 上面这些步骤就是
redux中中间件实现原理,分析了这么多,现在来看如何使用中间件将中间件传人applyMiddleware中,并让其作为createStore的参数即可,若createStore接收整个应用的初始状态作为参数,则applyMiddleware就是第三个参数,否则就是第二个参数
import { createStore , combineReducers , applyMiddlewaare } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger(); // 通过createLogger()生成logger
// 应用到applyMiddleware方法中,并传人createStore(),就完成了store.dispatch()的增强
// 改造 const store = createStore(reducer,applyMiddleware(logger) );
react-redux的API
-
<Provider store>
说明:<Provider store>使组件层级的connect()方法都能够获得Redux store,一般情况下,根组件应该嵌套在<Provider>中,这样就能够使用connect(),其中属性store是应用程序中唯一的Redux store对象 -
connect([mapStateToProps],[mapDispatchToProps],[mergeProps],[options])(MyComponent)
2.1 概要:
该api是连接React组件和Redux store,连接操作不会改变原来的组件类,而是返回一个新的已与Redux store连接的组件。
2.2 参数说明:[mapStateToProps(state,[ownProps]):stateProps](Function)即状态到属性的映射,若定义该参数。组件将会监听Redux store的变化,当Redux store发生改变,mapStateProps方法就会被调用。该回调方法必须返回一个纯对象,这个对象会与组件的props合并。若省略该参数,组件便不会监听Redux store的变化。若知道指定第二个参数ownProps则该参数的值会传递到组件的props,而且组件收到新的props,mapStateToProps也会被调用。[mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function)若该回调方法的返回值(即传递)是一个对象,则每个定义在该对象的函数都被当作Redux action creator,而且这个对象会与Redux store绑定在一起,其中所定义的方法名将作为属性名,合并到组件的props中;若该回调方法返回值是一个方法,该方法接收一个dispatch方法,然后自定义如何返回一个对象,这个对象通过dispatch方法与Redux action creator以某种方式绑在一起;若省略该参数,默认情况下,dispatch会注入到组件的props中。若指定了该回调方法中的第二个参数ownProps,则该参数的值会传递到组件的props,而且组件收到新的props,mapDispatchToProps也会被调用。[mergeProps(stateProps, dispatchProps, ownProps): props] (Function)若指定了该参数mapStateToProps()与mapDispatchToProps()的执行结果和组件自身的 props 将传入到这个回调函数中,而且其返回的对象将作为props传递到被包装的组件中;若省略该参数,默认情况下会返回Object.assign({}, ownProps, stateProps, dispatchProps)的结果。[options] (Object)如果指定这个参数,可以定制 connector 的行为。MyComponent自己定义的组件
2.3 返回值
WrappedComponent (Component)传递到connect()函数的原始组件类。
2.4 备注
connect方法不会修改传入的React组件,返回的是一个新的已与 Redux store 连接的组件,而且你应该使用这个新组件。mapStateToProps函数接收整个Redux store的state作为props,然后返回一个传入到组件 props 的对象。该函数被称之为selector
react-redux约定的目录结构
总结
- redux有五个API,分别是:
createStore(reducer, [initialState])combineReducers(reducers)applyMiddleware(...middlewares)bindActionCreators(actionCreators, dispatch)compose(...functions)
createStore生成的store有四个 API,分别是:- getState()
- dispatch(action)
- sbuscribe(listener)
- replaceReducer(nextReducer)