1. redux简介
2013年 Facebook 提出了 Flux 架构的思想,引发了很多的实现。2015年,Redux 出现,将 Flux 与函数式编程结合一起,很短时间内就成为了最热门的前端架构。Redux 由 Flux 演变而来,但受 Elm 的启发,避开了 Flux 的复杂性。
Redux 可以运行于不同的环境(客户端、服务器、原生应用),并且易于测试。Redux 在 Vue、Node、React 中都可以使用。 它体小精悍(只有2kB,包括依赖)。
需要使用Redux的项目:
- 用户的使用方式复杂,如:模块特别多,跨模块要做数据通信
- 不同身份的用户有不同的使用方式(比如普通用户和管理员)
- 多个用户之间可以协作
- 与服务器大量交互,或者使用了WebSocket
- View要从多个来源获取数据
从组件层面考虑,什么样子的需要 Redux:
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
2. Redux 设计思想和原则
Redux的设计思想:
- 视图和状态是一一对应的,也就是当数据改变,视图就会自动改变。
- 所有的状态,保存在一个对象里面(唯一数据源)。
注意:flux、redux 都不是必须和 react 搭配使用的,因为 flux 和 redux 是完整的架构,在学习 react 的时候,只是将 react 的组件作为 redux 中的视图层去使用了。
Redux 的使用的三大原则:
- 唯一的数据源,整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
- State 是只读的,唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
- 使用纯函数来执行修改,为了描述 action 如何改变 state tree ,你需要编写 reducers。Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分。
纯函数:
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出
。Reducer不是只有Redux里才有,之前学的数组方法reduce, 它的第一个参数就是一个reducer。
纯函数是函数式编程的概念,必须遵守以下一些约束。
- 不得改写参数
- 不能调用系统 I/O 的API
- 不能调用 Date.now() 或者 Math.random() 等不纯的方法,因为每次会得到不一样的结果
由于 Reducer 是纯函数,就可以保证同样的State,必定得到同样的 View。但也正因为这一点,Reducer 函数里面不能改变 State,必须返回一个全新的对象,
3. redux 简单使用
说了这么多理论,都觉得有些枯燥了,下面我们来分析一个使用 redux 的简单案例:在 node 环境下通过 redux 实现对数字的加减操作。
首先在终端中执行下面的命令,安装 redux:
yarn add redux -S
index.js文件:
//解构出redux中的createStore方法
const {createStore} = require('redux')
/**
* reducer是形式为(state,action)=>state 的纯函数
* 在reducer中对state设置默认值
* reducer的作用是通过判断action传过来的值,对数据进行操作
* 当state变化后,需要返回一个新的state,而不是修改传入的参数
**/
const reducer = (state = {count: 1}, action) => {
switch (action.type) {
case "increment":
return {
count: state.count + 1
}
case "decrement":
return {
count: state.count - 1
}
default:
return state;
}
}
//使用createStore创建一个redux store,参数是reducer
//store里的方法有 dispatch subscribe getState replaceReducer
const store = createStore(reducer)
//手动订阅更新
store.subscribe(() => console.log(store.getState().count))
//派发事件
//改变内部state唯一的方法就是dispatch一个action
store.dispatch({type: 'increment'}) //2
store.dispatch({type: 'increment'}) //3
store.dispatch({type: 'increment'}) //4
通过上面这个案例,我们来分析一下 redux 中几个要点:
- Action:是一个 JavaScript 普通对象,是把数据从组件传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。
- Reducer:是一个纯函数,接收两个参数(state,action),它的作用是通过 action 传过来的值,对数据进行操作,返回一个新的state。
- Store:用来集中管理 state dispatch action,使用 createStore() 创建一个 store ,它的参数是Reducer。Store 提供以下几个方法:
- getState() 获取 state;
- dispatch(action) 派发事件,更新 state;
- subscribe(listener) 订阅事件,要先派发事件,再订阅事件;
- subscribe(listener) 返回的函数可以注销订阅事件;
- replaceReducer() 数据替换,使用频率不高。
总结:
项目中所有的 state 都以一个对象树的形式储存在一个唯一的 store 中。 改变 state 的唯一办法是把要做的修改变成一个普通对象,也就是 action。通过dispatch(action) 派发事件,在 reducer 函数中根据 action 传过来的内容操作 state,最后返回一个新的 state。通过 store.getState() 方法读取 state 里的数据。
4. Redux 流程
1、store 通过 reducers 创建了初始状态;
2、页面加载时,view 通过 store.getState() 获取到 state 并挂载到自己的状态上;
3、当用户操作页面发生改变时,调用了 actions 的方法,创建了带有标识信息的 action ;
4、actions 将 action 通过调用 store.dispatch 方法发送到了reducer 中;
5、reducer 接收到 action 并根据标识信息对数据进行处理后,返回新的 state;
6、store 的 state 被 reducer 更改为新 state 的时候,store.subscribe 方法里的回调函数会执行,此时就可以通知 view 去重新获取 state;
5. 在 react 中使用 redux
Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。尽管如此,Redux 还是和 React 和 Deku 这类库搭配起来用最好,因为这类库允许你以 state 函数的形式来描述界面,Redux 通过 action 的形式来发起 state 变化。
react-redux 提供两个核心的api:
- Provider: 提供store,根据单一store原则 ,一般只会出现在整个应用程序的最顶层。
- connect: 用于连接展示组件和 Redux store。
点击查看在react项目中如何使用 react-redux 实现一个简单的计数器功能
6. Redux 划分
Redux 不支持多个 store。相反,只有一个单一的store
和一个根级的reduce函数(reducer
)。随着应用不断变大,你应该把根级的reducer
拆成多个小的 reducers
,分别独立地操作state
树的不同部分,而不是添加新的stores
。这就像一个 React 应用只有一个根级的组件,这个根组件又由很多小组件构成。
不同的业务模块有自己的reducers
,使用 combineReducers
方法将所有的reducer
整合到一起。
注意:
- 分离 reducer 的时候,每一个 reducer 维护的状态都应该不同;
- 通过 store.getState 获取到的数据也是会按照 reducers 去划分的;
- 划分多个 reducer 的时候,默认状态只能创建在 reducer 中,因为划分 reducer 的目的,就是为了让每一个 reducer 都去独立管理一部分状态;
用法:
import {combineReducers} from 'redux'
//IndexReducer可以作为一个模块导入进来,这里为了展示方便,都写在一个文件里
const IndexReducer=(state={isShow:true},action)=>{
switch(action.type){
case "change_map_val":
return{
isShow:!state.isShow
}
default:
return state;
}
}
const reducer=combineReducers({
IndexReducer, //注意:在connect中读取值的时候,要使用state.IndexReducer
...
})
export default reducer
7. Redux 异步
通常情况下,action 只是一个普通的对象,不能包含异步操作,这导致了很多创建 action 的逻辑只能写在组件中,代码量较多
也不便于复用
,组件的业务逻辑也不清晰
,使用异步中间件后,可以通过actionCreator
异步编写action
,这样代码就会拆分到 actionCreator
中,可维护性大大提高,可以方便于测试、复用,同时 actionCreator 还集成了异步操作中不同的 action 派发机制,减少编码过程中的代码量。