Lemonreds / snippets

code snippets.
https://github.com/Lemonreds/snippets/issues
2 stars 0 forks source link

[2018-08-26]: 阅读Redux源码01 - Redux简介 #5

Open Lemonreds opened 6 years ago

Lemonreds commented 6 years ago

React和Redux

在许多React应用中,通常都会使用Redux作为扩展,为的是解决一些React的痛点,使开发工作变得更为轻松。其中一个最常见的场景便是“组件通信”。在React中,父子组件之间交流数据可以通过props,嵌套多层的父子组件则可以使用context传递数据,而两个独立的、不相关的组件之间进行数据交流,就会变得非常困难,例如:

<App>
<Header />
<Footer />
</App>

Header组件和Footer组件进行数据交流的唯一方法就是状态提升,也就是将共享的状态提升到App这个组件,再由App组件分发props给Header组件和Footer组件。除此之外,React似乎不能提供更为优雅的解决方式。这还是最简单的场景,如果需要共享状态的两个组件位于不同组件内部嵌套层级很深的某一个角落中,这样的状态提升就会增加不少难度,并使代码臃肿冗余,难以阅读。

Redux就是解决方案,尽管Redux强调并非为React而生,但其与React配合,就会发生化合反应,变得异常强大。

Redux定义为 JavaScript 状态容器,提供可预测化的状态管理。何为状态?可以理解为随着时间变化而变化的数据,而Redux就是一个存放数据的容器,并负责对数据进行管理维护。上述遇到的组件通信困境的解决方法,就可以将组件共享的状态委托给Redux,由Redux代为管理,再由Redux分发状态给有需要的组件。组件也可以向Redux发出通信,更新容器中的数据。何为可预测化的状态管理?Redux提供单一的获取容器内数据的方法,同时更新容器内数据的方式也是唯一的,这样就很容易追踪到具体的组件,是以怎样的方式修改了容器中数据,从而使得调试和溯源变得简单。

Redux 思想

Redux整个框架设计都离不开以下几个概念,理解了以下的概念后,明白它们之间是如何相互作用的,也就明白了Redux的精髓,接下里简单介绍这些概念以及在JavaScript中的实现:

Store 

状态管理的容器,内部维护state状态树,对外暴露多个方法,如用于获取state的方法,更新state的方法,注册注销监听器的方法。Reudx强调,一个Redux应用只有一个单一的 store,当你的state变得复杂、需要拆分数据处理逻辑时,应该使用 reducer 组合,而不是创建多个 store。

在Redux的实现中,Store是一个对象,提供以下接口来做到上面的操作:

State 

状态树,存放数据,被保存于Store容器中,由Store负责维护,可以是任意类型,但一般是一个普通对象。

例如一个 Todo 应用的 State 可以是这样:

{
  // 可以是任意的javascript类型数据
  todos: [{ // array
    text: 'Eat food', //object
    completed: true
  }, {
    text: 'Exercise',
    completed: false
  }],
  visibilityFilter: 'SHOW_COMPLETED' // string
}

Action 

一个普通的对象,用于描述state即将发生什么变化。action 对象内必须使用一个字符串类型的 type 字段来表示将要执行的动作,其他字段可以任意定制,作为载荷携带任意数据。

例如 描述向状态树增加一个新的TODO的action可以是这样:

{ 
    // type 是必须的
    // redux 在处理 action 的时候
    // 根据 type 来执行不同的数据处理逻辑
    type: 'ADD_TODO', 
    // 其他字段可以任意定制
    // 这里传入 string 作为新增 TODO 的内容
    text: 'Go to swimming pool'
}

ActionCreator

这个概念并非核心概念,actionCreator产生的背景是:如果我们每次更新state树都要在代码中写一个个action,action的type又是必须的,这样就会造成代码的重复冗余,所以衍生出了一个actionCreator的概念:(其实是因为懒和不美观..)

我们直接看代码就明白了:

// actionCreator
function addTodo(text){
    return{
         type: 'ADD_TODO',
         text
    }
}
// 所以 我们可以这样使用
dispatc(addTodo('writing something'))

actionCreator就是一个产生action(注意,action的对象)的工厂函数,为偷懒而生,这样我们就不用每次都写重复的type,只需传入每次action不同的数据载荷即可,在代码中也可以减少冗余。Redux还提供了一个api,连dispatch也省略了。。后续会说到。

Reducer

是一个接收state和action的纯函数,纯函数就是不对传入参数进行修改的函数。函数内部解析action的不同封装数据处理逻辑,最终返回一个新的state,reducer必须要返回一个state。

例如 TODO应用的一个reducer可能是这样 :

// state = [] 为状态指定一个默认值
// 这样调用时 todos( undefined , action ) state就会被赋值为 []
function todos(state = [], action) {
 // 解析action.type 根据action的不同处理state
  switch (action.type) {
  case 'ADD_TODO':
    // 增加一个TODO 
    //返回一个新的state
    return state.concat([{ text: action.text, completed: false }]);
  case 'TOGGLE_TODO':     
    // 修改TODO的状态
    // 返回一个新的state
    return state.map((todo, index) =>
      action.index === index ?
        { text: todo.text, completed: !todo.completed } :
        todo
   )
  // 未知的action也要返回state 
  default:
    return state;
  }
}

总结

以上几乎是Redux的全部了,后面的源码解读中会多次出现这些概念,一个简单的记忆方法是,记住这几个概念是如何用Javascript表现的,例如,state最好的类型是对象,action是一个对象,reducer是一个纯函数。

再来总结一下Redux的运作流程:Redux负责创建一个负责管理维护State状态树的Store容器。Store对外提供来一系列的方法用于获取、更新、监听状态树。当需要更新状态树时,使用Store提供的 dispatch(action) 方法,Store容器内部由 reducer 负责来解析action,reducer函数接收两个参数,一个是当前的state,另一个是发起更新的action,根据action.type和action携带的数据对state树进行更新,并最终返回新的state。一个Store容器内部可以由多个reducer组合(当然也可以只有一个reducer)每一个reducer负责维护一部分数据,便于划分状态树的数据处理逻辑。

数据流动也就是:

Store ----(currentState )----> View ----(action) ---> Reducer ----( new State )---> Store

阅读Redux的源代码,我们就会发现,其主要文件还没有十个,代码也非常精炼,Redux的思想结晶就蕴藏在那么几百行代码中。

/src/index.js,redux的API都由此导出:

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

需要指出的是,Redux并没有那么复杂,接下来会对其API代码逐一进行解读。