阅读 175

深入理解redux之从redux源码到react-redux的原理

在使用react的过程中,用redux来管理应用中的状态,使应用流更清晰的同时也会有小小的疑惑,比如reducer在redux中时怎么发挥作用的,为什么只要写好reducer,和dispatch特定action配合,store的状态就会更新了,而react组件又是如何和store的状态产生关系的,为什么随着store状态的更新,react组件会被自动更新,下面就从redux的源码开始来总结下这其中的缘由~

redux管理状态的原理

redux是继承自flux体系,但它放弃了dispatcher,无需使用 event emitters(事件发送器)(在dispatcher中特定action逻辑里触发事件,组件里监听事件),而使用纯 reducer来代替,那么reducer是如何被调度的,又是如何影响状态更新的,不妨通过源码的逻辑来了解和加深一下~

从redux源码看action,reducer,和store之间的关系

reducer通常是由我们自己来写,在调用createStore函数生成store时来传入这个reducer,后续调用storedispatch方法来触发action时,则reducer函数会自动被调用来解析actin更新state,这一切的核心都在crateStore方法中

export  default function createStore(reducer, preloadedState, enhancer) {
    ...
    let currentReducer = reducer
    let currentState = preloadedState
    let currentListeners = []
    let nextListeners = currentListeners
    let isDispatching = false
    function getState() {
       ...
    }
    function subscribe(listener) {
        ...
    }
    function dispatch(action) {
        ...
    }
    function replaceReducer(nextReducer) {
        ...
    }
    function observable() {
        ...
    }
    return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
复制代码

可以看到createStore方法返回一个属性集合,我们所调用的redux的相关方法都是定义在createStroe方法内部,最后被通过这个属性集合中暴露出来,如处理action的dispath方法,同currentReducer,currentState是createStore方法中的私有变量,由dispath,subscribe,getState等方法共享,我们设置的reducer,redux的state状态,以及state改变之后应该自动触发哪些函数,这些逻辑都是通过这几个内部变量和函数来实现的,下面先来看一下几个核心方法,由我们直接接触到的dispath开始~

dispacth处理action

function dispatch(action) {
    ...
    try {
      //将flag置为true,表明处于分发逻辑中
      isDispatching = true 
      
      //currentReducer即为函入的reducer函数,这里会自动调用currentReducer函数,并将返回值赋给currentState
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }
    
    //调用监听函数
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    return action
}
复制代码

可以看到,在dispath中调用reducer方法处理action之后,返回值(新的state)会直接赋值给currentState,由此可以推测currentState应该就是getState要返回的状态

getState返回当前的状态

  function getState() {
    if (isDispatching) {
      throw new Error(
      ...
      )
    }
    return currentState //直接返回currentState内部变量
  }
复制代码

subscribe订阅状态

function subscribe(listener) {
    if (typeof listener !== 'function') {
      throw new Error(...)
    }
    if (isDispatching) {
      throw new Error(
        ...
      )
    }
    let isSubscribed = true
    ...
    nextListeners.push(listener)

    return function unsubscribe() {
      ...
    }
复制代码

在subscribe中,传入的listener函数会被添加进nextListeners数组中,当dispatch方法被调用时自动触发,react-redux的状态更新时,UI自动更新的特性是通过subscribe来实现的

通过redux来管理react的状态

首先,设想我们不知道react-redux库来连接react和redux,来试一下单纯的通过redux作为react组件的状态管理器

创建store

//首先,创建一个reducer处理函数
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return ...
    case ADD_TODO:
      return ...
    default:
      return state
  }
}
//创建store
const store = createStore(
  todoApp
);
复制代码

各组件之间共享store的状态

要在各组件之间共享变量store有两种方式可取,一种是通过props共享,一种是通过context实现,其中props共享变量需要每一层组件之间层层传递该变量,这样做无疑很麻烦,尤其是组件之前嵌套层次比较深的时候,所以我们这里用react的context属性来实现共享store

根组件提供context

class App extends Component {
  getChildContext() {
    return {
      store: this.props.store
    };
  }
  render() {
    return <ToDoList />;
  }
}

App.childContextTypes = {
  store: React.PropTypes.object
}
复制代码

子组件获取context

class VisibleTodoList extends Component {
  componentDidMount() {
    const { store } = this.context;
    this.unsubscribe = store.subscribe(() =>
      this.forceUpdate()
    );
  }

  render() {
    const props = this.props;
    const { store } = this.context;
    const state = store.getState();
    // ...
  }
}

VisibleTodoList.contextTypes = {
  store: React.PropTypes.object
}

复制代码

如上所示,在需要获取store状态的组件中,在组件加载完成后需要获取关心得context的变量值store,同时订阅事件,当store的状态变化后触发组件本身的强制更新,而render中只需用store.getState获取store的状态值即可

用react-redux来简化组件的写法

上例中写了一个组件的实现还好,但当组件多的时候,每个组件都需写自己的获取context,订阅事件强制更新自身,获取state,这样的样板代码,实际是没必要的,完全可以把这部分抽象出来,而react-redux就帮我们做了这些,让我们省去了自定义context和订阅事件,获取state等操作

省去自定义含有context属性的根组件

要利用redux来管理状态,需要在祖先组件的context属性中指定store,而这一定式化的操作可以有react-redux库中的Provider来完成,示例如下

<Provider store={store}>
    <App />
  </Provider>
复制代码

省去手动订阅store的状态变化事件

上节中提到过,要实现store的状态更新后能自动更新react组件,则组件需在挂载后调用storesubscribe方法来订阅store中状态的变更,而这块儿样板代码则可以由react-redux库中的connect创建的容器组件来自动完成


class TodoList extends Component {
 render(){
     ...
 }
}
const mapStateToProps = state => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}
const mapDispatchToProps = dispatch => {
  return {
    onTodoClick: id => {
      dispatch(toggleTodo(id))
    }
  }
}
const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)
复制代码

其中mapStateToProps提供从Redux store state到展示组件的 props的映射 ,mapDispatchToProps接收dispatch方法并提供期望注入到展示组件的 props 中的回调方法。

关注下面的标签,发现更多相似文章
评论