Redux 是一种前端“架构模式”,是 Flux 架构的一种变种,用来提供可预测的状态管理。虽然经常和 React 一起被提及,但是 Redux 却不仅仅只能用于 React,还可以将其运用到其他前端库中,Vue Angular甚至是 jQuery。Redux 只是一种架构模式而已,并没有和其他库绑定在一起。而 React-redux 就是把 Redux 和 React.js 结合起来的一个库。就像 Vuex 一样,是一个与 Vue.js 结合的 Flux变种。
为什么要用 Redux
也许有人会问:为什么我们会需要 redux 呢? 嗯... 确实,我们必须要先了解我们为什么需要 redux? redux 的出现是为了解决什么问题?
那么,我们来考虑这么一种场景,在你构建的一棵组件树中,有A、B那么两个组件,它们需要共享同一个状态,你会怎么办呢?
我们可以通过状态提升的思路,将该状态提升到附近的公共父组件上面,然后通过 props
把状态传递给子组件,这样就可以在A、B组件之间共享数据了。确实可以,但是如果A、B的父组件在组件树向上好几个组件的位置呢?就需要将状态通过 props 一级一级往下传递,那么状态的传递路径就会非常长,而且中间组件根本就不需要访问这个状态。而且,如果后续有一个 C 组件也要访问该状态并且A、B、C的公共父组件还要往上呢?你就不得不修改之前代码了。很显然,这不是一种很好的解决方案,它会让我们的代码维护起来非常痛苦。
难道就没有其他方法可以解决这个问题吗?其实也有的,那就是 react 的 context,一个组件只要往自己的 context 里面放了某些状态,那么这个组件的所有子组件都可以直接访问这个状态而不需要通过中间组件的传递,看起来问题解决了嘛。
我们虽然解决了状态传递的问题却引入了新的问题,我们引入的 context 打破了组件和组件之间通过 props
传递数据的规范,极大地增强了组件之间的耦合性。而且 context 就像全局变量一样,里面的数据可以被子组件随意更改,可能会导致程序不可预测的运行。
这时候我们就该考虑使用 Redux 了,Redux 可以帮你创建应用的共享状态,并且不能随意的更改这些状态。
Redux 的基本概念
我们已经了解了为什么要使用 Redux,那么我们先了解下 Redux 的三个基本概念。
Store
我们可以通过 createStore 来创建 store
import { createStore } from 'redux';
const store = createStore(reducers);
在 Redux 中,应用程序只能拥有一个 store,用来保存整个应用程序的 state,相当于一个应用程序的共享状态。
我们可以通过 store.getState() 来获取应用程序的当前状态。但是我们却不能随意的修改状态,我们只能通过 store.dispatch(action) 来修改状态。
修改完状态之后,我们希望可以做些 view 层的改变,这时可以通过 store.subscribe(() => {}) 来注册视图变化的回调函数。
Actions
Actions 是一个 JavaScript 普通对象,用来描述应用程序中发生的一些事情,也是把数据从应用传递给 store 的唯一途径。
我们约定,action 内必须使用一个字符串类型的 type
字段来表示将要执行的动作,type
一般会被定义成字符串常量。
const ADD_TODO = 'ADD_TODO' { type: ADD_TODO, data: 'some data' }
我们除了直接以 JavaScript 普通对象的形式来定义 action 之外,也可以通过函数形式来定义 action,这个函数被称作 Action 创建函数( actionCreator )。
const ADD_TODO = 'ADD_TODO'; function addTodo(data) { return { type: ADD_TODO, data } }
这里 action 创建函数 addTodo 很简单,只是返回一个 action。 我们可以通过 store.dispatch 来通知需要修改状态
store.dispatch(addTodo('some data'));
Reducers
我们已经知道可以通过 action 来修改状态,但是 action 传递过来的只是简单的对象,并没有具体处理状态的逻辑,这就是 reducers 要做的事情了。
Reducer 必须是一个纯函数(一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,这个函数就叫做纯函数)。因为纯函数非常“靠谱”,执行一个纯函数不会产生不可预料的行为,也不会对外部产生影响。
function todoApp(state = { title: 'todoApp', todos: [] }, action) { switch (action.type) { case ADD_TODO: return Object.assign({}, state, { todos: [ ...state.todos, { data: action.data, completed: false } ] }) default: return state } }
todoApp 接收旧的 state 和 action,并返回新的state。注意这里我们是将 state 拷贝一份,再添加我们改动的值去覆盖原来的数据,重新组合成新的 state 返回,而不是直接修改 state。
这是因为如果你直接去改变 state 里对象的属性,那么就需要去比较新旧两个 state 的区别,而比较两个 Javascript 对象所有的属性是否相同就需要对它们进行深比较。但是在真实的应用中 js 的对象都很大,进行深比较的代价十分昂贵。而如果你返回的是一个全新的对象,就只需要比较新旧两个对象的存储地址是否相同就可以了。Redux 就是这么做的,如果你在 reducer 内部直接修改旧的 state 对象的属性值,那么新的 state 和旧的 state 将都指向同一个存储地址,Redux 会认为没有任何改变。
我们可以有多个 reducer,每个 reducer 只负责管理全局 state 中它负责的那部分,每个 reducer 的 state
参数可以都不同,分别对应它管理的那部分 state 数据。然后通过 combineReducers 组成根 reducer 用来创建一个store。
import { combineReducers } from 'redux'; const todosReducer = (state = [], action) => { // do something } const titleReducer = (state = '', action) => { // do something } const reducer = combineReducers({ todos: todosReducer, title: titleReducer }); // 等价于 function reducer(state = {}, action) { return { todos: todosReducer(state.todos, action), title: titleReducer(state.title, action) } }
combineReducers 这个函数会调用你的定义的 reducer,每个 reducer 根据它们的 key(todos, title) 来筛选出 state 中的一部分数据处理并返回一份副本,根 reducer 会把这些副本组合起来形成一个新的大对象。最后根 reducer 将这个大对象传回给 store,store 再将它设为最终的状态。
Redux 工作流程
redux 一些基本概念我们都清楚了,我们来总结一下,Redux 为我们所做的事情:
1. 一个存放应用程序共享 state 的地方
2. 一个去分发 actions 通过纯函数修改应用程序共享 state 的机制
3. 一个可以订阅 state 更新的机制
严格的单向数据流是 Redux 架构的设计核心。
我们只要清楚了 redux 中的数据流动的过程就明白 redux 整个工作流程了,我们从产生一个 action 的切入点来分析数据是怎样流动的。
1. 通过用户在视图层的交互产生了一个 action,这个 action 可能是通过 actionCreator 返回的。
2. store 接受这这个 action 之后,将当前的 state 和 action 一起传递给根 reducer。
3. 根 reducer 将 state 分配给子 reducer 进行处理,子 reducer 返回修改后的副本给根 reducer,根 reducer 整合子 reducer 返回的副本生成一个新的 state 副本返回给 store。
4. store 根据新的 state 触发视图层的渲染。
5. 用户看到交互后视图的变化,又高兴地发起了一个 action ...
更多精彩内容,欢迎关注微信公众号~