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]) 
  • 它是将actionreducer联系到一起的对象,主要有以下职责:
    • 维持应用的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->viewview->store单向流动的,第一幅图详细展示了数据单向流动的细节部分。

中间件(middleware)

  1. 原型
  2. redux中间件就是一个函数,将store.dispatch进行改造,在发出action和执行reducer这两步之间添加其他功能
  3. 中间件的基本实现过程(理解中间件也是理解函数式编程)
  • 直接替换`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)即可
  1. 上面这些步骤就是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

  1. <Provider store>
    说明: <Provider store>使组件层级的connect()方法都能够获得Redux store,一般情况下,根组件应该嵌套在<Provider>中,这样就能够使用connect(),其中属性store是应用程序中唯一的Redux store对象

  2. 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,而且组件收到新的propsmapStateToProps也会被调用。
    • [mapDispatchToProps(dispatch, [ownProps]): dispatchProps] (Object or Function)若该回调方法的返回值(即传递)是一个对象,则每个定义在该对象的函数都被当作Redux action creator,而且这个对象会与Redux store绑定在一起,其中所定义的方法名将作为属性名,合并到组件的props中;若该回调方法返回值是一个方法,该方法接收一个dispatch方法,然后自定义如何返回一个对象,这个对象通过dispatch方法与Redux action creator以某种方式绑在一起;若省略该参数,默认情况下dispatch会注入到组件的props中。若指定了该回调方法中的第二个参数ownProps,则该参数的值会传递到组件的props,而且组件收到新的propsmapDispatchToProps也会被调用。
    • [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 storestate 作为 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)

参考文档